Muhammad-Mehdi commited on
Commit
eb03313
·
1 Parent(s): c0b9f7d

new DB fixed

Browse files
.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-dev.txt
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-dev.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
 
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
- "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
- "module": "ESNext",
6
- "moduleResolution": "bundler",
7
- "jsx": "react-jsx",
8
- "strict": true,
9
- "noUnusedLocals": true,
10
- "noUnusedParameters": true,
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
- // Public origin for sitemap.xml and robots.txt. Set SITE_URL in the deploy environment.
7
- const SITE_URL = process.env.SITE_URL ?? "https://notestack.ai";
8
 
9
- export default defineConfig({
10
- plugins: [
11
- react(),
12
- sitemapPlugin(SITE_URL),
13
- // Static HTML per public route for crawlers that do not run JavaScript. Must run after site-url fills index.html.
14
- prerenderPlugin(SITE_URL),
15
- // Link preview tags need absolute URLs: fill %SITE_URL% in index.html.
16
- { name: "site-url", transformIndexHtml: (html) => html.replaceAll("%SITE_URL%", SITE_URL.replace(/\/$/, "")) },
17
- ],
18
- // Read the shared root .env. Only these prefixes reach the browser; the Google client ID is
19
- // public by design, so it can be shared with the backend under its plain name.
20
- envDir: "..",
21
- envPrefix: ["VITE_", "GOOGLE_CLIENT_ID"],
22
- server: {
23
- port: 5173,
24
- proxy: { "/api": process.env.VITE_API_PROXY ?? "http://localhost:8000" },
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
  });