Initial commit: memo app with FastAPI, SQLite, APScheduler, Discord notifications

This commit is contained in:
2026-04-30 14:03:11 +02:00
commit 748ee8ae75
11 changed files with 727 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from typing import Optional
import os
from .database import init_db, get_db
from . import scheduler as sched
app = FastAPI(title="Memo")
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
@app.on_event("startup")
def startup():
init_db()
sched.start()
@app.on_event("shutdown")
def shutdown():
sched.stop()
@app.get("/", response_class=HTMLResponse)
def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
# --- Memo models ---
class MemoCreate(BaseModel):
title: str
description: Optional[str] = ""
reminder_time: str = "09:00"
class MemoUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
reminder_time: Optional[str] = None
# --- Memo routes ---
@app.get("/api/memos")
def list_memos(done: Optional[str] = None):
with get_db() as conn:
if done == "true":
rows = conn.execute("SELECT * FROM memos WHERE is_done = 1 ORDER BY done_at DESC").fetchall()
elif done == "false":
rows = conn.execute("SELECT * FROM memos WHERE is_done = 0 ORDER BY reminder_time, created_at").fetchall()
else:
rows = conn.execute("SELECT * FROM memos ORDER BY is_done, reminder_time, created_at").fetchall()
return [dict(r) for r in rows]
@app.post("/api/memos", status_code=201)
def create_memo(memo: MemoCreate):
with get_db() as conn:
cur = conn.execute(
"INSERT INTO memos (title, description, reminder_time) VALUES (?, ?, ?)",
(memo.title.strip(), memo.description.strip(), memo.reminder_time)
)
row = conn.execute("SELECT * FROM memos WHERE id = ?", (cur.lastrowid,)).fetchone()
return dict(row)
@app.put("/api/memos/{memo_id}")
def update_memo(memo_id: int, memo: MemoUpdate):
with get_db() as conn:
row = conn.execute("SELECT * FROM memos WHERE id = ?", (memo_id,)).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Memo introuvable")
fields, values = [], []
if memo.title is not None:
fields.append("title = ?"); values.append(memo.title.strip())
if memo.description is not None:
fields.append("description = ?"); values.append(memo.description.strip())
if memo.reminder_time is not None:
fields.append("reminder_time = ?"); values.append(memo.reminder_time)
if not fields:
return dict(row)
values.append(memo_id)
conn.execute(f"UPDATE memos SET {', '.join(fields)} WHERE id = ?", values)
return dict(conn.execute("SELECT * FROM memos WHERE id = ?", (memo_id,)).fetchone())
@app.post("/api/memos/{memo_id}/done")
def mark_done(memo_id: int):
with get_db() as conn:
row = conn.execute("SELECT * FROM memos WHERE id = ?", (memo_id,)).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Memo introuvable")
conn.execute(
"UPDATE memos SET is_done = 1, done_at = datetime('now', 'localtime') WHERE id = ?",
(memo_id,)
)
return dict(conn.execute("SELECT * FROM memos WHERE id = ?", (memo_id,)).fetchone())
@app.post("/api/memos/{memo_id}/undone")
def mark_undone(memo_id: int):
with get_db() as conn:
row = conn.execute("SELECT * FROM memos WHERE id = ?", (memo_id,)).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Memo introuvable")
conn.execute("UPDATE memos SET is_done = 0, done_at = NULL WHERE id = ?", (memo_id,))
return dict(conn.execute("SELECT * FROM memos WHERE id = ?", (memo_id,)).fetchone())
@app.delete("/api/memos/{memo_id}", status_code=204)
def delete_memo(memo_id: int):
with get_db() as conn:
conn.execute("DELETE FROM memos WHERE id = ?", (memo_id,))
# --- Settings ---
class SettingsUpdate(BaseModel):
discord_webhook: Optional[str] = None
@app.get("/api/settings")
def get_settings():
with get_db() as conn:
rows = conn.execute("SELECT key, value FROM settings").fetchall()
return {r["key"]: r["value"] for r in rows}
@app.post("/api/settings")
def update_settings(data: SettingsUpdate):
with get_db() as conn:
if data.discord_webhook is not None:
conn.execute("UPDATE settings SET value = ? WHERE key = 'discord_webhook'", (data.discord_webhook,))
return {"ok": True}