mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 12:04:02 +00:00
feat(WHE-27): add drizzle db package and typed env config
This commit is contained in:
18
packages/config/package.json
Normal file
18
packages/config/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@household/config",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "bun build src/index.ts --outdir dist --target bun",
|
||||
"typecheck": "tsgo --project tsconfig.json --noEmit",
|
||||
"test": "bun test --pass-with-no-tests",
|
||||
"lint": "oxlint \"src\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-core": "^0.13.8",
|
||||
"zod": "^4.1.5"
|
||||
}
|
||||
}
|
||||
33
packages/config/src/env.ts
Normal file
33
packages/config/src/env.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createEnv } from '@t3-oss/env-core'
|
||||
import { z } from 'zod'
|
||||
|
||||
const server = {
|
||||
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
||||
APP_URL: z.string().url(),
|
||||
DATABASE_URL: z.string().url(),
|
||||
SUPABASE_URL: z.string().url(),
|
||||
SUPABASE_PUBLISHABLE_KEY: z.string().min(1),
|
||||
SUPABASE_SERVICE_ROLE_KEY: z.string().min(1),
|
||||
TELEGRAM_BOT_TOKEN: z.string().min(1),
|
||||
TELEGRAM_WEBHOOK_SECRET: z.string().min(1),
|
||||
TELEGRAM_BOT_USERNAME: z.string().min(1),
|
||||
OPENAI_API_KEY: z.string().min(1),
|
||||
PARSER_MODEL: z.string().min(1).default('gpt-4.1-mini'),
|
||||
SENTRY_DSN: z.string().url().optional(),
|
||||
GCP_PROJECT_ID: z.string().min(1),
|
||||
GCP_REGION: z.string().min(1).default('europe-west1'),
|
||||
CLOUD_RUN_SERVICE_BOT: z.string().min(1).default('household-bot'),
|
||||
SCHEDULER_SHARED_SECRET: z.string().min(1)
|
||||
}
|
||||
|
||||
export const env = createEnv({
|
||||
server,
|
||||
runtimeEnv: process.env,
|
||||
emptyStringAsUndefined: true,
|
||||
onValidationError: (issues) => {
|
||||
console.error('Invalid environment variables:')
|
||||
console.error(JSON.stringify(issues, null, 2))
|
||||
throw new Error('Environment validation failed')
|
||||
}
|
||||
})
|
||||
1
packages/config/src/index.ts
Normal file
1
packages/config/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { env } from './env'
|
||||
7
packages/config/tsconfig.json
Normal file
7
packages/config/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
14
packages/db/drizzle.config.ts
Normal file
14
packages/db/drizzle.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
|
||||
const dbCredentials = process.env.DATABASE_URL
|
||||
? {
|
||||
url: process.env.DATABASE_URL
|
||||
}
|
||||
: undefined
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'postgresql',
|
||||
schema: './packages/db/src/schema.ts',
|
||||
out: './packages/db/drizzle',
|
||||
dbCredentials
|
||||
})
|
||||
18
packages/db/drizzle/0000_modern_centennial.sql
Normal file
18
packages/db/drizzle/0000_modern_centennial.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "households" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "members" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"household_id" uuid NOT NULL,
|
||||
"telegram_user_id" text NOT NULL,
|
||||
"display_name" text NOT NULL,
|
||||
"is_admin" integer DEFAULT 0 NOT NULL,
|
||||
"joined_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "members" ADD CONSTRAINT "members_household_id_households_id_fk" FOREIGN KEY ("household_id") REFERENCES "public"."households"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "members_household_idx" ON "members" USING btree ("household_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "members_household_tg_user_unique" ON "members" USING btree ("household_id","telegram_user_id");
|
||||
151
packages/db/drizzle/meta/0000_snapshot.json
Normal file
151
packages/db/drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"id": "41ded009-68a0-4c58-87ba-e4ff51d87211",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.households": {
|
||||
"name": "households",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.members": {
|
||||
"name": "members",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"telegram_user_id": {
|
||||
"name": "telegram_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"is_admin": {
|
||||
"name": "is_admin",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"joined_at": {
|
||||
"name": "joined_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"members_household_idx": {
|
||||
"name": "members_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"members_household_tg_user_unique": {
|
||||
"name": "members_household_tg_user_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "telegram_user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"members_household_id_households_id_fk": {
|
||||
"name": "members_household_id_households_id_fk",
|
||||
"tableFrom": "members",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": ["household_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
13
packages/db/drizzle/meta/_journal.json
Normal file
13
packages/db/drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1772663250726,
|
||||
"tag": "0000_modern_centennial",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
16
packages/db/package.json
Normal file
16
packages/db/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@household/db",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "bun build src/index.ts --outdir dist --target bun",
|
||||
"typecheck": "tsgo --project tsconfig.json --noEmit",
|
||||
"test": "bun test --pass-with-no-tests",
|
||||
"lint": "oxlint \"src\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@household/config": "workspace:*",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"postgres": "^3.4.7"
|
||||
}
|
||||
}
|
||||
12
packages/db/src/client.ts
Normal file
12
packages/db/src/client.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import postgres from 'postgres'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
|
||||
import { env } from '@household/config'
|
||||
|
||||
const queryClient = postgres(env.DATABASE_URL, {
|
||||
prepare: false,
|
||||
max: 5
|
||||
})
|
||||
|
||||
export const db = drizzle(queryClient)
|
||||
export { queryClient }
|
||||
2
packages/db/src/index.ts
Normal file
2
packages/db/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { db, queryClient } from './client'
|
||||
export * as schema from './schema'
|
||||
28
packages/db/src/schema.ts
Normal file
28
packages/db/src/schema.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { index, integer, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core'
|
||||
|
||||
export const households = pgTable('households', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
||||
})
|
||||
|
||||
export const members = pgTable(
|
||||
'members',
|
||||
{
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
householdId: uuid('household_id')
|
||||
.notNull()
|
||||
.references(() => households.id, { onDelete: 'cascade' }),
|
||||
telegramUserId: text('telegram_user_id').notNull(),
|
||||
displayName: text('display_name').notNull(),
|
||||
isAdmin: integer('is_admin').default(0).notNull(),
|
||||
joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow().notNull()
|
||||
},
|
||||
(table) => ({
|
||||
householdIdx: index('members_household_idx').on(table.householdId),
|
||||
householdTgUserUnique: uniqueIndex('members_household_tg_user_unique').on(
|
||||
table.householdId,
|
||||
table.telegramUserId
|
||||
)
|
||||
})
|
||||
)
|
||||
7
packages/db/tsconfig.json
Normal file
7
packages/db/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "drizzle.config.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user