Spaces:
Runtime error
Runtime error
Commit ·
eb03313
1
Parent(s): c0b9f7d
new DB fixed
Browse files- .github/workflows/ci.yml +1 -1
- README.md +1 -1
- backend/app/config.py +13 -0
- backend/app/main.py +12 -0
- backend/requirements-dev.txt +0 -3
- backend/requirements.txt +4 -0
- docker-compose.yml +1 -0
- frontend/package-lock.json +18 -0
- frontend/package.json +1 -0
- frontend/tsconfig.app.json +26 -0
- frontend/tsconfig.json +9 -15
- frontend/tsconfig.node.json +18 -0
- frontend/vite.config.ts +32 -20
.github/workflows/ci.yml
CHANGED
|
@@ -17,7 +17,7 @@ jobs:
|
|
| 17 |
- uses: actions/checkout@v4
|
| 18 |
- uses: actions/setup-python@v5
|
| 19 |
with: { python-version: "3.12", cache: pip }
|
| 20 |
-
- run: pip install -r requirements
|
| 21 |
- run: ruff check app tests
|
| 22 |
- run: pytest -q
|
| 23 |
|
|
|
|
| 17 |
- uses: actions/checkout@v4
|
| 18 |
- uses: actions/setup-python@v5
|
| 19 |
with: { python-version: "3.12", cache: pip }
|
| 20 |
+
- run: pip install -r requirements.txt
|
| 21 |
- run: ruff check app tests
|
| 22 |
- run: pytest -q
|
| 23 |
|
README.md
CHANGED
|
@@ -71,7 +71,7 @@ docker compose up --build
|
|
| 71 |
Without Docker (one API process does everything):
|
| 72 |
|
| 73 |
```bash
|
| 74 |
-
cd backend && python -m venv .venv && .venv/Scripts/pip install -r requirements
|
| 75 |
alembic upgrade head && uvicorn app.main:app --reload # SQLite at backend/notestack.db
|
| 76 |
cd frontend && npm install && npm run dev
|
| 77 |
cd renderer && npm install && npm start # only needed for videos, quote cards, carousels
|
|
|
|
| 71 |
Without Docker (one API process does everything):
|
| 72 |
|
| 73 |
```bash
|
| 74 |
+
cd backend && python -m venv .venv && .venv/Scripts/pip install -r requirements.txt
|
| 75 |
alembic upgrade head && uvicorn app.main:app --reload # SQLite at backend/notestack.db
|
| 76 |
cd frontend && npm install && npm run dev
|
| 77 |
cd renderer && npm install && npm start # only needed for videos, quote cards, carousels
|
backend/app/config.py
CHANGED
|
@@ -20,6 +20,7 @@ class Settings(BaseSettings):
|
|
| 20 |
|
| 21 |
# Local default is a SQLite file (relative to the working directory, normally backend/).
|
| 22 |
# Prod and docker-compose use Postgres: postgresql+psycopg://user:pass@host:5432/db
|
|
|
|
| 23 |
database_url: str = "sqlite:///./notestack.db"
|
| 24 |
|
| 25 |
# Worker (Postgres backed queue, see app/worker.py)
|
|
@@ -128,6 +129,18 @@ class Settings(BaseSettings):
|
|
| 128 |
"""RUN_WORKER_IN_API= (blank, as in .env.example) means "decide from ENV", not a parse error."""
|
| 129 |
return None if isinstance(value, str) and not value.strip() else value
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
@property
|
| 132 |
def worker_in_api(self) -> bool:
|
| 133 |
return self.env == "development" if self.run_worker_in_api is None else self.run_worker_in_api
|
|
|
|
| 20 |
|
| 21 |
# Local default is a SQLite file (relative to the working directory, normally backend/).
|
| 22 |
# Prod and docker-compose use Postgres: postgresql+psycopg://user:pass@host:5432/db
|
| 23 |
+
# (plain postgres:// and postgresql:// URLs are rewritten to use psycopg, see below)
|
| 24 |
database_url: str = "sqlite:///./notestack.db"
|
| 25 |
|
| 26 |
# Worker (Postgres backed queue, see app/worker.py)
|
|
|
|
| 129 |
"""RUN_WORKER_IN_API= (blank, as in .env.example) means "decide from ENV", not a parse error."""
|
| 130 |
return None if isinstance(value, str) and not value.strip() else value
|
| 131 |
|
| 132 |
+
@field_validator("database_url", mode="before")
|
| 133 |
+
@classmethod
|
| 134 |
+
def _use_psycopg_driver(cls, value):
|
| 135 |
+
"""Accept plain postgres:// or postgresql:// URLs (as Neon, Heroku etc. hand out) and
|
| 136 |
+
point them at psycopg 3, the only Postgres driver installed. Query params pass through."""
|
| 137 |
+
if isinstance(value, str):
|
| 138 |
+
value = value.strip()
|
| 139 |
+
for prefix in ("postgres://", "postgresql://"):
|
| 140 |
+
if value.startswith(prefix):
|
| 141 |
+
return "postgresql+psycopg://" + value[len(prefix):]
|
| 142 |
+
return value
|
| 143 |
+
|
| 144 |
@property
|
| 145 |
def worker_in_api(self) -> bool:
|
| 146 |
return self.env == "development" if self.run_worker_in_api is None else self.run_worker_in_api
|
backend/app/main.py
CHANGED
|
@@ -29,6 +29,18 @@ from app.services.storage import storage as store
|
|
| 29 |
log = logging.getLogger("notestack")
|
| 30 |
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
@asynccontextmanager
|
| 33 |
async def lifespan(app: FastAPI):
|
| 34 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 29 |
log = logging.getLogger("notestack")
|
| 30 |
|
| 31 |
|
| 32 |
+
def _drop_color_message(record: logging.LogRecord) -> bool:
|
| 33 |
+
"""litellm (via dspy) adds a secret redaction filter to uvicorn.error that formats the message
|
| 34 |
+
and clears record.args. uvicorn's colored formatter then swaps in its color_message template
|
| 35 |
+
and prints it with no args ("Uvicorn running on %s://%s:%d"). Without the template it prints
|
| 36 |
+
the already formatted, redacted message."""
|
| 37 |
+
record.__dict__.pop("color_message", None)
|
| 38 |
+
return True
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
logging.getLogger("uvicorn.error").addFilter(_drop_color_message)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
@asynccontextmanager
|
| 45 |
async def lifespan(app: FastAPI):
|
| 46 |
logging.basicConfig(level=logging.INFO)
|
backend/requirements-dev.txt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
-r requirements.txt
|
| 2 |
-
pytest==8.*
|
| 3 |
-
ruff==0.8.*
|
|
|
|
|
|
|
|
|
|
|
|
backend/requirements.txt
CHANGED
|
@@ -18,3 +18,7 @@ feedparser==6.*
|
|
| 18 |
beautifulsoup4==4.*
|
| 19 |
pypdf==5.*
|
| 20 |
stripe==13.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
beautifulsoup4==4.*
|
| 19 |
pypdf==5.*
|
| 20 |
stripe==13.*
|
| 21 |
+
|
| 22 |
+
# Dev tools (tests, lint)
|
| 23 |
+
pytest==8.*
|
| 24 |
+
ruff==0.8.*
|
docker-compose.yml
CHANGED
|
@@ -62,6 +62,7 @@ services:
|
|
| 62 |
working_dir: /app
|
| 63 |
command: sh -c "npm install && npx vite --host 0.0.0.0"
|
| 64 |
environment:
|
|
|
|
| 65 |
VITE_API_PROXY: http://api:8000
|
| 66 |
VITE_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
| 67 |
volumes: ["./frontend:/app", "/app/node_modules"]
|
|
|
|
| 62 |
working_dir: /app
|
| 63 |
command: sh -c "npm install && npx vite --host 0.0.0.0"
|
| 64 |
environment:
|
| 65 |
+
# The browser calls /api on :5173 and Vite proxies it to the api service (in-network name).
|
| 66 |
VITE_API_PROXY: http://api:8000
|
| 67 |
VITE_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
| 68 |
volumes: ["./frontend:/app", "/app/node_modules"]
|
frontend/package-lock.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
| 13 |
"react-router-dom": "^6.28.0"
|
| 14 |
},
|
| 15 |
"devDependencies": {
|
|
|
|
| 16 |
"@types/react": "^18.3.12",
|
| 17 |
"@types/react-dom": "^18.3.1",
|
| 18 |
"@vitejs/plugin-react": "^4.3.4",
|
|
@@ -1178,6 +1179,16 @@
|
|
| 1178 |
"dev": true,
|
| 1179 |
"license": "MIT"
|
| 1180 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1181 |
"node_modules/@types/prop-types": {
|
| 1182 |
"version": "15.7.15",
|
| 1183 |
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
|
@@ -1690,6 +1701,13 @@
|
|
| 1690 |
"node": ">=14.17"
|
| 1691 |
}
|
| 1692 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1693 |
"node_modules/update-browserslist-db": {
|
| 1694 |
"version": "1.3.3",
|
| 1695 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
|
|
|
|
| 13 |
"react-router-dom": "^6.28.0"
|
| 14 |
},
|
| 15 |
"devDependencies": {
|
| 16 |
+
"@types/node": "^20.19.43",
|
| 17 |
"@types/react": "^18.3.12",
|
| 18 |
"@types/react-dom": "^18.3.1",
|
| 19 |
"@vitejs/plugin-react": "^4.3.4",
|
|
|
|
| 1179 |
"dev": true,
|
| 1180 |
"license": "MIT"
|
| 1181 |
},
|
| 1182 |
+
"node_modules/@types/node": {
|
| 1183 |
+
"version": "20.19.43",
|
| 1184 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
| 1185 |
+
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
| 1186 |
+
"dev": true,
|
| 1187 |
+
"license": "MIT",
|
| 1188 |
+
"dependencies": {
|
| 1189 |
+
"undici-types": "~6.21.0"
|
| 1190 |
+
}
|
| 1191 |
+
},
|
| 1192 |
"node_modules/@types/prop-types": {
|
| 1193 |
"version": "15.7.15",
|
| 1194 |
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
|
|
|
| 1701 |
"node": ">=14.17"
|
| 1702 |
}
|
| 1703 |
},
|
| 1704 |
+
"node_modules/undici-types": {
|
| 1705 |
+
"version": "6.21.0",
|
| 1706 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 1707 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 1708 |
+
"dev": true,
|
| 1709 |
+
"license": "MIT"
|
| 1710 |
+
},
|
| 1711 |
"node_modules/update-browserslist-db": {
|
| 1712 |
"version": "1.3.3",
|
| 1713 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
|
frontend/package.json
CHANGED
|
@@ -16,6 +16,7 @@
|
|
| 16 |
"react-router-dom": "^6.28.0"
|
| 17 |
},
|
| 18 |
"devDependencies": {
|
|
|
|
| 19 |
"@types/react": "^18.3.12",
|
| 20 |
"@types/react-dom": "^18.3.1",
|
| 21 |
"@vitejs/plugin-react": "^4.3.4",
|
|
|
|
| 16 |
"react-router-dom": "^6.28.0"
|
| 17 |
},
|
| 18 |
"devDependencies": {
|
| 19 |
+
"@types/node": "^20.19.43",
|
| 20 |
"@types/react": "^18.3.12",
|
| 21 |
"@types/react-dom": "^18.3.1",
|
| 22 |
"@vitejs/plugin-react": "^4.3.4",
|
frontend/tsconfig.app.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
| 4 |
+
"target": "ES2022",
|
| 5 |
+
"lib": [
|
| 6 |
+
"ES2022",
|
| 7 |
+
"DOM",
|
| 8 |
+
"DOM.Iterable"
|
| 9 |
+
],
|
| 10 |
+
"module": "ESNext",
|
| 11 |
+
"moduleResolution": "bundler",
|
| 12 |
+
"jsx": "react-jsx",
|
| 13 |
+
"strict": true,
|
| 14 |
+
"noUnusedLocals": true,
|
| 15 |
+
"noUnusedParameters": true,
|
| 16 |
+
"skipLibCheck": true,
|
| 17 |
+
"isolatedModules": true,
|
| 18 |
+
"noEmit": true,
|
| 19 |
+
"types": [
|
| 20 |
+
"vite/client"
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
"include": [
|
| 24 |
+
"src"
|
| 25 |
+
]
|
| 26 |
+
}
|
frontend/tsconfig.json
CHANGED
|
@@ -1,17 +1,11 @@
|
|
| 1 |
{
|
| 2 |
-
"
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
"skipLibCheck": true,
|
| 12 |
-
"isolatedModules": true,
|
| 13 |
-
"noEmit": true,
|
| 14 |
-
"types": ["vite/client"]
|
| 15 |
-
},
|
| 16 |
-
"include": ["src"]
|
| 17 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"files": [],
|
| 3 |
+
"references": [
|
| 4 |
+
{
|
| 5 |
+
"path": "./tsconfig.app.json"
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"path": "./tsconfig.node.json"
|
| 9 |
+
}
|
| 10 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
frontend/tsconfig.node.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
| 4 |
+
"target": "ES2022",
|
| 5 |
+
"lib": ["ES2023"],
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"moduleResolution": "bundler",
|
| 8 |
+
"jsx": "react-jsx",
|
| 9 |
+
"strict": true,
|
| 10 |
+
"noUnusedLocals": true,
|
| 11 |
+
"noUnusedParameters": true,
|
| 12 |
+
"skipLibCheck": true,
|
| 13 |
+
"isolatedModules": true,
|
| 14 |
+
"noEmit": true,
|
| 15 |
+
"types": ["node"]
|
| 16 |
+
},
|
| 17 |
+
"include": ["vite.config.ts", "seo"]
|
| 18 |
+
}
|
frontend/vite.config.ts
CHANGED
|
@@ -1,26 +1,38 @@
|
|
| 1 |
-
import { defineConfig } from "vite";
|
| 2 |
import react from "@vitejs/plugin-react";
|
|
|
|
| 3 |
import { prerenderPlugin } from "./seo/prerender";
|
| 4 |
import { sitemapPlugin } from "./seo/sitemap";
|
| 5 |
|
| 6 |
-
//
|
| 7 |
-
const
|
| 8 |
|
| 9 |
-
export default defineConfig({
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
});
|
|
|
|
| 1 |
+
import { defineConfig, loadEnv } from "vite";
|
| 2 |
import react from "@vitejs/plugin-react";
|
| 3 |
+
import { fileURLToPath } from "node:url";
|
| 4 |
import { prerenderPlugin } from "./seo/prerender";
|
| 5 |
import { sitemapPlugin } from "./seo/sitemap";
|
| 6 |
|
| 7 |
+
// frontend/ itself: .env, .env.local, .env.[mode] here configure the app.
|
| 8 |
+
const ENV_DIR = fileURLToPath(new URL(".", import.meta.url));
|
| 9 |
|
| 10 |
+
export default defineConfig(({ mode }) => {
|
| 11 |
+
// Config-only settings may come from frontend/.env too; real environment variables still win
|
| 12 |
+
// (docker-compose, Cloudflare Pages). Prefix "" loads everything, but none of it reaches the browser.
|
| 13 |
+
const env = loadEnv(mode, ENV_DIR, "");
|
| 14 |
+
|
| 15 |
+
// Public origin for sitemap.xml and robots.txt. Set SITE_URL in the deploy environment.
|
| 16 |
+
const SITE_URL = env.SITE_URL || "https://notestack.ai";
|
| 17 |
+
|
| 18 |
+
return {
|
| 19 |
+
plugins: [
|
| 20 |
+
react(),
|
| 21 |
+
sitemapPlugin(SITE_URL),
|
| 22 |
+
// Static HTML per public route for crawlers that do not run JavaScript. Must run after site-url fills index.html.
|
| 23 |
+
prerenderPlugin(SITE_URL),
|
| 24 |
+
// Link preview tags need absolute URLs: fill %SITE_URL% in index.html.
|
| 25 |
+
{ name: "site-url", transformIndexHtml: (html) => html.replaceAll("%SITE_URL%", SITE_URL.replace(/\/$/, "")) },
|
| 26 |
+
],
|
| 27 |
+
// Only these prefixes reach the browser; the Google client ID is public by design, so the
|
| 28 |
+
// backend's plain name is accepted as well as VITE_GOOGLE_CLIENT_ID.
|
| 29 |
+
envDir: ENV_DIR,
|
| 30 |
+
envPrefix: ["VITE_", "GOOGLE_CLIENT_ID"],
|
| 31 |
+
server: {
|
| 32 |
+
port: 5173,
|
| 33 |
+
// In dev the app calls /api on this origin (VITE_API_URL unset) and the backend is reached
|
| 34 |
+
// through this proxy: frontend/.env locally, http://api:8000 under docker-compose.
|
| 35 |
+
proxy: { "/api": env.VITE_API_PROXY || "http://localhost:8000" },
|
| 36 |
+
},
|
| 37 |
+
};
|
| 38 |
});
|