Switch to drizzle
This commit is contained in:
parent
c9d946ea0e
commit
4ed65a8034
17 changed files with 1645 additions and 184 deletions
11
drizzle.config.ts
Normal file
11
drizzle.config.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import "dotenv/config";
|
||||
import type { Config } from "drizzle-kit";
|
||||
|
||||
export default {
|
||||
schema: "./src/schema.ts",
|
||||
out: "./drizzle",
|
||||
driver: "pg",
|
||||
dbCredentials: {
|
||||
connectionString: process.env.DATABASE_URL!,
|
||||
},
|
||||
} satisfies Config;
|
39
drizzle/0000_icy_vulcan.sql
Normal file
39
drizzle/0000_icy_vulcan.sql
Normal file
|
@ -0,0 +1,39 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "status" AS ENUM('pending', 'success', 'error');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "letters" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
"updated_at" timestamp DEFAULT now(),
|
||||
"first_name" text NOT NULL,
|
||||
"last_name" text NOT NULL,
|
||||
"lang" text NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"phone" text NOT NULL,
|
||||
"variant" text NOT NULL,
|
||||
"gender" text,
|
||||
"branch" text NOT NULL,
|
||||
"is_client" boolean NOT NULL,
|
||||
"message" text NOT NULL,
|
||||
"is_subscribed" boolean NOT NULL,
|
||||
"confirmation_token" text NOT NULL,
|
||||
"is_confirmed" boolean DEFAULT false,
|
||||
CONSTRAINT "letters_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "messages" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"letter_id" integer,
|
||||
"from" text NOT NULL,
|
||||
"to" text NOT NULL,
|
||||
"status" "status" NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "messages" ADD CONSTRAINT "messages_letter_id_letters_id_fk" FOREIGN KEY ("letter_id") REFERENCES "letters"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
195
drizzle/meta/0000_snapshot.json
Normal file
195
drizzle/meta/0000_snapshot.json
Normal file
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"id": "ba6993c6-c9e8-443c-a3cc-abade1e5d34e",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"letters": {
|
||||
"name": "letters",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"first_name": {
|
||||
"name": "first_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_name": {
|
||||
"name": "last_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"lang": {
|
||||
"name": "lang",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"variant": {
|
||||
"name": "variant",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"gender": {
|
||||
"name": "gender",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"branch": {
|
||||
"name": "branch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"is_client": {
|
||||
"name": "is_client",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"message": {
|
||||
"name": "message",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"is_subscribed": {
|
||||
"name": "is_subscribed",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"confirmation_token": {
|
||||
"name": "confirmation_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"is_confirmed": {
|
||||
"name": "is_confirmed",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"letters_email_unique": {
|
||||
"name": "letters_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"name": "messages",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"letter_id": {
|
||||
"name": "letter_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"from": {
|
||||
"name": "from",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"to": {
|
||||
"name": "to",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "status",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"messages_letter_id_letters_id_fk": {
|
||||
"name": "messages_letter_id_letters_id_fk",
|
||||
"tableFrom": "messages",
|
||||
"tableTo": "letters",
|
||||
"columnsFrom": [
|
||||
"letter_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"status": {
|
||||
"name": "status",
|
||||
"values": {
|
||||
"pending": "pending",
|
||||
"success": "success",
|
||||
"error": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1709563345794,
|
||||
"tag": "0000_icy_vulcan",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
1302
package-lock.json
generated
1302
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -10,25 +10,27 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"test": "vitest",
|
||||
"migrate": "node --loader ts-node/esm ./scripts/migrate.ts",
|
||||
"db:generate": "drizzle-kit generate:pg",
|
||||
"db:migrate": "node --loader ts-node/esm ./scripts/migrate.ts",
|
||||
"tn": "node --loader ts-node/esm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.4.1",
|
||||
"@astrojs/node": "^8.2.0",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@types/pg": "^8.11.2",
|
||||
"astro": "^4.3.2",
|
||||
"better-sqlite3": "^9.4.0",
|
||||
"change-case": "^5.4.2",
|
||||
"drizzle-orm": "^0.29.4",
|
||||
"html-template-tag": "^4.0.1",
|
||||
"kysely": "^0.27.2",
|
||||
"postgres": "^3.4.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.9",
|
||||
"dotenv": "^16.4.5",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"ts-node": "^10.9.2",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
|
|
|
@ -2,19 +2,18 @@ import "dotenv/config";
|
|||
|
||||
import { db } from "../src/db";
|
||||
import * as fs from "node:fs/promises";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import * as schema from "../src/schema";
|
||||
|
||||
async function exportEmails() {
|
||||
const file = await fs.open("./emails.csv", "w");
|
||||
const letters = await db
|
||||
.selectFrom("letters")
|
||||
.where("confirmed", "=", 1)
|
||||
.where("subscribed", "=", 1)
|
||||
.groupBy("email")
|
||||
.select(["email", "firstName", "lastName"])
|
||||
.execute();
|
||||
.select()
|
||||
.from(schema.letters)
|
||||
.where(and(eq(schema.letters.confirmed, true), eq(schema.letters.subscribed, true)))
|
||||
.groupBy(schema.letters.email);
|
||||
|
||||
for (const letter of letters) {
|
||||
console.log(letter);
|
||||
file.write(`${letter.email},"${letter.firstName} ${letter.lastName}"\n`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,63 +1,7 @@
|
|||
import "dotenv/config";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||
import postgres from "postgres";
|
||||
|
||||
import { sql } from "kysely";
|
||||
import { db } from "../src/db";
|
||||
|
||||
const migrations = [
|
||||
db.schema
|
||||
.createTable("letters")
|
||||
.addColumn("id", "integer", (c) => c.primaryKey())
|
||||
.addColumn("created", "datetime", (c) => c.defaultTo(sql`CURRENT_TIMESTAMP`).notNull())
|
||||
.addColumn("updated", "datetime", (c) => c.defaultTo(sql`CURRENT_TIMESTAMP`).notNull())
|
||||
.addColumn("firstName", "text", (c) => c.notNull())
|
||||
.addColumn("lastName", "text", (c) => c.notNull())
|
||||
.addColumn("language", "text", (c) => c.notNull())
|
||||
.addColumn("email", "text", (c) => c.notNull())
|
||||
.addColumn("phone", "text")
|
||||
.addColumn("variant", "text", (c) => c.notNull())
|
||||
.addColumn("gender", "text")
|
||||
.addColumn("branch", "text", (c) => c.notNull())
|
||||
.addColumn("isClient", "boolean", (c) => c.notNull())
|
||||
.addColumn("message", "text", (c) => c.notNull())
|
||||
.addColumn("subscribed", "boolean", (c) => c.notNull())
|
||||
.addColumn("confirmationToken", "text", (c) => c.notNull())
|
||||
.addColumn("confirmed", "boolean", (c) => c.notNull().defaultTo(false)),
|
||||
db.schema
|
||||
.createTable("messages")
|
||||
.addColumn("id", "integer", (c) => c.primaryKey())
|
||||
.addColumn("letterId", "integer", (c) => c.notNull())
|
||||
.addColumn("from", "text", (c) => c.notNull())
|
||||
.addColumn("to", "text", (c) => c.notNull())
|
||||
.addColumn("status", "text", (c) => c.notNull()),
|
||||
];
|
||||
|
||||
async function initMigrationsTable() {
|
||||
await db.schema
|
||||
.createTable("migrations")
|
||||
.ifNotExists()
|
||||
.addColumn("id", "integer", (c) => c.primaryKey())
|
||||
.addColumn("executedAt", "datetime", (c) => c.defaultTo(sql`CURRENT_TIMESTAMP`).notNull())
|
||||
.execute();
|
||||
}
|
||||
|
||||
async function runMigrations() {
|
||||
initMigrationsTable();
|
||||
|
||||
for (const [id, migration] of migrations.entries()) {
|
||||
const migrationRecord = await db
|
||||
.selectFrom("migrations")
|
||||
.where("id", "=", id)
|
||||
.select(["id", "executedAt"])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (migrationRecord) {
|
||||
console.log(id, "|", "at", migrationRecord.executedAt);
|
||||
} else {
|
||||
await migration.execute();
|
||||
await db.insertInto("migrations").values({ id }).execute();
|
||||
console.log(id, "|", "now");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runMigrations();
|
||||
const migrationClient = postgres(process.env.DATABASE_URL!, { max: 1 });
|
||||
migrate(drizzle(migrationClient), { migrationsFolder: "drizzle" });
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import "dotenv/config";
|
||||
|
||||
import { db } from "../src/db";
|
||||
import * as schema from "../src/schema";
|
||||
import { getEntry, getCollection } from "astro:content";
|
||||
import { mailClient } from "../src/mail";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
|
@ -26,18 +28,12 @@ export function getSubject(lang: string, variantNr: string) {
|
|||
}
|
||||
|
||||
async function processLetters() {
|
||||
const letters = await db
|
||||
.selectFrom("letters")
|
||||
.where("confirmed", "=", 1)
|
||||
.where("language", "=", "hu")
|
||||
.where("id", ">", 177)
|
||||
.select(["id", "firstName", "lastName", "language", "variant", "branch", "email", "message"])
|
||||
.execute();
|
||||
const letters = await db.select().from(schema.letters).where(eq(schema.letters.confirmed, true));
|
||||
|
||||
for (const letter of letters) {
|
||||
const branch = await getEntry("branches", letter.branch as string);
|
||||
const subject = getSubject(
|
||||
letter.branch === "vig-holding" ? "en" : letter.language,
|
||||
letter.branch === "vig-holding" ? "en" : letter.lang,
|
||||
letter.variant
|
||||
);
|
||||
console.log("Subject", subject);
|
||||
|
@ -48,17 +44,15 @@ async function processLetters() {
|
|||
}
|
||||
|
||||
for (const recipientEmail of branch.data.emails) {
|
||||
console.log("sending", letter.id, "to", recipientEmail);
|
||||
const message = await db
|
||||
.insertInto("messages")
|
||||
const [message] = await db
|
||||
.insert(schema.messages)
|
||||
.values({
|
||||
letterId: letter.id,
|
||||
from: letter.email,
|
||||
to: recipientEmail,
|
||||
status: "pending",
|
||||
})
|
||||
.returning(["id"])
|
||||
.executeTakeFirst();
|
||||
.returning({ id: schema.messages.id });
|
||||
|
||||
if (!message) {
|
||||
console.error("Error creating message");
|
||||
|
@ -67,7 +61,7 @@ async function processLetters() {
|
|||
|
||||
const name = `${letter.firstName} ${letter.lastName}`;
|
||||
const messageText =
|
||||
letter.language === "hu"
|
||||
letter.lang === "hu"
|
||||
? letter.message.replace("Tisztelt Hölgyem,", "Tisztelt Hölgyem/Uram,")
|
||||
: letter.message;
|
||||
|
||||
|
@ -83,17 +77,15 @@ async function processLetters() {
|
|||
console.log(result);
|
||||
|
||||
await db
|
||||
.updateTable("messages")
|
||||
.update(schema.messages)
|
||||
.set({ status: "success" })
|
||||
.where("id", "=", message.id)
|
||||
.execute();
|
||||
.where(eq(schema.messages.id, message.id));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await db
|
||||
.updateTable("messages")
|
||||
.update(schema.messages)
|
||||
.set({ status: "error" })
|
||||
.where("id", "=", message.id)
|
||||
.execute();
|
||||
.where(eq(schema.messages.id, message.id));
|
||||
}
|
||||
|
||||
await setTimeout(2000);
|
||||
|
|
6
src/db.ts
Normal file
6
src/db.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const client = postgres(import.meta.env.DATABASE_URL);
|
||||
export const db = drizzle(client, { schema });
|
|
@ -1,16 +0,0 @@
|
|||
import { Kysely, SqliteDialect } from "kysely";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
const path = process.env.DATABASE_PATH;
|
||||
const database = new Database(path);
|
||||
database.exec("PRAGMA journal_mode = WAL;");
|
||||
|
||||
const dialect = new SqliteDialect({
|
||||
database,
|
||||
});
|
||||
|
||||
const kysely = new Kysely<any>({
|
||||
dialect,
|
||||
});
|
||||
|
||||
export { kysely as db };
|
|
@ -1,25 +0,0 @@
|
|||
import type { ColumnType } from "kysely";
|
||||
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S, I | undefined, U>
|
||||
: ColumnType<T, T | undefined, T>;
|
||||
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||
|
||||
export type Letter = {
|
||||
id: Generated<number>;
|
||||
created: Generated<string>;
|
||||
updated: Generated<string>;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string | null;
|
||||
variant: string;
|
||||
gender: string | null;
|
||||
branch: string;
|
||||
message: string;
|
||||
subscribed: number;
|
||||
confirmationToken: string;
|
||||
confirmed: Generated<number>;
|
||||
};
|
||||
export type DB = {
|
||||
Letter: Letter;
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import "dotenv/config";
|
||||
import type { MiddlewareHandler } from "astro";
|
||||
import { isKnownLang } from "./lang";
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { APIContext } from "astro";
|
||||
import { db } from "../../../db";
|
||||
import * as schema from "../../../schema";
|
||||
import type { Lang } from "../../../lang";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
|
@ -10,18 +12,19 @@ interface Params extends Record<string, string> {
|
|||
}
|
||||
|
||||
export async function GET({ params: { lang, token }, redirect }: APIContext<never, Params>) {
|
||||
const letter = await db
|
||||
.selectFrom("letters")
|
||||
.where("confirmationToken", "=", token)
|
||||
.select(["id", "confirmationToken", "confirmed"])
|
||||
.executeTakeFirst();
|
||||
const letter = await db.query.letters.findFirst({
|
||||
where: eq(schema.letters.confirmationToken, token),
|
||||
});
|
||||
|
||||
if (!letter) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
if (!letter.confirmed) {
|
||||
db.updateTable("letters").set({ confirmed: 1 }).where("id", "=", letter.id).execute();
|
||||
await db
|
||||
.update(schema.letters)
|
||||
.set({ confirmed: true })
|
||||
.where(eq(schema.letters.id, letter.id));
|
||||
}
|
||||
|
||||
return redirect(`/${lang}/email-confirmed`, 301);
|
||||
|
|
|
@ -2,10 +2,12 @@ import type { APIContext } from "astro";
|
|||
import type { Lang } from "../../lang";
|
||||
import { letterFormSchema } from "../../letter/schema";
|
||||
import { db } from "../../db";
|
||||
import * as schema from "../../schema";
|
||||
import { generateToken } from "../../utils";
|
||||
import { mailClient } from "../../mail";
|
||||
import { renderMail } from "../../mails/confirm-email";
|
||||
import { makeT } from "../../i18n";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
|
@ -28,12 +30,9 @@ export async function POST({ request, params: { lang }, redirect }: APIContext<n
|
|||
|
||||
const { data } = validationResult;
|
||||
|
||||
const existingLetter = await db
|
||||
.selectFrom("letters")
|
||||
.where("email", "=", data.email)
|
||||
.where("branch", "=", data.branch)
|
||||
.select("id")
|
||||
.executeTakeFirst();
|
||||
const existingLetter = await db.query.letters.findFirst({
|
||||
where: and(eq(schema.letters.email, data.email), eq(schema.letters.branch, data.branch)),
|
||||
});
|
||||
|
||||
if (existingLetter) {
|
||||
return redirect(`/${lang}/letter-exists`, 303);
|
||||
|
@ -41,19 +40,17 @@ export async function POST({ request, params: { lang }, redirect }: APIContext<n
|
|||
|
||||
const values = {
|
||||
...data,
|
||||
language: lang,
|
||||
confirmationToken: generateToken(32),
|
||||
updated: new Date().toISOString(),
|
||||
isClient: Number(data.subscribed),
|
||||
subscribed: Number(data.subscribed),
|
||||
confirmed: 0,
|
||||
};
|
||||
updated: new Date(),
|
||||
phone: data.phone ?? "",
|
||||
lang,
|
||||
} satisfies typeof schema.letters.$inferInsert;
|
||||
|
||||
const letterRecord = await db
|
||||
.insertInto("letters")
|
||||
.values(values)
|
||||
.returning(["id", "email", "confirmationToken"])
|
||||
.executeTakeFirst();
|
||||
const [letterRecord] = await db.insert(schema.letters).values(values).returning({
|
||||
id: schema.letters.id,
|
||||
email: schema.letters.email,
|
||||
confirmationToken: schema.letters.confirmationToken,
|
||||
});
|
||||
|
||||
if (!letterRecord) {
|
||||
throw new Error("No letter record returned from database.");
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
---
|
||||
import { count, countDistinct, desc, eq } from "drizzle-orm";
|
||||
import { db } from "../db";
|
||||
import * as schema from "../schema";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const query = db.selectFrom("letters").select(({ fn }) => [fn.count("id").as("count")]);
|
||||
const query = db.select({ count: count() }).from(schema.letters);
|
||||
|
||||
const [totalSubmissions] = await query.execute();
|
||||
const [confirmedSubmissions] = await query.where("confirmed", "=", 1).execute();
|
||||
const [totalSubmissions] = await query;
|
||||
const [confirmedSubmissions] = await query.where(eq(schema.letters.confirmed, true)).execute();
|
||||
|
||||
const submissionsByLanguage = await db
|
||||
.selectFrom("letters")
|
||||
.groupBy("language")
|
||||
.select(({ fn }) => ["language", fn.count("id").as("count")])
|
||||
.orderBy("count", "desc")
|
||||
.execute();
|
||||
.select({ count: count(), lang: schema.letters.lang })
|
||||
.from(schema.letters)
|
||||
.groupBy(schema.letters.lang)
|
||||
.orderBy((row) => desc(row.count));
|
||||
|
||||
const [uniqueSenders] = await db
|
||||
.selectFrom("letters")
|
||||
.select(({ fn }) => [fn.count("email").distinct().as("count")])
|
||||
.execute();
|
||||
.select({ count: countDistinct(schema.letters.email) })
|
||||
.from(schema.letters);
|
||||
---
|
||||
|
||||
<div>
|
||||
|
@ -37,7 +37,7 @@ const [uniqueSenders] = await db
|
|||
{
|
||||
submissionsByLanguage.map((row) => (
|
||||
<tr>
|
||||
<th class="text-left">{row.language}</th>
|
||||
<th class="text-left">{row.lang}</th>
|
||||
<th class="text-right">{row.count}</th>
|
||||
</tr>
|
||||
))
|
||||
|
|
30
src/schema.ts
Normal file
30
src/schema.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { text, boolean, pgTable, serial, timestamp, integer, pgEnum } from "drizzle-orm/pg-core";
|
||||
|
||||
export const letters = pgTable("letters", {
|
||||
id: serial("id").primaryKey(),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
updated: timestamp("updated_at").defaultNow(),
|
||||
firstName: text("first_name").notNull(),
|
||||
lastName: text("last_name").notNull(),
|
||||
lang: text("lang").notNull(),
|
||||
email: text("email").notNull().unique(),
|
||||
phone: text("phone").notNull(),
|
||||
variant: text("variant").notNull(),
|
||||
gender: text("gender"),
|
||||
branch: text("branch").notNull(),
|
||||
isClient: boolean("is_client").notNull(),
|
||||
message: text("message").notNull(),
|
||||
subscribed: boolean("is_subscribed").notNull(),
|
||||
confirmationToken: text("confirmation_token").notNull(),
|
||||
confirmed: boolean("is_confirmed").default(false),
|
||||
});
|
||||
|
||||
export const statusEnum = pgEnum("status", ["pending", "success", "error"]);
|
||||
|
||||
export const messages = pgTable("messages", {
|
||||
id: serial("id").primaryKey(),
|
||||
letterId: integer("letter_id").references(() => letters.id),
|
||||
from: text("from").notNull(),
|
||||
to: text("to").notNull(),
|
||||
status: statusEnum("status").notNull(),
|
||||
});
|
Reference in a new issue