Merge branch 'worktree-agent-a730aaff' into Develop
# Conflicts: # .planning/ROADMAP.md # .planning/STATE.md
This commit is contained in:
@@ -9,9 +9,9 @@ Requirements for this milestone. Each maps to roadmap phases.
|
|||||||
|
|
||||||
### Database Migration
|
### Database Migration
|
||||||
|
|
||||||
- [ ] **DB-01**: Application runs on PostgreSQL instead of SQLite
|
- [x] **DB-01**: Application runs on PostgreSQL instead of SQLite
|
||||||
- [ ] **DB-02**: All service functions use async database operations
|
- [ ] **DB-02**: All service functions use async database operations
|
||||||
- [ ] **DB-03**: Test infrastructure uses PGlite instead of bun:sqlite in-memory databases
|
- [x] **DB-03**: Test infrastructure uses PGlite instead of bun:sqlite in-memory databases
|
||||||
- [ ] **DB-04**: Existing SQLite data can be migrated to Postgres via a one-time script
|
- [ ] **DB-04**: Existing SQLite data can be migrated to Postgres via a one-time script
|
||||||
- [ ] **DB-05**: Docker Compose provides Postgres for local development
|
- [ ] **DB-05**: Docker Compose provides Postgres for local development
|
||||||
|
|
||||||
@@ -116,9 +116,9 @@ Which phases cover which requirements. Updated during roadmap creation.
|
|||||||
|
|
||||||
| Requirement | Phase | Status |
|
| Requirement | Phase | Status |
|
||||||
|-------------|-------|--------|
|
|-------------|-------|--------|
|
||||||
| DB-01 | Phase 14 | Pending |
|
| DB-01 | Phase 14 | Complete |
|
||||||
| DB-02 | Phase 14 | Pending |
|
| DB-02 | Phase 14 | Pending |
|
||||||
| DB-03 | Phase 14 | Pending |
|
| DB-03 | Phase 14 | Complete |
|
||||||
| DB-04 | Phase 14 | Pending |
|
| DB-04 | Phase 14 | Pending |
|
||||||
| DB-05 | Phase 14 | Pending |
|
| DB-05 | Phase 14 | Pending |
|
||||||
| AUTH-01 | Phase 15 | Pending |
|
| AUTH-01 | Phase 15 | Pending |
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ Plans:
|
|||||||
4. Docker Compose brings up Postgres alongside the app with a single command for local development
|
4. Docker Compose brings up Postgres alongside the app with a single command for local development
|
||||||
**Plans:** 6 plans
|
**Plans:** 6 plans
|
||||||
Plans:
|
Plans:
|
||||||
- [ ] 14-01-PLAN.md — Schema rewrite (pg-core), DB config (postgres.js), test helper (PGlite), Drizzle config, initial migration
|
- [x] 14-01-PLAN.md — Schema rewrite (pg-core), DB config (postgres.js), test helper (PGlite), Drizzle config, initial migration
|
||||||
- [ ] 14-02-PLAN.md — Docker Compose dev/prod + Dockerfile update for PostgreSQL
|
- [ ] 14-02-PLAN.md — Docker Compose dev/prod + Dockerfile update for PostgreSQL
|
||||||
- [ ] 14-03-PLAN.md — All 9 services async conversion + seed + server index
|
- [ ] 14-03-PLAN.md — All 9 services async conversion + seed + server index
|
||||||
- [ ] 14-04-PLAN.md — All 9 route handlers async conversion
|
- [ ] 14-04-PLAN.md — All 9 route handlers async conversion
|
||||||
@@ -195,7 +195,7 @@ Plans:
|
|||||||
| 11. Candidate Ranking | v1.3 | 2/2 | Complete | 2026-03-16 |
|
| 11. Candidate Ranking | v1.3 | 2/2 | Complete | 2026-03-16 |
|
||||||
| 12. Comparison View | v1.3 | 1/1 | Complete | 2026-03-17 |
|
| 12. Comparison View | v1.3 | 1/1 | Complete | 2026-03-17 |
|
||||||
| 13. Setup Impact Preview | v1.3 | 0/2 | Not started | - |
|
| 13. Setup Impact Preview | v1.3 | 0/2 | Not started | - |
|
||||||
| 14. PostgreSQL Migration | v2.0 | 0/6 | Not started | - |
|
| 14. PostgreSQL Migration | v2.0 | 1/6 | In progress | - |
|
||||||
| 15. External Authentication | v2.0 | 0/? | Not started | - |
|
| 15. External Authentication | v2.0 | 0/? | Not started | - |
|
||||||
| 16. Multi-User Data Model | v2.0 | 0/? | Not started | - |
|
| 16. Multi-User Data Model | v2.0 | 0/? | Not started | - |
|
||||||
| 17. Object Storage | v2.0 | 0/? | Not started | - |
|
| 17. Object Storage | v2.0 | 0/? | Not started | - |
|
||||||
|
|||||||
@@ -26,19 +26,19 @@ See: .planning/PROJECT.md (updated 2026-04-03)
|
|||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 14 of 18 (PostgreSQL Migration)
|
Phase: 14 of 18 (PostgreSQL Migration)
|
||||||
Plan: 0 of ? in current phase
|
Plan: 1 of 6 in current phase
|
||||||
Status: Ready to plan
|
Status: Executing
|
||||||
Last activity: 2026-04-03 — v2.0 roadmap created (Phases 14-18)
|
Last activity: 2026-04-04 — Completed 14-01 (Database Foundation)
|
||||||
|
|
||||||
Progress: [----------] 0% (v2.0 milestone)
|
Progress: [=---------] 3% (v2.0 milestone)
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
**Velocity:**
|
**Velocity:**
|
||||||
|
|
||||||
- Total plans completed: 0 (v2.0 milestone)
|
- Total plans completed: 1 (v2.0 milestone)
|
||||||
- Average duration: --
|
- Average duration: 3min
|
||||||
- Total execution time: --
|
- Total execution time: 3min
|
||||||
|
|
||||||
*Updated after each plan completion*
|
*Updated after each plan completion*
|
||||||
|
|
||||||
@@ -55,6 +55,12 @@ Key decisions made during v2.0 planning:
|
|||||||
- Separate globalItems table — not a flag on user items table
|
- Separate globalItems table — not a flag on user items table
|
||||||
- Single-user SQLite mode diverges at v2.0 boundary
|
- Single-user SQLite mode diverges at v2.0 boundary
|
||||||
|
|
||||||
|
Key decisions made during Phase 14 execution:
|
||||||
|
|
||||||
|
- postgres.js driver (not node-postgres) for PostgreSQL connection
|
||||||
|
- PGlite for in-memory test databases replacing bun:sqlite
|
||||||
|
- Separate drizzle-pg/ migration directory from old drizzle/
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
None active.
|
None active.
|
||||||
@@ -66,6 +72,6 @@ None active.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-04T10:12:41.532Z
|
Last session: 2026-04-04T10:19:11Z
|
||||||
Stopped at: Phase 14 plans created (6 plans, 3 waves)
|
Stopped at: Completed 14-01-PLAN.md (Database Foundation)
|
||||||
Resume file: .planning/phases/14-postgresql-migration/14-01-PLAN.md
|
Resume file: .planning/phases/14-postgresql-migration/14-02-PLAN.md
|
||||||
|
|||||||
120
.planning/phases/14-postgresql-migration/14-01-SUMMARY.md
Normal file
120
.planning/phases/14-postgresql-migration/14-01-SUMMARY.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
phase: 14-postgresql-migration
|
||||||
|
plan: 01
|
||||||
|
subsystem: database
|
||||||
|
tags: [postgresql, drizzle-orm, pglite, postgres-js, migration]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 13-setup-impact-preview
|
||||||
|
provides: "Complete SQLite-based application"
|
||||||
|
provides:
|
||||||
|
- "PostgreSQL schema definitions (13 tables via pg-core)"
|
||||||
|
- "postgres.js database connection with DATABASE_URL"
|
||||||
|
- "PGlite-based async test helper"
|
||||||
|
- "Initial PostgreSQL migration (drizzle-pg/)"
|
||||||
|
- "Async seed function"
|
||||||
|
affects: [14-02, 14-03, 14-04, 14-05, 14-06]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: [postgres (postgres.js driver), "@electric-sql/pglite"]
|
||||||
|
patterns: ["pgTable schema definitions", "async createTestDb() with PGlite", "DATABASE_URL environment variable for connection"]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: ["drizzle-pg/0000_fuzzy_shiva.sql"]
|
||||||
|
modified: ["src/db/schema.ts", "src/db/index.ts", "src/db/migrate.ts", "src/db/seed.ts", "drizzle.config.ts", "tests/helpers/db.ts", "package.json", "biome.json"]
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Used postgres.js (not pg/node-postgres) as PostgreSQL driver for Drizzle ORM"
|
||||||
|
- "PGlite for in-memory test databases replacing bun:sqlite :memory:"
|
||||||
|
- "Migration output directory drizzle-pg/ separate from old drizzle/ directory"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "All schema tables use pgTable with serial primary keys (except settings/sessions with text PKs)"
|
||||||
|
- "Timestamps use native timestamp type with defaultNow() instead of integer mode:timestamp"
|
||||||
|
- "Test databases created via async createTestDb() returning PGlite-backed Drizzle instance"
|
||||||
|
|
||||||
|
requirements-completed: [DB-01, DB-03]
|
||||||
|
|
||||||
|
duration: 3min
|
||||||
|
completed: 2026-04-04
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 14 Plan 01: Database Foundation Summary
|
||||||
|
|
||||||
|
**PostgreSQL schema with 13 pgTable definitions, postgres.js connection, PGlite test infrastructure, and initial migration**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 3 min
|
||||||
|
- **Started:** 2026-04-04T10:15:43Z
|
||||||
|
- **Completed:** 2026-04-04T10:19:11Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 10
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
- Rewrote all 13 table definitions from sqliteTable to pgTable with correct type mappings (serial, timestamp, doublePrecision, boolean)
|
||||||
|
- Established postgres.js connection with DATABASE_URL environment variable
|
||||||
|
- Created async PGlite test helper that applies migrations and seeds in-memory
|
||||||
|
- Generated initial PostgreSQL migration with 13 CREATE TABLE statements
|
||||||
|
- Zero SQLite references remain in database layer files
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Install dependencies and rewrite schema + DB config files** - `3724cf8` (feat)
|
||||||
|
2. **Task 2: Rewrite test helper and generate initial PostgreSQL migration** - `3bf1fd7` (feat)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
- `src/db/schema.ts` - 13 PostgreSQL table definitions using drizzle-orm/pg-core
|
||||||
|
- `src/db/index.ts` - postgres.js connection with DATABASE_URL
|
||||||
|
- `src/db/migrate.ts` - postgres-js migrator targeting drizzle-pg/
|
||||||
|
- `src/db/seed.ts` - Async seed function for default category
|
||||||
|
- `drizzle.config.ts` - PostgreSQL dialect config with drizzle-pg/ output
|
||||||
|
- `tests/helpers/db.ts` - Async PGlite-backed createTestDb()
|
||||||
|
- `package.json` - Added postgres, @electric-sql/pglite; removed better-sqlite3
|
||||||
|
- `biome.json` - Added drizzle-pg/ to ignore list
|
||||||
|
- `drizzle-pg/0000_fuzzy_shiva.sql` - Initial migration with 13 tables
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
- Used postgres.js driver (lightweight, ESM-native, good Drizzle integration) over node-postgres
|
||||||
|
- PGlite creates ephemeral in-memory Postgres for tests -- no external DB needed
|
||||||
|
- Separate migration directory (drizzle-pg/) to avoid conflicts with old SQLite migrations (drizzle/)
|
||||||
|
- Added drizzle-pg/ to biome ignore since it contains generated files
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 - Blocking] Added drizzle-pg/ to biome ignore list**
|
||||||
|
- **Found during:** Task 2 (migration generation)
|
||||||
|
- **Issue:** Generated drizzle-pg/ JSON snapshot failed biome formatting (2-space vs tab indent)
|
||||||
|
- **Fix:** Added "!drizzle-pg" to biome.json files.includes array (matching existing "!drizzle" pattern)
|
||||||
|
- **Files modified:** biome.json
|
||||||
|
- **Verification:** `bun run lint` passes clean
|
||||||
|
- **Committed in:** 3bf1fd7 (Task 2 commit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 1 auto-fixed (1 blocking)
|
||||||
|
**Impact on plan:** Necessary to maintain passing lint. No scope creep.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
- PGlite smoke test exits with code 99 when no explicit `process.exit(0)` is called -- this is a known PGlite cleanup behavior, not a real error. Adding explicit exit resolves it.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None - no external service configuration required.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
- Schema and test infrastructure ready for service layer conversion (Plan 14-02)
|
||||||
|
- All services can now be updated to use async Drizzle operations against PostgreSQL types
|
||||||
|
- PGlite test helper available for all test files to migrate to
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
All 7 key files verified present. Both task commits (3724cf8, 3bf1fd7) verified in git log.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 14-postgresql-migration*
|
||||||
|
*Completed: 2026-04-04*
|
||||||
@@ -7,7 +7,13 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": false,
|
"ignoreUnknown": false,
|
||||||
"includes": ["**", "!src/client/routeTree.gen.ts", "!drizzle", "!.planning"]
|
"includes": [
|
||||||
|
"**",
|
||||||
|
"!src/client/routeTree.gen.ts",
|
||||||
|
"!drizzle",
|
||||||
|
"!drizzle-pg",
|
||||||
|
"!.planning"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
8
bun.lock
8
bun.lock
@@ -5,6 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "gearbox",
|
"name": "gearbox",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@electric-sql/pglite": "^0.4.3",
|
||||||
"@hono/zod-validator": "^0.7.6",
|
"@hono/zod-validator": "^0.7.6",
|
||||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
"hono": "^4.12.8",
|
"hono": "^4.12.8",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
|
"postgres": "^3.4.8",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"recharts": "^3.8.0",
|
"recharts": "^3.8.0",
|
||||||
@@ -28,12 +30,10 @@
|
|||||||
"@tanstack/react-query-devtools": "^5.91.3",
|
"@tanstack/react-query-devtools": "^5.91.3",
|
||||||
"@tanstack/react-router-devtools": "^1.166.7",
|
"@tanstack/react-router-devtools": "^1.166.7",
|
||||||
"@tanstack/router-plugin": "^1.166.9",
|
"@tanstack/router-plugin": "^1.166.9",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"better-sqlite3": "^12.8.0",
|
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"drizzle-kit": "^0.31.9",
|
"drizzle-kit": "^0.31.9",
|
||||||
"vite": "^8.0.0",
|
"vite": "^8.0.0",
|
||||||
@@ -102,6 +102,8 @@
|
|||||||
|
|
||||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||||
|
|
||||||
|
"@electric-sql/pglite": ["@electric-sql/pglite@0.4.3", "", {}, "sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ=="],
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.9.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w=="],
|
"@emnapi/core": ["@emnapi/core@1.9.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="],
|
||||||
@@ -684,6 +686,8 @@
|
|||||||
|
|
||||||
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||||
|
|
||||||
|
"postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="],
|
||||||
|
|
||||||
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
||||||
|
|
||||||
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
||||||
|
|||||||
133
drizzle-pg/0000_fuzzy_shiva.sql
Normal file
133
drizzle-pg/0000_fuzzy_shiva.sql
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
CREATE TABLE "api_keys" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"key_hash" text NOT NULL,
|
||||||
|
"key_prefix" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "categories" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"icon" text DEFAULT 'package' NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "categories_name_unique" UNIQUE("name")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "items" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"weight_grams" double precision,
|
||||||
|
"price_cents" integer,
|
||||||
|
"category_id" integer NOT NULL,
|
||||||
|
"notes" text,
|
||||||
|
"product_url" text,
|
||||||
|
"image_filename" text,
|
||||||
|
"image_source_url" text,
|
||||||
|
"quantity" integer DEFAULT 1 NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "oauth_clients" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"client_name" text,
|
||||||
|
"redirect_uris" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "oauth_clients_client_id_unique" UNIQUE("client_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "oauth_codes" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"code" text NOT NULL,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"code_challenge" text NOT NULL,
|
||||||
|
"code_challenge_method" text DEFAULT 'S256' NOT NULL,
|
||||||
|
"redirect_uri" text NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"used" boolean DEFAULT false NOT NULL,
|
||||||
|
CONSTRAINT "oauth_codes_code_unique" UNIQUE("code")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "oauth_tokens" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"access_token_hash" text NOT NULL,
|
||||||
|
"refresh_token_hash" text NOT NULL,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"refresh_expires_at" timestamp NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "oauth_tokens_access_token_hash_unique" UNIQUE("access_token_hash"),
|
||||||
|
CONSTRAINT "oauth_tokens_refresh_token_hash_unique" UNIQUE("refresh_token_hash")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "sessions" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "settings" (
|
||||||
|
"key" text PRIMARY KEY NOT NULL,
|
||||||
|
"value" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "setup_items" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"setup_id" integer NOT NULL,
|
||||||
|
"item_id" integer NOT NULL,
|
||||||
|
"classification" text DEFAULT 'base' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "setups" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "thread_candidates" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"thread_id" integer NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"weight_grams" double precision,
|
||||||
|
"price_cents" integer,
|
||||||
|
"category_id" integer NOT NULL,
|
||||||
|
"notes" text,
|
||||||
|
"product_url" text,
|
||||||
|
"image_filename" text,
|
||||||
|
"image_source_url" text,
|
||||||
|
"status" text DEFAULT 'researching' NOT NULL,
|
||||||
|
"pros" text,
|
||||||
|
"cons" text,
|
||||||
|
"sort_order" double precision DEFAULT 0 NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "threads" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"status" text DEFAULT 'active' NOT NULL,
|
||||||
|
"resolved_candidate_id" integer,
|
||||||
|
"category_id" integer NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"username" text NOT NULL,
|
||||||
|
"password_hash" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "users_username_unique" UNIQUE("username")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "items" ADD CONSTRAINT "items_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "setup_items" ADD CONSTRAINT "setup_items_setup_id_setups_id_fk" FOREIGN KEY ("setup_id") REFERENCES "public"."setups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "setup_items" ADD CONSTRAINT "setup_items_item_id_items_id_fk" FOREIGN KEY ("item_id") REFERENCES "public"."items"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "thread_candidates" ADD CONSTRAINT "thread_candidates_thread_id_threads_id_fk" FOREIGN KEY ("thread_id") REFERENCES "public"."threads"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "thread_candidates" ADD CONSTRAINT "thread_candidates_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "threads" ADD CONSTRAINT "threads_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;
|
||||||
838
drizzle-pg/meta/0000_snapshot.json
Normal file
838
drizzle-pg/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,838 @@
|
|||||||
|
{
|
||||||
|
"id": "2b9c2a13-0ec1-4011-b34a-13c710cf6c34",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.api_keys": {
|
||||||
|
"name": "api_keys",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"key_hash": {
|
||||||
|
"name": "key_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"key_prefix": {
|
||||||
|
"name": "key_prefix",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.categories": {
|
||||||
|
"name": "categories",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"name": "icon",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'package'"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"categories_name_unique": {
|
||||||
|
"name": "categories_name_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.items": {
|
||||||
|
"name": "items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"weight_grams": {
|
||||||
|
"name": "weight_grams",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price_cents": {
|
||||||
|
"name": "price_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"name": "notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"product_url": {
|
||||||
|
"name": "product_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_filename": {
|
||||||
|
"name": "image_filename",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_source_url": {
|
||||||
|
"name": "image_source_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"name": "quantity",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"items_category_id_categories_id_fk": {
|
||||||
|
"name": "items_category_id_categories_id_fk",
|
||||||
|
"tableFrom": "items",
|
||||||
|
"tableTo": "categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.oauth_clients": {
|
||||||
|
"name": "oauth_clients",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_id": {
|
||||||
|
"name": "client_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_name": {
|
||||||
|
"name": "client_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"redirect_uris": {
|
||||||
|
"name": "redirect_uris",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"oauth_clients_client_id_unique": {
|
||||||
|
"name": "oauth_clients_client_id_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"client_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.oauth_codes": {
|
||||||
|
"name": "oauth_codes",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"name": "code",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_id": {
|
||||||
|
"name": "client_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"code_challenge": {
|
||||||
|
"name": "code_challenge",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"code_challenge_method": {
|
||||||
|
"name": "code_challenge_method",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'S256'"
|
||||||
|
},
|
||||||
|
"redirect_uri": {
|
||||||
|
"name": "redirect_uri",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"used": {
|
||||||
|
"name": "used",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"oauth_codes_code_unique": {
|
||||||
|
"name": "oauth_codes_code_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.oauth_tokens": {
|
||||||
|
"name": "oauth_tokens",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"access_token_hash": {
|
||||||
|
"name": "access_token_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"refresh_token_hash": {
|
||||||
|
"name": "refresh_token_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"client_id": {
|
||||||
|
"name": "client_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"refresh_expires_at": {
|
||||||
|
"name": "refresh_expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"oauth_tokens_access_token_hash_unique": {
|
||||||
|
"name": "oauth_tokens_access_token_hash_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"access_token_hash"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"oauth_tokens_refresh_token_hash_unique": {
|
||||||
|
"name": "oauth_tokens_refresh_token_hash_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"refresh_token_hash"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.settings": {
|
||||||
|
"name": "settings",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.setup_items": {
|
||||||
|
"name": "setup_items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"setup_id": {
|
||||||
|
"name": "setup_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"item_id": {
|
||||||
|
"name": "item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"classification": {
|
||||||
|
"name": "classification",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'base'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"setup_items_setup_id_setups_id_fk": {
|
||||||
|
"name": "setup_items_setup_id_setups_id_fk",
|
||||||
|
"tableFrom": "setup_items",
|
||||||
|
"tableTo": "setups",
|
||||||
|
"columnsFrom": [
|
||||||
|
"setup_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"setup_items_item_id_items_id_fk": {
|
||||||
|
"name": "setup_items_item_id_items_id_fk",
|
||||||
|
"tableFrom": "setup_items",
|
||||||
|
"tableTo": "items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.setups": {
|
||||||
|
"name": "setups",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.thread_candidates": {
|
||||||
|
"name": "thread_candidates",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"thread_id": {
|
||||||
|
"name": "thread_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"weight_grams": {
|
||||||
|
"name": "weight_grams",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price_cents": {
|
||||||
|
"name": "price_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"name": "notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"product_url": {
|
||||||
|
"name": "product_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_filename": {
|
||||||
|
"name": "image_filename",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_source_url": {
|
||||||
|
"name": "image_source_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'researching'"
|
||||||
|
},
|
||||||
|
"pros": {
|
||||||
|
"name": "pros",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cons": {
|
||||||
|
"name": "cons",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"thread_candidates_thread_id_threads_id_fk": {
|
||||||
|
"name": "thread_candidates_thread_id_threads_id_fk",
|
||||||
|
"tableFrom": "thread_candidates",
|
||||||
|
"tableTo": "threads",
|
||||||
|
"columnsFrom": [
|
||||||
|
"thread_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"thread_candidates_category_id_categories_id_fk": {
|
||||||
|
"name": "thread_candidates_category_id_categories_id_fk",
|
||||||
|
"tableFrom": "thread_candidates",
|
||||||
|
"tableTo": "categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.threads": {
|
||||||
|
"name": "threads",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'active'"
|
||||||
|
},
|
||||||
|
"resolved_candidate_id": {
|
||||||
|
"name": "resolved_candidate_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"threads_category_id_categories_id_fk": {
|
||||||
|
"name": "threads_category_id_categories_id_fk",
|
||||||
|
"tableFrom": "threads",
|
||||||
|
"tableTo": "categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"users_username_unique": {
|
||||||
|
"name": "users_username_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
drizzle-pg/meta/_journal.json
Normal file
13
drizzle-pg/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1775297839520,
|
||||||
|
"tag": "0000_fuzzy_shiva",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { defineConfig } from "drizzle-kit";
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
out: "./drizzle",
|
out: "./drizzle-pg",
|
||||||
schema: "./src/db/schema.ts",
|
schema: "./src/db/schema.ts",
|
||||||
dialect: "sqlite",
|
dialect: "postgresql",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: process.env.DATABASE_PATH || "gearbox.db",
|
url:
|
||||||
|
process.env.DATABASE_URL ||
|
||||||
|
"postgresql://gearbox:gearbox@localhost:5432/gearbox",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,12 +21,10 @@
|
|||||||
"@tanstack/react-query-devtools": "^5.91.3",
|
"@tanstack/react-query-devtools": "^5.91.3",
|
||||||
"@tanstack/react-router-devtools": "^1.166.7",
|
"@tanstack/react-router-devtools": "^1.166.7",
|
||||||
"@tanstack/router-plugin": "^1.166.9",
|
"@tanstack/router-plugin": "^1.166.9",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"better-sqlite3": "^12.8.0",
|
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"drizzle-kit": "^0.31.9",
|
"drizzle-kit": "^0.31.9",
|
||||||
"vite": "^8.0.0"
|
"vite": "^8.0.0"
|
||||||
@@ -35,6 +33,7 @@
|
|||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@electric-sql/pglite": "^0.4.3",
|
||||||
"@hono/zod-validator": "^0.7.6",
|
"@hono/zod-validator": "^0.7.6",
|
||||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
@@ -45,6 +44,7 @@
|
|||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
"hono": "^4.12.8",
|
"hono": "^4.12.8",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
|
"postgres": "^3.4.8",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"recharts": "^3.8.0",
|
"recharts": "^3.8.0",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Database } from "bun:sqlite";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
import postgres from "postgres";
|
||||||
import * as schema from "./schema.ts";
|
import * as schema from "./schema.ts";
|
||||||
|
|
||||||
const sqlite = new Database(process.env.DATABASE_PATH || "gearbox.db");
|
const connectionString =
|
||||||
sqlite.run("PRAGMA journal_mode = WAL");
|
process.env.DATABASE_URL ||
|
||||||
sqlite.run("PRAGMA foreign_keys = ON");
|
"postgresql://gearbox:gearbox@localhost:5432/gearbox";
|
||||||
|
const queryClient = postgres(connectionString);
|
||||||
export const db = drizzle(sqlite, { schema });
|
export const db = drizzle(queryClient, { schema });
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Database } from "bun:sqlite";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||||
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
import postgres from "postgres";
|
||||||
|
|
||||||
const sqlite = new Database(process.env.DATABASE_PATH || "gearbox.db");
|
const connectionString =
|
||||||
sqlite.run("PRAGMA journal_mode = WAL");
|
process.env.DATABASE_URL ||
|
||||||
sqlite.run("PRAGMA foreign_keys = ON");
|
"postgresql://gearbox:gearbox@localhost:5432/gearbox";
|
||||||
|
const migrationClient = postgres(connectionString, { max: 1 });
|
||||||
|
const db = drizzle(migrationClient);
|
||||||
|
|
||||||
const db = drizzle(sqlite);
|
await migrate(db, { migrationsFolder: "./drizzle-pg" });
|
||||||
migrate(db, { migrationsFolder: "./drizzle" });
|
await migrationClient.end();
|
||||||
|
|
||||||
sqlite.close();
|
|
||||||
console.log("Migrations applied successfully");
|
console.log("Migrations applied successfully");
|
||||||
|
|||||||
128
src/db/schema.ts
128
src/db/schema.ts
@@ -1,18 +1,24 @@
|
|||||||
import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
import {
|
||||||
|
boolean,
|
||||||
|
doublePrecision,
|
||||||
|
integer,
|
||||||
|
pgTable,
|
||||||
|
serial,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const categories = sqliteTable("categories", {
|
export const categories = pgTable("categories", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull().unique(),
|
name: text("name").notNull().unique(),
|
||||||
icon: text("icon").notNull().default("package"),
|
icon: text("icon").notNull().default("package"),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const items = sqliteTable("items", {
|
export const items = pgTable("items", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
weightGrams: real("weight_grams"),
|
weightGrams: doublePrecision("weight_grams"),
|
||||||
priceCents: integer("price_cents"),
|
priceCents: integer("price_cents"),
|
||||||
categoryId: integer("category_id")
|
categoryId: integer("category_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -22,37 +28,29 @@ export const items = sqliteTable("items", {
|
|||||||
imageFilename: text("image_filename"),
|
imageFilename: text("image_filename"),
|
||||||
imageSourceUrl: text("image_source_url"),
|
imageSourceUrl: text("image_source_url"),
|
||||||
quantity: integer("quantity").notNull().default(1),
|
quantity: integer("quantity").notNull().default(1),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const threads = sqliteTable("threads", {
|
export const threads = pgTable("threads", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
status: text("status").notNull().default("active"),
|
status: text("status").notNull().default("active"),
|
||||||
resolvedCandidateId: integer("resolved_candidate_id"),
|
resolvedCandidateId: integer("resolved_candidate_id"),
|
||||||
categoryId: integer("category_id")
|
categoryId: integer("category_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => categories.id),
|
.references(() => categories.id),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const threadCandidates = sqliteTable("thread_candidates", {
|
export const threadCandidates = pgTable("thread_candidates", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
threadId: integer("thread_id")
|
threadId: integer("thread_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => threads.id, { onDelete: "cascade" }),
|
.references(() => threads.id, { onDelete: "cascade" }),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
weightGrams: real("weight_grams"),
|
weightGrams: doublePrecision("weight_grams"),
|
||||||
priceCents: integer("price_cents"),
|
priceCents: integer("price_cents"),
|
||||||
categoryId: integer("category_id")
|
categoryId: integer("category_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -64,28 +62,20 @@ export const threadCandidates = sqliteTable("thread_candidates", {
|
|||||||
status: text("status").notNull().default("researching"),
|
status: text("status").notNull().default("researching"),
|
||||||
pros: text("pros"),
|
pros: text("pros"),
|
||||||
cons: text("cons"),
|
cons: text("cons"),
|
||||||
sortOrder: real("sort_order").notNull().default(0),
|
sortOrder: doublePrecision("sort_order").notNull().default(0),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setups = sqliteTable("setups", {
|
export const setups = pgTable("setups", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupItems = sqliteTable("setup_items", {
|
export const setupItems = pgTable("setup_items", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
setupId: integer("setup_id")
|
setupId: integer("setup_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => setups.id, { onDelete: "cascade" }),
|
.references(() => setups.id, { onDelete: "cascade" }),
|
||||||
@@ -95,69 +85,59 @@ export const setupItems = sqliteTable("setup_items", {
|
|||||||
classification: text("classification").notNull().default("base"),
|
classification: text("classification").notNull().default("base"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const settings = sqliteTable("settings", {
|
export const settings = pgTable("settings", {
|
||||||
key: text("key").primaryKey(),
|
key: text("key").primaryKey(),
|
||||||
value: text("value").notNull(),
|
value: text("value").notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const users = sqliteTable("users", {
|
export const users = pgTable("users", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
username: text("username").notNull().unique(),
|
username: text("username").notNull().unique(),
|
||||||
passwordHash: text("password_hash").notNull(),
|
passwordHash: text("password_hash").notNull(),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sessions = sqliteTable("sessions", {
|
export const sessions = pgTable("sessions", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
userId: integer("user_id")
|
userId: integer("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => users.id, { onDelete: "cascade" }),
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiKeys = sqliteTable("api_keys", {
|
export const apiKeys = pgTable("api_keys", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
keyHash: text("key_hash").notNull(),
|
keyHash: text("key_hash").notNull(),
|
||||||
keyPrefix: text("key_prefix").notNull(),
|
keyPrefix: text("key_prefix").notNull(),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const oauthClients = sqliteTable("oauth_clients", {
|
export const oauthClients = pgTable("oauth_clients", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
clientId: text("client_id").notNull().unique(),
|
clientId: text("client_id").notNull().unique(),
|
||||||
clientName: text("client_name"),
|
clientName: text("client_name"),
|
||||||
redirectUris: text("redirect_uris").notNull(), // JSON array
|
redirectUris: text("redirect_uris").notNull(), // JSON array
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const oauthCodes = sqliteTable("oauth_codes", {
|
export const oauthCodes = pgTable("oauth_codes", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
code: text("code").notNull().unique(),
|
code: text("code").notNull().unique(),
|
||||||
clientId: text("client_id").notNull(),
|
clientId: text("client_id").notNull(),
|
||||||
codeChallenge: text("code_challenge").notNull(),
|
codeChallenge: text("code_challenge").notNull(),
|
||||||
codeChallengeMethod: text("code_challenge_method").notNull().default("S256"),
|
codeChallengeMethod: text("code_challenge_method").notNull().default("S256"),
|
||||||
redirectUri: text("redirect_uri").notNull(),
|
redirectUri: text("redirect_uri").notNull(),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
used: integer("used").notNull().default(0),
|
used: boolean("used").notNull().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const oauthTokens = sqliteTable("oauth_tokens", {
|
export const oauthTokens = pgTable("oauth_tokens", {
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: serial("id").primaryKey(),
|
||||||
accessTokenHash: text("access_token_hash").notNull().unique(),
|
accessTokenHash: text("access_token_hash").notNull().unique(),
|
||||||
refreshTokenHash: text("refresh_token_hash").notNull().unique(),
|
refreshTokenHash: text("refresh_token_hash").notNull().unique(),
|
||||||
clientId: text("client_id").notNull(),
|
clientId: text("client_id").notNull(),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(), // access token expiry
|
expiresAt: timestamp("expires_at").notNull(), // access token expiry
|
||||||
refreshExpiresAt: integer("refresh_expires_at", {
|
refreshExpiresAt: timestamp("refresh_expires_at").notNull(), // refresh token expiry
|
||||||
mode: "timestamp",
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
}).notNull(), // refresh token expiry
|
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { db } from "./index.ts";
|
import { db } from "./index.ts";
|
||||||
import { categories } from "./schema.ts";
|
import { categories } from "./schema.ts";
|
||||||
|
|
||||||
export function seedDefaults() {
|
export async function seedDefaults() {
|
||||||
const existing = db.select().from(categories).all();
|
const existing = await db.select().from(categories);
|
||||||
if (existing.length === 0) {
|
if (existing.length === 0) {
|
||||||
db.insert(categories)
|
await db.insert(categories).values({
|
||||||
.values({
|
name: "Uncategorized",
|
||||||
name: "Uncategorized",
|
icon: "package",
|
||||||
icon: "package",
|
});
|
||||||
})
|
|
||||||
.run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
import { Database } from "bun:sqlite";
|
import { drizzle } from "drizzle-orm/pglite";
|
||||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
import { migrate } from "drizzle-orm/pglite/migrator";
|
||||||
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
|
||||||
import * as schema from "../../src/db/schema.ts";
|
import * as schema from "../../src/db/schema.ts";
|
||||||
|
|
||||||
export function createTestDb() {
|
export async function createTestDb() {
|
||||||
const sqlite = new Database(":memory:");
|
const db = drizzle({ schema });
|
||||||
sqlite.run("PRAGMA foreign_keys = ON");
|
|
||||||
|
|
||||||
const db = drizzle(sqlite, { schema });
|
// Apply migrations from the new PostgreSQL migration directory
|
||||||
|
await migrate(db, { migrationsFolder: "./drizzle-pg" });
|
||||||
// Apply all migrations to create tables
|
|
||||||
migrate(db, { migrationsFolder: "./drizzle" });
|
|
||||||
|
|
||||||
// Seed default Uncategorized category
|
// Seed default Uncategorized category
|
||||||
db.insert(schema.categories)
|
await db
|
||||||
.values({ name: "Uncategorized", icon: "package" })
|
.insert(schema.categories)
|
||||||
.run();
|
.values({ name: "Uncategorized", icon: "package" });
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user