Много изменений в бд

This commit is contained in:
2025-05-30 12:19:25 +03:00
parent 21b202f090
commit f21acb23ab
6 changed files with 173 additions and 65 deletions

View File

@@ -5,7 +5,7 @@ from colorama import Fore
import requests
from tqdm import tqdm
from config import ClockifyConfig, config
from config import ClockifyConfig, config, db
from classes import Activity, Tag, VetroProject
from exceptions import InvalidToken
from utils import parse_task_id, time_to_hour
@@ -51,6 +51,9 @@ class ClockifyAPI(ClockifyConfig):
except ValueError:
logger.info(f'{activity["timeInterval"]["start"]}: {activity["description"]} - Неправильный формат тега {tag}')
continue
author = db.get_user_by("clockify_user_id", activity["userId"])
result.append(
Activity(
id=activity["id"],
@@ -62,7 +65,8 @@ class ClockifyAPI(ClockifyConfig):
time_spent=time_to_hour((end if end else datetime.utcnow()) - start),
date_start=start,
date_end=end,
is_tracked=activity["billable"]
is_tracked=activity["billable"],
author_id=author["id"]
)
)
logger.info(f"Сохранено задач: {len(result)}")
@@ -135,16 +139,27 @@ class ClockifyAPI(ClockifyConfig):
resp = resp.json()
logger.debug({"id": resp["id"], "email": resp["email"], "name": resp["name"]})
return token
def getenvs(self):
"""Получает нужные USER_ID и WORKSPACE_ID с сервера Clockify"""
def get_user(self):
"""Получает нужные данные пользователя с сервера Clockify"""
resp = requests.get(self.base_url + self.user_url, headers={"X-API-KEY": self.token})
if not resp.ok:
logger.error(resp.json())
raise requests.exceptions.RequestException(Fore.RED + "Ошибка запроса." + Fore.RESET)
json = resp.json()
return {
"id": json['id'],
"workspace_id": json['activeWorkspace'],
"email": json["email"],
"username": json["name"]
}
def getenvs(self):
"""Получает нужные USER_ID и WORKSPACE_ID с сервера Clockify"""
user = self.get_user()
with open(config.ENV_TXT, "w") as file:
try:
file.write(f"USER_ID={resp.json()['id']}\nWORKSPACE_ID={resp.json()['activeWorkspace']}")
file.write(f"USER_ID={user['id']}\nWORKSPACE_ID={user['workspace_id']}")
logger.info(f"{Fore.GREEN}Необходимые переменные были сохранены в файл {config.ENV_TXT}{Fore.RESET}")
except Exception as e:
logger.error(f"{Fore.RED}Не удалось сохранить необходимые переменные.{Fore.RESET}")

View File

@@ -23,7 +23,8 @@ class RedmineAPI(RedmineConfig):
"""
def __init__(self, token):
super().__init__()
self.token = self._check_token(token)
self.token = token
self._check_token()
def track_activities(self, activities: list[Activity]) -> list[Activity]:
"""Заносит трудочасы на сервер Redmine"""
@@ -75,12 +76,21 @@ class RedmineAPI(RedmineConfig):
if act.get("active")
]
def _check_token(self, token):
"""Проверяет токен"""
resp = requests.get(self.base_url + self.my_account_url, headers={"X-Redmine-Api-Key": token})
def get_user(self):
"""Проверяет токен и возвращает данные юзера"""
resp = requests.get(self.base_url + self.my_account_url, headers={"X-Redmine-Api-Key": self.token})
if not resp.ok:
logger.error("Invalid Token for RedmineAPI")
raise InvalidToken
resp = resp.json()['user']
logger.debug({"login": resp["login"], "email": resp["mail"], "full_name": f'{resp["firstname"]} {resp["lastname"]}'})
return token
json = resp.json()["user"]
return {
"id": json["id"],
"first_name": json["firstname"],
"last_name": json["lastname"],
"email": json["mail"],
"username": json["login"]
}
def _check_token(self):
user = self.get_user()
logging.debug(user)

View File

@@ -7,6 +7,7 @@ from enums import ClockifyProjects, VetroProjects
@dataclass
class Activity:
id: str
author_id: int
vetro_project_id: VetroProjects
task_id: int
tag_id: str

View File

@@ -1,44 +1,60 @@
CREATE TABLE IF NOT EXISTS "activities" (
"id" VARCHAR NOT NULL UNIQUE,
"description" VARCHAR NOT NULL,
"time_spent" NUMERIC(3, 3) NOT NULL,
"date_start" DATETIME NOT NULL,
"date_end" DATETIME,
"is_tracked" BOOLEAN NOT NULL DEFAULT false,
"vetro_project_id" VARCHAR NOT NULL,
"tag_id" VARCHAR NOT NULL,
"project_id" VARCHAR NOT NULL,
PRIMARY KEY("id"),
FOREIGN KEY ("tag_id") REFERENCES "tags"("id") ON UPDATE NO ACTION ON DELETE NO ACTION,
FOREIGN KEY ("vetro_project_id") REFERENCES "vetro_projects"("id") ON UPDATE NO ACTION ON DELETE NO ACTION
CREATE TABLE IF NOT EXISTS "activity" (
"id" VARCHAR NOT NULL UNIQUE,
"description" VARCHAR NOT NULL,
"author_id" INTEGER NOT NULL,
"time_spent" NUMERIC NOT NULL,
"date_start" DATETIME NOT NULL,
"date_end" DATETIME,
"is_tracked" BOOLEAN NOT NULL DEFAULT false,
"vetro_project_id" VARCHAR NOT NULL,
"tag_id" VARCHAR NOT NULL,
"project_id" VARCHAR NOT NULL,
PRIMARY KEY("id"),
FOREIGN KEY ("author_id") REFERENCES "user"("id")
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE IF NOT EXISTS "tags" (
"id" VARCHAR NOT NULL UNIQUE,
"title" VARCHAR NOT NULL,
PRIMARY KEY("id")
CREATE TABLE IF NOT EXISTS "tag" (
"id" VARCHAR NOT NULL UNIQUE,
"title" VARCHAR NOT NULL,
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "vetro_projects" (
"id" VARCHAR NOT NULL UNIQUE,
"title" VARCHAR NOT NULL,
PRIMARY KEY("id")
CREATE TABLE IF NOT EXISTS "vetro_project" (
"id" VARCHAR NOT NULL UNIQUE,
"title" VARCHAR NOT NULL,
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "activity_types" (
"id" INTEGER NOT NULL UNIQUE,
"title" VARCHAR NOT NULL,
PRIMARY KEY("id")
CREATE TABLE IF NOT EXISTS "activity_type" (
"id" INTEGER NOT NULL UNIQUE,
"title" VARCHAR NOT NULL,
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "time_entry" (
"id" INTEGER PRIMARY KEY,
"date" DATETIME NOT NULL,
"description" VARCHAR NOT NULL,
"activity_id" VARCHAR NOT NULL,
"time_spent" NUMERIC(3, 3) NOT NULL,
"task_id" INTEGER NOT NULL,
"activity_type_id" INTEGER NOT NULL,
FOREIGN KEY ("activity_id") REFERENCES "activities"("id") ON UPDATE NO ACTION ON DELETE NO ACTION,
FOREIGN KEY ("activity_type_id") REFERENCES "activity_types"("id") ON UPDATE NO ACTION ON DELETE NO ACTION
);
"id" INTEGER,
"author_id" INTEGER NOT NULL,
"description" VARCHAR NOT NULL,
"date" DATETIME NOT NULL,
"activity_id" VARCHAR NOT NULL,
"time_spent" NUMERIC NOT NULL,
"task_id" INTEGER NOT NULL,
"activity_type_id" INTEGER NOT NULL,
PRIMARY KEY("id"),
FOREIGN KEY ("author_id") REFERENCES "user"("id")
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE IF NOT EXISTS "user" (
"id" INTEGER NOT NULL UNIQUE,
"first_name" VARCHAR,
"last_name" VARCHAR,
"clockify_user_id" VARCHAR NOT NULL UNIQUE,
"redmine_user_id" VARCHAR NOT NULL UNIQUE,
"clockify_email" VARCHAR UNIQUE,
"clockify_username" VARCHAR UNIQUE,
"redmine_email" VARCHAR UNIQUE,
"redmine_username" VARCHAR UNIQUE,
PRIMARY KEY("id")
);

View File

@@ -34,16 +34,17 @@ class Database:
is_updated = True
for act in activities:
if is_updated:
tags = self.cursor.execute("SELECT * FROM tags WHERE id = ?", (act.tag_id, )).fetchall()
vetro_projects = self.cursor.execute("SELECT * FROM vetro_projects WHERE id = ?", (act.vetro_project_id, )).fetchall()
tags = self.cursor.execute("SELECT * FROM tag WHERE id = ?", (act.tag_id, )).fetchall()
vetro_projects = self.cursor.execute("SELECT * FROM vetro_project WHERE id = ?", (act.vetro_project_id, )).fetchall()
if not tags or not vetro_projects:
is_updated = False
try:
self.cursor.execute(
"INSERT INTO activities VALUES(?,?,?,?,?,?,?,?,?)",
"INSERT INTO activity VALUES(?,?,?,?,?,?,?,?,?,?)",
(
act.id,
act.description,
act.author_id,
act.time_spent,
act.date_start,
act.date_end,
@@ -55,7 +56,7 @@ class Database:
)
except sq.IntegrityError:
self.cursor.execute(
"""UPDATE activities
"""UPDATE activity
SET description = ?, time_spent = ?, date_start = ?, date_end = ?, is_tracked = ?, vetro_project_id = ?, tag_id = ?, project_id = ?
WHERE id = ?""",
(
@@ -81,15 +82,16 @@ class Database:
try:
self.cursor.execute(
"""INSERT INTO time_entry
(date, description, activity_id, time_spent, task_id, activity_type_id)
VALUES(?,?,?,?,?,?)""",
(date, description, activity_id, time_spent, task_id, activity_type_id, author_id)
VALUES(?,?,?,?,?,?,?)""",
(
act.date_start.date(),
act.description,
act.id,
act.time_spent,
act.task_id,
ActivityTypes.DEVELOPMENT
ActivityTypes.DEVELOPMENT,
act.author_id
)
)
self.con.commit()
@@ -101,10 +103,10 @@ class Database:
"""Сохраняет в базу данных теги Clockify"""
for tag in tags:
try:
self.cursor.execute("INSERT INTO tags VALUES(?, ?)",(tag.id,tag.title))
self.cursor.execute("INSERT INTO tag VALUES(?, ?)",(tag.id,tag.title))
except sq.IntegrityError:
self.cursor.execute(
"""UPDATE tags
"""UPDATE tag
SET title = ?
WHERE id = ?""",
(tag.title, tag.id)
@@ -117,10 +119,10 @@ class Database:
"""Сохраняет в базу данных Проекты (ДО) Ветро Clockify"""
for vetro_project in vetro_projects:
try:
self.cursor.execute("INSERT INTO vetro_projects VALUES(?, ?)", (vetro_project.id, vetro_project.title))
self.cursor.execute("INSERT INTO vetro_project VALUES(?, ?)", (vetro_project.id, vetro_project.title))
except sq.IntegrityError:
self.cursor.execute("""
UPDATE vetro_projects
UPDATE vetro_project
SET title = ?
WHERE id = ?""",
(vetro_project.title, vetro_project.id)
@@ -132,13 +134,63 @@ class Database:
"""Сохраняет в базу данных Деятельности Redmine"""
for activity_type in activity_types:
try:
self.cursor.execute("INSERT INTO activity_types VALUES(?, ?)", (activity_type.id, activity_type.title))
self.cursor.execute("INSERT INTO activity_type VALUES(?, ?)", (activity_type.id, activity_type.title))
except sq.IntegrityError:
self.cursor.execute("""
UPDATE activity_types
UPDATE activity_type
SET title = ?
WHERE id = ?""",
(activity_type.title, activity_type.id)
)
finally:
self.con.commit()
def insert_user(self, **kwargs):
"""Сохраняет в базу данных пользователя"""
print(kwargs.values())
try:
self.cursor.execute("""INSERT INTO user(
clockify_user_id, clockify_email, clockify_username,
redmine_user_id, redmine_email, first_name, last_name, redmine_username)
VALUES(?, ?, ?, ?, ?, ?, ?, ?)""", [v for k, v in kwargs.items()])
except sq.IntegrityError:
self.cursor.execute("""
UPDATE user
SET clockify_email = ?,
clockify_username = ?,
redmine_email = ?,
first_name = ?,
last_name = ?,
redmine_username = ?
WHERE redmine_user_id = ? AND clockify_user_id = ?""",
(
kwargs["clockify_email"],
kwargs["clockify_username"],
kwargs["redmine_email"],
kwargs["first_name"],
kwargs["last_name"],
kwargs["redmine_username"],
kwargs["redmine_user_id"],
kwargs["clockify_user_id"]
)
)
finally:
self.con.commit()
def get_user_by(self, by_field: str, value: str):
"""Получает пользователя из бд"""
try:
user = self.cursor.execute(f"SELECT * FROM user WHERE {by_field} = ?", (value,)).fetchone()
return {
"id": user[0],
"first_name": user[1],
"last_name": user[2],
"clockify_user_id": user[3],
"redmine_user_id": user[4],
"clockify_email": user[5],
"clockify_username": user[6],
"redmine_email": user[7],
"redmine_username": user[8]
}
except Exception as e:
logging.error(e)

View File

@@ -62,7 +62,21 @@ def main():
logger.info(f'{Fore.YELLOW}Проверьте файл "{config.ENV_TXT}". Если необходимых переменных в нём нет, то просто удалите его.{Fore.RESET}')
return
red = RedmineAPI(config.REDMINE_TOKEN)
user = clock.get_user()
clock_user = {
"clockify_user_id": user["id"],
"clockify_email": user["email"],
"clockify_username": user["username"],
}
user = red.get_user()
red_user = {
"redmine_user_id": user["id"],
"redmine_email": user["email"],
"first_name": user["first_name"],
"last_name": user["last_name"],
"redmine_username": user["username"]
}
db.insert_user(**clock_user, **red_user)
if args.init_db:
logger.info(Fore.YELLOW + "Обновление базы данных..." + Fore.RESET)
try:
@@ -74,13 +88,13 @@ def main():
else:
logger.info(Fore.GREEN + "База данных теперь актуальна." + Fore.RESET)
activities = clock.get_activities(
activities = sorted(clock.get_activities(
**{
"start": format_date(start),
"end": format_date(end),
"page-size": args.page_size,
},
)
), key=lambda x: x.date_start)
db.insert_activities(activities)
if args.debug:
logger.debug("Пропускаем этап загрузки трудочасов")