Files
wish-board/server/main.py
T

334 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Optional
import sqlite3
import uuid
from datetime import datetime
import os
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
DB_PATH = "wish_board.db"
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_db()
conn.execute("""
CREATE TABLE IF NOT EXISTS boards (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
color TEXT DEFAULT '#6366f1',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS lists (
id TEXT PRIMARY KEY,
board_id TEXT NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
title TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS cards (
id TEXT PRIMARY KEY,
list_id TEXT NOT NULL REFERENCES lists(id) ON DELETE CASCADE,
board_id TEXT NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT DEFAULT '',
position INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (list_id) REFERENCES lists(id) ON DELETE CASCADE,
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE
)
""")
conn.commit()
conn.close()
init_db()
class BoardCreate(BaseModel):
name: str
color: str = "#6366f1"
class ListCreate(BaseModel):
title: str
class CardCreate(BaseModel):
title: str
description: str = ""
class BoardUpdate(BaseModel):
name: Optional[str] = None
color: Optional[str] = None
class ListUpdate(BaseModel):
title: Optional[str] = None
position: Optional[int] = None
class CardUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
position: Optional[int] = None
list_id: Optional[str] = None
# Boards
@app.get("/api/boards")
def get_boards():
conn = get_db()
boards = conn.execute("SELECT * FROM boards ORDER BY created_at DESC").fetchall()
conn.close()
return [dict(b) for b in boards]
@app.post("/api/boards")
def create_board(data: BoardCreate):
board_id = str(uuid.uuid4())
conn = get_db()
conn.execute("INSERT INTO boards (id, name, color) VALUES (?, ?, ?)",
(board_id, data.name, data.color))
conn.commit()
conn.close()
return {"id": board_id, "name": data.name, "color": data.color}
@app.delete("/api/boards/{board_id}")
def delete_board(board_id: str):
conn = get_db()
conn.execute("DELETE FROM boards WHERE id = ?", (board_id,))
conn.commit()
conn.close()
return {"ok": True}
@app.patch("/api/boards/{board_id}")
def update_board(board_id: str, data: BoardUpdate):
conn = get_db()
updates = []
params = []
if data.name is not None:
updates.append("name = ?")
params.append(data.name)
if data.color is not None:
updates.append("color = ?")
params.append(data.color)
if updates:
params.append(board_id)
conn.execute(f"UPDATE boards SET {', '.join(updates)} WHERE id = ?", params)
conn.commit()
conn.close()
return {"ok": True}
# Lists
@app.get("/api/boards/{board_id}/lists")
def get_lists(board_id: str):
conn = get_db()
lists = conn.execute("SELECT * FROM lists WHERE board_id = ? ORDER BY position", (board_id,)).fetchall()
conn.close()
return [dict(l) for l in lists]
@app.post("/api/boards/{board_id}/lists")
def create_list(board_id: str, data: ListCreate):
list_id = str(uuid.uuid4())
conn = get_db()
max_pos = conn.execute("SELECT COALESCE(MAX(position), -1) as max FROM lists WHERE board_id = ?", (board_id,)).fetchone()["max"]
conn.execute("INSERT INTO lists (id, board_id, title, position) VALUES (?, ?, ?, ?)",
(list_id, board_id, data.title, max_pos + 1))
conn.commit()
conn.close()
return {"id": list_id, "board_id": board_id, "title": data.title, "position": max_pos + 1}
@app.patch("/api/lists/{list_id}")
def update_list(list_id: str, data: ListUpdate):
conn = get_db()
updates = []
params = []
if data.title is not None:
updates.append("title = ?")
params.append(data.title)
if data.position is not None:
updates.append("position = ?")
params.append(data.position)
if updates:
params.append(list_id)
conn.execute(f"UPDATE lists SET {', '.join(updates)} WHERE id = ?", params)
conn.commit()
conn.close()
return {"ok": True}
@app.delete("/api/lists/{list_id}")
def delete_list(list_id: str):
conn = get_db()
conn.execute("DELETE FROM lists WHERE id = ?", (list_id,))
conn.commit()
conn.close()
return {"ok": True}
# Cards
@app.get("/api/lists/{list_id}/cards")
def get_cards(list_id: str):
conn = get_db()
cards = conn.execute("SELECT * FROM cards WHERE list_id = ? ORDER BY position", (list_id,)).fetchall()
conn.close()
return [dict(c) for c in cards]
@app.post("/api/lists/{list_id}/cards")
def create_card(list_id: str, data: CardCreate):
card_id = str(uuid.uuid4())
conn = get_db()
list_row = conn.execute("SELECT board_id FROM lists WHERE id = ?", (list_id,)).fetchone()
board_id = list_row["board_id"]
max_pos = conn.execute("SELECT COALESCE(MAX(position), -1) as max FROM cards WHERE list_id = ?", (list_id,)).fetchone()["max"]
conn.execute("INSERT INTO cards (id, list_id, board_id, title, description, position) VALUES (?, ?, ?, ?, ?, ?)",
(card_id, list_id, board_id, data.title, data.description, max_pos + 1))
conn.commit()
conn.close()
return {"id": card_id, "list_id": list_id, "board_id": board_id, "title": data.title, "description": data.description, "position": max_pos + 1}
@app.patch("/api/cards/{card_id}")
def update_card(card_id: str, data: CardUpdate):
conn = get_db()
updates = []
params = []
if data.title is not None:
updates.append("title = ?")
params.append(data.title)
if data.description is not None:
updates.append("description = ?")
params.append(data.description)
if data.position is not None:
updates.append("position = ?")
params.append(data.position)
if data.list_id is not None:
updates.append("list_id = ?")
params.append(data.list_id)
if updates:
params.append(card_id)
conn.execute(f"UPDATE cards SET {', '.join(updates)} WHERE id = ?", params)
conn.commit()
conn.close()
return {"ok": True}
@app.delete("/api/cards/{card_id}")
def delete_card(card_id: str):
conn = get_db()
conn.execute("DELETE FROM cards WHERE id = ?", (card_id,))
conn.commit()
conn.close()
return {"ok": True}
# Serve frontend
@app.get("/")
def serve_index():
return FileResponse("static/index.html")
@app.get("/api/generate-cards")
def generate_random_cards(count: int = 5):
"""Generate random cards from predefined list."""
CARD_OPTIONS = [
"Изучить React и TypeScript",
"Написать мобильное приложение",
"Запустить свой SaaS-продукт",
"Собрать первый миллион на крипто",
"Путешествие в Японию",
"Купить дом в деревне",
"Научиться играть на гитаре",
"Прочитать 50 книг за год",
"Запустить YouTube канал",
"Создать AI-бота для бизнеса",
"Собрать свой первый ПК",
"Выучить английский на C1",
"Написать книгу",
"Открыть кофейню",
"Сделать татуировку",
"Прыгнуть с парашютом",
"Пройти марафон",
"Выучить Python с нуля",
"Собрать сервер для дома",
"Запустить Telegram-бот",
"Создать свой дизайн-систему",
"Написать игру на Unity",
"Собрать умный дом",
"Выучить три языка",
"Построить баню",
"Купить машину мечты",
"Собрать коллекцию винила",
"Пройти курс по UX/UI",
"Создать подкаст",
"Написать приложение для заметок",
"Собрать свой первый drone",
"Выучить JavaScript",
"Создать свой сайт-портфолио",
"Научиться фотографировать",
"Собрать свой первый сервер",
"Запустить свой блог",
"Создать свой мессенджер",
"Выучить Rust",
"Собрать свой первый VR-шлем",
"Написать свой фреймворк",
"Создать свою операционную систему",
"Выучить машинное обучение",
"Собрать свой первый робот",
"Написать свой компилятор",
"Создать свою базу данных",
"Выучить ассемблер",
"Собрать свой первый суперкомпьютер",
"Написать свой процессор",
"Создать свою криптовалюту",
"Выучить квантовые вычисления",
"Собрать свой первый квантовый компьютер",
"Написать свой AI-модель",
"Создать свою нейросеть",
"Выучить нейронные сети",
"Собрать свой первый GPU-кластер",
"Написать свой GPU-драйвер",
"Создать свою операционную систему для GPU",
"Выучить FPGA-программирование",
"Собрать свой первый FPGA-ускоритель",
"Написать свой ASIC",
"Создать свою квантовую программу",
"Выучить квантовую физику",
"Собрать свой первый квантовый датчик",
"Написать свой квантовый алгоритм",
"Создать свою квантовую сеть",
"Выучить квантовую криптографию",
"Собрать свой первый квантовый ретранслятор",
"Написать свой квантовый протокол",
"Создать свою квантовую облачную платформу",
"Выучить квантовое машинное обучение",
"Собрать свой первый квантовый симулятор",
"Написать свой квантовый компилятор",
"Создать свою квантовую IDE",
"Выучить квантовое программирование",
"Собрать свой первый квантовый отладчик",
"Написать свой квантовый тестер",
"Создать свою квантовую документацию",
"Выучить квантовую математику",
"Собрать свой первый квантовый калькулятор",
"Написать свой квантовый solver",
"Создать свою квантовую оптимизацию",
"Выучить квантовую статистику",
"Собрать свой первый квантовый генератор случайных чисел",
"Написать свой квантовый шифр",
"Создать свою квантовую стеганографию",
"Выучить квантовую теорию игр",
"Собрать свой первый квантовый аукцион",
"Написать свой квантовый консенсус",
"Создать свою квантовую блокчейн",
]
import random
selected = random.sample(CARD_OPTIONS, min(count, len(CARD_OPTIONS)))
return {
"cards": selected,
"total": len(CARD_OPTIONS)
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=4000)