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

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

View File

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

View File

@@ -1,7 +1,8 @@
CREATE TABLE IF NOT EXISTS "activities" ( CREATE TABLE IF NOT EXISTS "activity" (
"id" VARCHAR NOT NULL UNIQUE, "id" VARCHAR NOT NULL UNIQUE,
"description" VARCHAR NOT NULL, "description" VARCHAR NOT NULL,
"time_spent" NUMERIC(3, 3) NOT NULL, "author_id" INTEGER NOT NULL,
"time_spent" NUMERIC NOT NULL,
"date_start" DATETIME NOT NULL, "date_start" DATETIME NOT NULL,
"date_end" DATETIME, "date_end" DATETIME,
"is_tracked" BOOLEAN NOT NULL DEFAULT false, "is_tracked" BOOLEAN NOT NULL DEFAULT false,
@@ -9,36 +10,51 @@ CREATE TABLE IF NOT EXISTS "activities" (
"tag_id" VARCHAR NOT NULL, "tag_id" VARCHAR NOT NULL,
"project_id" VARCHAR NOT NULL, "project_id" VARCHAR NOT NULL,
PRIMARY KEY("id"), PRIMARY KEY("id"),
FOREIGN KEY ("tag_id") REFERENCES "tags"("id") ON UPDATE NO ACTION ON DELETE NO ACTION, FOREIGN KEY ("author_id") REFERENCES "user"("id")
FOREIGN KEY ("vetro_project_id") REFERENCES "vetro_projects"("id") ON UPDATE NO ACTION ON DELETE NO ACTION ON UPDATE NO ACTION ON DELETE NO ACTION
); );
CREATE TABLE IF NOT EXISTS "tags" ( CREATE TABLE IF NOT EXISTS "tag" (
"id" VARCHAR NOT NULL UNIQUE, "id" VARCHAR NOT NULL UNIQUE,
"title" VARCHAR NOT NULL, "title" VARCHAR NOT NULL,
PRIMARY KEY("id") PRIMARY KEY("id")
); );
CREATE TABLE IF NOT EXISTS "vetro_projects" ( CREATE TABLE IF NOT EXISTS "vetro_project" (
"id" VARCHAR NOT NULL UNIQUE, "id" VARCHAR NOT NULL UNIQUE,
"title" VARCHAR NOT NULL, "title" VARCHAR NOT NULL,
PRIMARY KEY("id") PRIMARY KEY("id")
); );
CREATE TABLE IF NOT EXISTS "activity_types" ( CREATE TABLE IF NOT EXISTS "activity_type" (
"id" INTEGER NOT NULL UNIQUE, "id" INTEGER NOT NULL UNIQUE,
"title" VARCHAR NOT NULL, "title" VARCHAR NOT NULL,
PRIMARY KEY("id") PRIMARY KEY("id")
); );
CREATE TABLE IF NOT EXISTS "time_entry" ( CREATE TABLE IF NOT EXISTS "time_entry" (
"id" INTEGER PRIMARY KEY, "id" INTEGER,
"date" DATETIME NOT NULL, "author_id" INTEGER NOT NULL,
"description" VARCHAR NOT NULL, "description" VARCHAR NOT NULL,
"date" DATETIME NOT NULL,
"activity_id" VARCHAR NOT NULL, "activity_id" VARCHAR NOT NULL,
"time_spent" NUMERIC(3, 3) NOT NULL, "time_spent" NUMERIC NOT NULL,
"task_id" INTEGER NOT NULL, "task_id" INTEGER NOT NULL,
"activity_type_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, PRIMARY KEY("id"),
FOREIGN KEY ("activity_type_id") REFERENCES "activity_types"("id") ON UPDATE NO ACTION ON DELETE NO ACTION 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 is_updated = True
for act in activities: for act in activities:
if is_updated: if is_updated:
tags = self.cursor.execute("SELECT * FROM tags WHERE id = ?", (act.tag_id, )).fetchall() tags = self.cursor.execute("SELECT * FROM tag WHERE id = ?", (act.tag_id, )).fetchall()
vetro_projects = self.cursor.execute("SELECT * FROM vetro_projects WHERE id = ?", (act.vetro_project_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: if not tags or not vetro_projects:
is_updated = False is_updated = False
try: try:
self.cursor.execute( self.cursor.execute(
"INSERT INTO activities VALUES(?,?,?,?,?,?,?,?,?)", "INSERT INTO activity VALUES(?,?,?,?,?,?,?,?,?,?)",
( (
act.id, act.id,
act.description, act.description,
act.author_id,
act.time_spent, act.time_spent,
act.date_start, act.date_start,
act.date_end, act.date_end,
@@ -55,7 +56,7 @@ class Database:
) )
except sq.IntegrityError: except sq.IntegrityError:
self.cursor.execute( self.cursor.execute(
"""UPDATE activities """UPDATE activity
SET description = ?, time_spent = ?, date_start = ?, date_end = ?, is_tracked = ?, vetro_project_id = ?, tag_id = ?, project_id = ? SET description = ?, time_spent = ?, date_start = ?, date_end = ?, is_tracked = ?, vetro_project_id = ?, tag_id = ?, project_id = ?
WHERE id = ?""", WHERE id = ?""",
( (
@@ -81,15 +82,16 @@ class Database:
try: try:
self.cursor.execute( self.cursor.execute(
"""INSERT INTO time_entry """INSERT INTO time_entry
(date, description, activity_id, time_spent, task_id, activity_type_id) (date, description, activity_id, time_spent, task_id, activity_type_id, author_id)
VALUES(?,?,?,?,?,?)""", VALUES(?,?,?,?,?,?,?)""",
( (
act.date_start.date(), act.date_start.date(),
act.description, act.description,
act.id, act.id,
act.time_spent, act.time_spent,
act.task_id, act.task_id,
ActivityTypes.DEVELOPMENT ActivityTypes.DEVELOPMENT,
act.author_id
) )
) )
self.con.commit() self.con.commit()
@@ -101,10 +103,10 @@ class Database:
"""Сохраняет в базу данных теги Clockify""" """Сохраняет в базу данных теги Clockify"""
for tag in tags: for tag in tags:
try: 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: except sq.IntegrityError:
self.cursor.execute( self.cursor.execute(
"""UPDATE tags """UPDATE tag
SET title = ? SET title = ?
WHERE id = ?""", WHERE id = ?""",
(tag.title, tag.id) (tag.title, tag.id)
@@ -117,10 +119,10 @@ class Database:
"""Сохраняет в базу данных Проекты (ДО) Ветро Clockify""" """Сохраняет в базу данных Проекты (ДО) Ветро Clockify"""
for vetro_project in vetro_projects: for vetro_project in vetro_projects:
try: 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: except sq.IntegrityError:
self.cursor.execute(""" self.cursor.execute("""
UPDATE vetro_projects UPDATE vetro_project
SET title = ? SET title = ?
WHERE id = ?""", WHERE id = ?""",
(vetro_project.title, vetro_project.id) (vetro_project.title, vetro_project.id)
@@ -132,13 +134,63 @@ class Database:
"""Сохраняет в базу данных Деятельности Redmine""" """Сохраняет в базу данных Деятельности Redmine"""
for activity_type in activity_types: for activity_type in activity_types:
try: 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: except sq.IntegrityError:
self.cursor.execute(""" self.cursor.execute("""
UPDATE activity_types UPDATE activity_type
SET title = ? SET title = ?
WHERE id = ?""", WHERE id = ?""",
(activity_type.title, activity_type.id) (activity_type.title, activity_type.id)
) )
finally: finally:
self.con.commit() 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}') logger.info(f'{Fore.YELLOW}Проверьте файл "{config.ENV_TXT}". Если необходимых переменных в нём нет, то просто удалите его.{Fore.RESET}')
return return
red = RedmineAPI(config.REDMINE_TOKEN) 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: if args.init_db:
logger.info(Fore.YELLOW + "Обновление базы данных..." + Fore.RESET) logger.info(Fore.YELLOW + "Обновление базы данных..." + Fore.RESET)
try: try:
@@ -74,13 +88,13 @@ def main():
else: else:
logger.info(Fore.GREEN + "База данных теперь актуальна." + Fore.RESET) logger.info(Fore.GREEN + "База данных теперь актуальна." + Fore.RESET)
activities = clock.get_activities( activities = sorted(clock.get_activities(
**{ **{
"start": format_date(start), "start": format_date(start),
"end": format_date(end), "end": format_date(end),
"page-size": args.page_size, "page-size": args.page_size,
}, },
) ), key=lambda x: x.date_start)
db.insert_activities(activities) db.insert_activities(activities)
if args.debug: if args.debug:
logger.debug("Пропускаем этап загрузки трудочасов") logger.debug("Пропускаем этап загрузки трудочасов")