Add Drizzle ORM with shared db package (#10)

This commit is contained in:
Ullrich Schäfer 2026-03-22 23:35:50 +01:00 committed by GitHub
parent 6a3e566438
commit 9deda5f125
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 783 additions and 109 deletions

View file

@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: ["./src/schema/planner.ts", "./src/schema/journal.ts"],
out: "./migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL ?? "postgres://trails:trails@localhost:5432/trails",
},
});

16
packages/db/package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "@trails-cool/db",
"version": "0.0.1",
"type": "module",
"exports": {
".": "./src/index.ts",
"./schema/planner": "./src/schema/planner.ts",
"./schema/journal": "./src/schema/journal.ts"
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"dependencies": {
"drizzle-orm": "catalog:",
"postgres": "catalog:"
}
}

17
packages/db/src/index.ts Normal file
View file

@ -0,0 +1,17 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as plannerSchema from "./schema/planner";
import * as journalSchema from "./schema/journal";
export function createDb(connectionString?: string) {
const client = postgres(
connectionString ?? process.env.DATABASE_URL ?? "postgres://trails:trails@localhost:5432/trails",
);
return drizzle(client, {
schema: { ...plannerSchema, ...journalSchema },
});
}
export type Database = ReturnType<typeof createDb>;
export { plannerSchema, journalSchema };

View file

@ -0,0 +1,86 @@
import {
pgSchema,
text,
timestamp,
integer,
real,
jsonb,
customType,
} from "drizzle-orm/pg-core";
const bytea = customType<{ data: Buffer }>({
dataType() {
return "bytea";
},
});
const lineString = customType<{ data: string }>({
dataType() {
return "geometry(LineString, 4326)";
},
});
export const journalSchema = pgSchema("journal");
export const users = journalSchema.table("users", {
id: text("id").primaryKey(),
email: text("email").notNull().unique(),
passwordHash: text("password_hash").notNull(),
username: text("username").notNull().unique(),
displayName: text("display_name"),
bio: text("bio"),
domain: text("domain").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
});
export const routes = journalSchema.table("routes", {
id: text("id").primaryKey(),
ownerId: text("owner_id")
.notNull()
.references(() => users.id),
name: text("name").notNull(),
description: text("description").default(""),
gpx: text("gpx"),
geom: lineString("geom"),
routingProfile: text("routing_profile"),
distance: real("distance"),
elevationGain: real("elevation_gain"),
elevationLoss: real("elevation_loss"),
dayBreaks: jsonb("day_breaks").$type<number[]>(),
tags: jsonb("tags").$type<string[]>(),
plannerState: bytea("planner_state"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
export const routeVersions = journalSchema.table("route_versions", {
id: text("id").primaryKey(),
routeId: text("route_id")
.notNull()
.references(() => routes.id, { onDelete: "cascade" }),
version: integer("version").notNull(),
gpx: text("gpx").notNull(),
createdBy: text("created_by").references(() => users.id),
changeDescription: text("change_description"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
});
export const activities = journalSchema.table("activities", {
id: text("id").primaryKey(),
ownerId: text("owner_id")
.notNull()
.references(() => users.id),
routeId: text("route_id").references(() => routes.id),
name: text("name").notNull(),
description: text("description").default(""),
gpx: text("gpx"),
geom: lineString("geom"),
startedAt: timestamp("started_at", { withTimezone: true }),
duration: integer("duration"),
distance: real("distance"),
elevationGain: real("elevation_gain"),
elevationLoss: real("elevation_loss"),
photos: jsonb("photos").$type<string[]>(),
participants: jsonb("participants").$type<string[]>(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
});

View file

@ -0,0 +1,19 @@
import { pgSchema, text, timestamp, boolean, customType } from "drizzle-orm/pg-core";
const bytea = customType<{ data: Buffer }>({
dataType() {
return "bytea";
},
});
export const plannerSchema = pgSchema("planner");
export const sessions = plannerSchema.table("sessions", {
id: text("id").primaryKey(),
yjsState: bytea("yjs_state"),
callbackUrl: text("callback_url"),
callbackToken: text("callback_token"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
lastActivity: timestamp("last_activity", { withTimezone: true }).notNull().defaultNow(),
closed: boolean("closed").notNull().default(false),
});

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}