From: PhiTux Date: Sat, 28 Dec 2024 17:46:04 +0000 (+0100) Subject: writing logs is working X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=c632f20b4eacc6aa20f5ca123b55969e506774fd;p=DailyTxT.git writing logs is working --- diff --git a/backend/server/main.py b/backend/server/main.py index 2a933c9..c358ad3 100644 --- a/backend/server/main.py +++ b/backend/server/main.py @@ -1,5 +1,5 @@ from fastapi import FastAPI -from .routers import users +from .routers import users, logs from fastapi.middleware.cors import CORSMiddleware import logging from sys import stdout @@ -8,11 +8,13 @@ logger = logging.getLogger("dailytxtLogger") consoleHandler = logging.StreamHandler(stdout) consoleHandler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) logger.addHandler(consoleHandler) +logger.setLevel(logging.DEBUG) app = FastAPI() origins = [ "http://localhost:5173", + "localhost:5173", ] app.add_middleware( @@ -23,7 +25,8 @@ app.add_middleware( allow_headers=["*"], ) -app.include_router(users.router) +app.include_router(users.router, prefix="/users") +app.include_router(logs.router, prefix="/logs") @app.get("/") async def root(): diff --git a/backend/server/routers/logs.py b/backend/server/routers/logs.py new file mode 100644 index 0000000..b4e161b --- /dev/null +++ b/backend/server/routers/logs.py @@ -0,0 +1,88 @@ +import datetime +import logging +from fastapi import APIRouter, Cookie +from pydantic import BaseModel +from fastapi import Depends +from . import users +from ..utils import fileHandling +from ..utils import security + + +logger = logging.getLogger("dailytxtLogger") + +router = APIRouter() + + +class Log(BaseModel): + date: str + text: str + date_written: str + +@router.post("/saveLog") +async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)): + print(datetime.datetime.fromisoformat(log.date)) + year = datetime.datetime.fromisoformat(log.date).year + month = datetime.datetime.fromisoformat(log.date).month + day = datetime.datetime.fromisoformat(log.date).day + + content:dict = fileHandling.getDay(cookie["user_id"], year, month) + + # move old log to history + if "days" in content.keys(): + for dayLog in content["days"]: + if dayLog["day"] == day: + historyVersion = 0 + if "history" not in dayLog.keys(): + dayLog["history"] = [] + else: + for historyLog in dayLog["history"]: + if historyLog["version"] > historyVersion: + historyVersion = historyLog["version"] + historyVersion += 1 + dayLog["history"].append({"version": historyVersion, "text": dayLog["text"], "date_written": dayLog["date_written"]}) + break + + # save new log + encrypted_text = security.encrypt_text(log.text, cookie["derived_key"]) + encrypted_date_written = security.encrypt_text(log.date_written, cookie["derived_key"]) + + if "days" not in content.keys(): + content["days"] = [] + content["days"].append({"day": day, "text": encrypted_text, "date_written": encrypted_date_written}) + else: + found = False + for dayLog in content["days"]: + if dayLog["day"] == day: + dayLog["text"] = encrypted_text + dayLog["date_written"] = encrypted_date_written + found = True + break + if not found: + content["days"].append({"day": day, "text": encrypted_text, "date_written": encrypted_date_written}) + + if not fileHandling.writeDay(cookie["user_id"], year, month, content): + logger.error(f"Failed to save log for {cookie['user_id']} on {year}-{month:02d}-{day:02d}") + return {"success": False} + + return {"success": True} + + +@router.get("/getLog") +async def getLog(date: str, cookie = Depends(users.isLoggedIn)): + + year = datetime.datetime.fromisoformat(date).year + month = datetime.datetime.fromisoformat(date).month + day = datetime.datetime.fromisoformat(date).day + + content:dict = fileHandling.getDay(cookie["user_id"], year, month) + + if "days" not in content.keys(): + return {"text": "", "date_written": ""} + + for dayLog in content["days"]: + if dayLog["day"] == day: + text = security.decrypt_text(dayLog["text"], cookie["derived_key"]) + date_written = security.decrypt_text(dayLog["date_written"], cookie["derived_key"]) + return {"text": text, "date_written": date_written} + + return {"text": "", "date_written": ""} \ No newline at end of file diff --git a/backend/server/routers/users.py b/backend/server/routers/users.py index 07be90d..ef825ea 100644 --- a/backend/server/routers/users.py +++ b/backend/server/routers/users.py @@ -2,7 +2,7 @@ import asyncio import datetime import json import secrets -from fastapi import APIRouter, HTTPException, Response +from fastapi import APIRouter, Cookie, HTTPException, Response from pydantic import BaseModel from ..utils import fileHandling from ..utils import security @@ -19,8 +19,8 @@ class Login(BaseModel): username: str password: str -@router.post("/users/login") -async def login(login: Login, respose: Response): +@router.post("/login") +async def login(login: Login, response: Response): # check if user exists content:dict = fileHandling.getUsers() @@ -37,19 +37,30 @@ async def login(login: Login, respose: Response): # get intermediate key derived_key = base64.b64encode(security.derive_key_from_password(login.password, user["salt"])).decode() - # build jwt - jwt = create_jwt(user["user_id"], user["username"], derived_key) - respose.set_cookie(key="jwt", value=jwt, httponly=True) + token = create_jwt(user["user_id"], user["username"], derived_key) + response.set_cookie(key="token", value=token, httponly=True) return {"username": user["username"]} def create_jwt(user_id, username, derived_key): - return jwt.encode({"iat": datetime.datetime.now() + datetime.timedelta(days=settings.logout_after_days), "user_id": user_id, "name": username, "derived_key": derived_key}, settings.secret_token, algorithm="HS256") + return jwt.encode({"exp": datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(days=settings.logout_after_days), "user_id": user_id, "name": username, "derived_key": derived_key}, settings.secret_token, algorithm="HS256") + +def decode_jwt(token): + return jwt.decode(token, settings.secret_token, algorithms="HS256") + +def isLoggedIn(token: str = Cookie()) -> int: + try: + decoded = decode_jwt(token) + return decoded + except jwt.ExpiredSignatureError: + raise HTTPException(status_code=440, detail="Token expired") + except: + raise HTTPException(status_code=401, detail="Not logged in") -@router.get("/users/logout") +@router.get("/logout") def logout(response: Response): - response.delete_cookie("jwt") + response.delete_cookie("token", httponly=True) return {"success": True} @@ -57,7 +68,7 @@ class Register(BaseModel): username: str password: str -@router.post("/users/register") +@router.post("/register") async def register(register: Register): content:dict = fileHandling.getUsers() diff --git a/backend/server/utils/fileHandling.py b/backend/server/utils/fileHandling.py index b1b0121..fb78d71 100644 --- a/backend/server/utils/fileHandling.py +++ b/backend/server/utils/fileHandling.py @@ -23,6 +23,22 @@ def getUsers(): return {} return json.loads(s) +def getDay(user_id, year, month): + try: + f = open(os.path.join(settings.data_path, f"{user_id}/{year}/{month:02d}.json"), "r") + except FileNotFoundError: + logger.info(f"{user_id}/{year}/{month:02d}.json - File not found") + return {} + except Exception as e: + logger.exception(e) + raise HTTPException(status_code=500, detail="Internal Server Error when trying to open {year}-{month}.json") + else: + with f: + s = f.read() + if s == "": + return {} + return json.loads(s) + def writeUsers(content): # print working directory try: @@ -30,6 +46,18 @@ def writeUsers(content): except Exception as e: logger.exception(e) return e + else: + with f: + f.write(json.dumps(content, indent=4)) + return True + +def writeDay(user_id, year, month, content): + try: + os.makedirs(os.path.join(settings.data_path, f"{user_id}/{year}"), exist_ok=True) + f = open(os.path.join(settings.data_path, f"{user_id}/{year}/{month:02d}.json"), "w") + except Exception as e: + logger.exception(e) + return False else: with f: f.write(json.dumps(content, indent=4)) diff --git a/backend/server/utils/security.py b/backend/server/utils/security.py index cf5af99..69602ad 100644 --- a/backend/server/utils/security.py +++ b/backend/server/utils/security.py @@ -17,3 +17,11 @@ def create_new_enc_enc_key(password: str, salt: str) -> bytes: key = Fernet.generate_key() # actual encryption key f = Fernet(base64.urlsafe_b64encode(derived_key)) return f.encrypt(key) + +def encrypt_text(text: str, derived_key: str) -> str: + f = Fernet(base64.urlsafe_b64encode(base64.b64decode(derived_key))) + return f.encrypt(text.encode()).decode() + +def decrypt_text(text: str, derived_key: str) -> str: + f = Fernet(base64.urlsafe_b64encode(base64.b64decode(derived_key))) + return f.decrypt(text.encode()).decode() \ No newline at end of file diff --git a/backend/server/utils/settings.py b/backend/server/utils/settings.py index 1781186..b1e1fc9 100644 --- a/backend/server/utils/settings.py +++ b/backend/server/utils/settings.py @@ -3,7 +3,8 @@ from pydantic_settings import BaseSettings class Settings(BaseSettings): data_path: str = "/data" - secret_token: str = secrets.token_urlsafe(32) + development: bool = False + secret_token: str = secrets.token_urlsafe(32) logout_after_days: int = 30 settings = Settings() \ No newline at end of file diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index c63f5ee..512b8f7 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -3,17 +3,53 @@ import * as bootstrap from 'bootstrap'; import Sidenav from './Sidenav.svelte'; import { selectedDate } from '$lib/calendarStore.js'; - import dayjs from 'dayjs'; + import axios from 'axios'; + import { dev } from '$app/environment'; + import { goto } from '$app/navigation'; + + let API_URL = dev ? 'http://localhost:8000' : window.location.pathname.replace(/\/+$/, ''); + + axios.interceptors.request.use((config) => { + config.withCredentials = true; + return config; + }); + + axios.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + if ( + error.response && + error.response.status && + (error.response.status == 401 || error.response.status == 440) + ) { + // logout + axios + .get(API_URL + '/users/logout') + .then((response) => { + localStorage.removeItem('user'); + goto(`/login?error=${error.response.status}`); + }) + .catch((error) => { + console.error(error); + }); + } + return Promise.reject(error); + } + ); $effect(() => { if ($selectedDate) { - console.log('hu'); + console.log('selectedDate changed'); } }); let currentLog = $state(''); let savedLog = $state(''); + let logDateWritten = $state(''); + let timeout; function debounce(fn) { @@ -29,8 +65,40 @@ function saveLog() { // axios to backend - console.log(dayjs().format('DD.MM.YYYY, HH:mm [Uhr]')); - savedLog = currentLog; + let date_written = new Date().toLocaleString('de-DE', { + timeZone: 'Europe/Berlin', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); + + console.log(new Date($selectedDate).toISOString()); + + axios + .post(API_URL + '/logs/saveLog', { + date: new Date($selectedDate).toISOString(), + text: currentLog, + date_written: date_written + }) + .then((response) => { + if (response.data.success) { + savedLog = currentLog; + logDateWritten = date_written; + } else { + // toast + const toast = new bootstrap.Toast(document.getElementById('toastErrorSavingLog')); + toast.show(); + console.error('Log not saved'); + } + }) + .catch((error) => { + // toast + const toast = new bootstrap.Toast(document.getElementById('toastErrorSavingLog')); + toast.show(); + console.error(error.response); + }); } @@ -65,7 +133,7 @@
Geschrieben am:
- TODO + {logDateWritten}
history
delete
@@ -80,6 +148,20 @@ + +
+ +