webpage start

This commit is contained in:
2026-03-02 14:47:14 +01:00
parent 0856e154b9
commit 65d5ab71d7
59 changed files with 1889 additions and 1309 deletions

View File

@@ -1,28 +1,28 @@
{
"name": "@kk/api",
"type": "module",
"exports": {
".": {
"default": "./src/index.ts"
},
"./*": {
"default": "./src/*.ts"
}
},
"scripts": {},
"dependencies": {
"@kk/auth": "workspace:*",
"@kk/db": "workspace:*",
"@kk/env": "workspace:*",
"@orpc/client": "catalog:",
"@orpc/openapi": "catalog:",
"@orpc/server": "catalog:",
"@orpc/zod": "catalog:",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"typescript": "catalog:"
}
"name": "@kk/api",
"type": "module",
"exports": {
".": {
"default": "./src/index.ts"
},
"./*": {
"default": "./src/*.ts"
}
},
"scripts": {},
"dependencies": {
"@kk/auth": "workspace:*",
"@kk/db": "workspace:*",
"@kk/env": "workspace:*",
"@orpc/client": "catalog:",
"@orpc/openapi": "catalog:",
"@orpc/server": "catalog:",
"@orpc/zod": "catalog:",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"typescript": "catalog:"
}
}

View File

@@ -1,12 +1,12 @@
import { auth } from "@kk/auth";
export async function createContext({ req }: { req: Request }) {
const session = await auth.api.getSession({
headers: req.headers,
});
return {
session,
};
const session = await auth.api.getSession({
headers: req.headers,
});
return {
session,
};
}
export type Context = Awaited<ReturnType<typeof createContext>>;

View File

@@ -7,14 +7,14 @@ export const o = os.$context<Context>();
export const publicProcedure = o;
const requireAuth = o.middleware(async ({ context, next }) => {
if (!context.session?.user) {
throw new ORPCError("UNAUTHORIZED");
}
return next({
context: {
session: context.session,
},
});
if (!context.session?.user) {
throw new ORPCError("UNAUTHORIZED");
}
return next({
context: {
session: context.session,
},
});
});
export const protectedProcedure = publicProcedure.use(requireAuth);

View File

@@ -3,15 +3,15 @@ import type { RouterClient } from "@orpc/server";
import { protectedProcedure, publicProcedure } from "../index";
export const appRouter = {
healthCheck: publicProcedure.handler(() => {
return "OK";
}),
privateData: protectedProcedure.handler(({ context }) => {
return {
message: "This is private",
user: context.session?.user,
};
}),
healthCheck: publicProcedure.handler(() => {
return "OK";
}),
privateData: protectedProcedure.handler(({ context }) => {
return {
message: "This is private",
user: context.session?.user,
};
}),
};
export type AppRouter = typeof appRouter;
export type AppRouterClient = RouterClient<typeof appRouter>;

View File

@@ -1,10 +1,10 @@
{
"extends": "@kk/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"composite": true
}
"extends": "@kk/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"composite": true
}
}

View File

@@ -1,24 +1,24 @@
{
"name": "@kk/auth",
"type": "module",
"exports": {
".": {
"default": "./src/index.ts"
},
"./*": {
"default": "./src/*.ts"
}
},
"scripts": {},
"dependencies": {
"@kk/db": "workspace:*",
"@kk/env": "workspace:*",
"better-auth": "catalog:",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"typescript": "catalog:"
}
"name": "@kk/auth",
"type": "module",
"exports": {
".": {
"default": "./src/index.ts"
},
"./*": {
"default": "./src/*.ts"
}
},
"scripts": {},
"dependencies": {
"@kk/db": "workspace:*",
"@kk/env": "workspace:*",
"better-auth": "catalog:",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"typescript": "catalog:"
}
}

View File

@@ -6,14 +6,14 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { tanstackStartCookies } from "better-auth/tanstack-start";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
database: drizzleAdapter(db, {
provider: "sqlite",
schema: schema,
}),
trustedOrigins: [env.CORS_ORIGIN],
emailAndPassword: {
enabled: true,
},
plugins: [tanstackStartCookies()],
schema: schema,
}),
trustedOrigins: [env.CORS_ORIGIN],
emailAndPassword: {
enabled: true,
},
plugins: [tanstackStartCookies()],
});

View File

@@ -1,10 +1,10 @@
{
"extends": "@kk/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"composite": true
}
"extends": "@kk/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"composite": true
}
}

View File

@@ -1,5 +1,5 @@
{
"name": "@kk/config",
"version": "0.0.0",
"private": true
"name": "@kk/config",
"version": "0.0.0",
"private": true
}

View File

@@ -1,22 +1,22 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ESNext"],
"verbatimModuleSyntax": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"types": ["node", "@cloudflare/workers-types"]
}
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ESNext"],
"verbatimModuleSyntax": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"types": ["node", "@cloudflare/workers-types"]
}
}

View File

@@ -2,14 +2,14 @@ import dotenv from "dotenv";
import { defineConfig } from "drizzle-kit";
dotenv.config({
path: "../../apps/web/.env",
path: "../../apps/web/.env",
});
export default defineConfig({
schema: "./src/schema",
out: "./src/migrations",
dialect: "turso",
dbCredentials: {
url: process.env.DATABASE_URL || "",
},
schema: "./src/schema",
out: "./src/migrations",
dialect: "turso",
dbCredentials: {
url: process.env.DATABASE_URL || "",
},
});

View File

@@ -1,32 +1,32 @@
{
"name": "@kk/db",
"type": "module",
"exports": {
".": {
"default": "./src/index.ts"
},
"./*": {
"default": "./src/*.ts"
}
},
"scripts": {
"db:local": "turso dev --db-file local.db",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:studio": "drizzle-kit studio",
"db:migrate": "drizzle-kit migrate"
},
"dependencies": {
"@kk/env": "workspace:*",
"@libsql/client": "catalog:",
"dotenv": "catalog:",
"drizzle-orm": "^0.45.1",
"libsql": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"drizzle-kit": "^0.31.8",
"typescript": "catalog:"
}
"name": "@kk/db",
"type": "module",
"exports": {
".": {
"default": "./src/index.ts"
},
"./*": {
"default": "./src/*.ts"
}
},
"scripts": {
"db:local": "turso dev --db-file local.db",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:studio": "drizzle-kit studio",
"db:migrate": "drizzle-kit migrate"
},
"dependencies": {
"@kk/env": "workspace:*",
"@libsql/client": "catalog:",
"dotenv": "catalog:",
"drizzle-orm": "^0.45.1",
"libsql": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"drizzle-kit": "^0.31.8",
"typescript": "catalog:"
}
}

View File

@@ -5,7 +5,7 @@ import { drizzle } from "drizzle-orm/libsql";
import * as schema from "./schema";
const client = createClient({
url: env.DATABASE_URL,
url: env.DATABASE_URL,
});
export const db = drizzle({ client, schema });

View File

@@ -1,105 +1,107 @@
import { relations, sql } from "drizzle-orm";
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("email_verified", { mode: "boolean" }).default(false).notNull(),
image: text("image"),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("email_verified", { mode: "boolean" })
.default(false)
.notNull(),
image: text("image"),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const session = sqliteTable(
"session",
{
id: text("id").primaryKey(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
token: text("token").notNull().unique(),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
(table) => [index("session_userId_idx").on(table.userId)],
"session",
{
id: text("id").primaryKey(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
token: text("token").notNull().unique(),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
(table) => [index("session_userId_idx").on(table.userId)],
);
export const account = sqliteTable(
"account",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: integer("access_token_expires_at", {
mode: "timestamp_ms",
}),
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
mode: "timestamp_ms",
}),
scope: text("scope"),
password: text("password"),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("account_userId_idx").on(table.userId)],
"account",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: integer("access_token_expires_at", {
mode: "timestamp_ms",
}),
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
mode: "timestamp_ms",
}),
scope: text("scope"),
password: text("password"),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("account_userId_idx").on(table.userId)],
);
export const verification = sqliteTable(
"verification",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("verification_identifier_idx").on(table.identifier)],
"verification",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("verification_identifier_idx").on(table.identifier)],
);
export const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account),
sessions: many(session),
accounts: many(account),
}));
export const sessionRelations = relations(session, ({ one }) => ({
user: one(user, {
fields: [session.userId],
references: [user.id],
}),
user: one(user, {
fields: [session.userId],
references: [user.id],
}),
}));
export const accountRelations = relations(account, ({ one }) => ({
user: one(user, {
fields: [account.userId],
references: [user.id],
}),
user: one(user, {
fields: [account.userId],
references: [user.id],
}),
}));

View File

@@ -1,2 +1 @@
export * from "./auth";
export {};

View File

@@ -1,10 +1,10 @@
{
"extends": "@kk/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"composite": true
}
"extends": "@kk/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"composite": true
}
}

View File

@@ -1,21 +1,21 @@
{
"name": "@kk/env",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
"./server": "./src/server.ts",
"./web": "./src/web.ts"
},
"dependencies": {
"@t3-oss/env-core": "^0.13.1",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"@kk/infra": "workspace:*",
"@types/node": "catalog:",
"typescript": "catalog:"
}
"name": "@kk/env",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
"./server": "./src/server.ts",
"./web": "./src/web.ts"
},
"dependencies": {
"@t3-oss/env-core": "^0.13.1",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"@kk/infra": "workspace:*",
"@types/node": "catalog:",
"typescript": "catalog:"
}
}

View File

@@ -3,13 +3,15 @@ import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().min(1),
BETTER_AUTH_SECRET: z.string().min(32),
BETTER_AUTH_URL: z.url(),
CORS_ORIGIN: z.url(),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
server: {
DATABASE_URL: z.string().min(1),
BETTER_AUTH_SECRET: z.string().min(32),
BETTER_AUTH_URL: z.url(),
CORS_ORIGIN: z.url(),
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
});

View File

@@ -1,9 +1,9 @@
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
clientPrefix: "VITE_",
client: {},
runtimeEnv: (import.meta as any).env,
emptyStringAsUndefined: true,
clientPrefix: "VITE_",
client: {},
// biome-ignore lint/suspicious/noExplicitAny: Vite's import.meta.env doesn't have proper types
runtimeEnv: (import.meta as any).env,
emptyStringAsUndefined: true,
});

View File

@@ -1,3 +1,3 @@
{
"extends": "@kk/config/tsconfig.base.json"
"extends": "@kk/config/tsconfig.base.json"
}

View File

@@ -7,14 +7,23 @@ config({ path: "../../apps/web/.env" });
const app = await alchemy("kk");
// Helper function to get required env var
function getEnvVar(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
export const web = await TanStackStart("web", {
cwd: "../../apps/web",
bindings: {
DATABASE_URL: alchemy.secret.env.DATABASE_URL!,
CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,
BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,
BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,
},
cwd: "../../apps/web",
bindings: {
DATABASE_URL: getEnvVar("DATABASE_URL"),
CORS_ORIGIN: getEnvVar("CORS_ORIGIN"),
BETTER_AUTH_SECRET: getEnvVar("BETTER_AUTH_SECRET"),
BETTER_AUTH_URL: getEnvVar("BETTER_AUTH_URL"),
},
});
console.log(`Web -> ${web.url}`);

View File

@@ -1,19 +1,19 @@
{
"name": "@kk/infra",
"private": true,
"type": "module",
"scripts": {
"dev": "alchemy dev",
"deploy": "alchemy deploy",
"destroy": "alchemy destroy"
},
"dependencies": {
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"alchemy": "catalog:",
"typescript": "catalog:"
}
"name": "@kk/infra",
"private": true,
"type": "module",
"scripts": {
"dev": "alchemy dev",
"deploy": "alchemy deploy",
"destroy": "alchemy destroy"
},
"dependencies": {
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@kk/config": "workspace:*",
"alchemy": "catalog:",
"typescript": "catalog:"
}
}