initial commit
This commit is contained in:
34
packages/api/.gitignore
vendored
Normal file
34
packages/api/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
28
packages/api/package.json
Normal file
28
packages/api/package.json
Normal file
@@ -0,0 +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:"
|
||||
}
|
||||
}
|
||||
12
packages/api/src/context.ts
Normal file
12
packages/api/src/context.ts
Normal file
@@ -0,0 +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,
|
||||
};
|
||||
}
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||
20
packages/api/src/index.ts
Normal file
20
packages/api/src/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ORPCError, os } from "@orpc/server";
|
||||
|
||||
import type { Context } from "./context";
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export const protectedProcedure = publicProcedure.use(requireAuth);
|
||||
17
packages/api/src/routers/index.ts
Normal file
17
packages/api/src/routers/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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,
|
||||
};
|
||||
}),
|
||||
};
|
||||
export type AppRouter = typeof appRouter;
|
||||
export type AppRouterClient = RouterClient<typeof appRouter>;
|
||||
10
packages/api/tsconfig.json
Normal file
10
packages/api/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@kk/config/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
34
packages/auth/.gitignore
vendored
Normal file
34
packages/auth/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
24
packages/auth/package.json
Normal file
24
packages/auth/package.json
Normal file
@@ -0,0 +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:"
|
||||
}
|
||||
}
|
||||
19
packages/auth/src/index.ts
Normal file
19
packages/auth/src/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { db } from "@kk/db";
|
||||
import * as schema from "@kk/db/schema/auth";
|
||||
import { env } from "@kk/env/server";
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "sqlite",
|
||||
|
||||
schema: schema,
|
||||
}),
|
||||
trustedOrigins: [env.CORS_ORIGIN],
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [tanstackStartCookies()],
|
||||
});
|
||||
10
packages/auth/tsconfig.json
Normal file
10
packages/auth/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@kk/config/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
5
packages/config/package.json
Normal file
5
packages/config/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@kk/config",
|
||||
"version": "0.0.0",
|
||||
"private": true
|
||||
}
|
||||
22
packages/config/tsconfig.base.json
Normal file
22
packages/config/tsconfig.base.json
Normal file
@@ -0,0 +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"]
|
||||
}
|
||||
}
|
||||
35
packages/db/.gitignore
vendored
Normal file
35
packages/db/.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
/prisma/generated
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
packages/db/drizzle.config.ts
Normal file
15
packages/db/drizzle.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import dotenv from "dotenv";
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
dotenv.config({
|
||||
path: "../../apps/web/.env",
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/schema",
|
||||
out: "./src/migrations",
|
||||
dialect: "turso",
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL || "",
|
||||
},
|
||||
});
|
||||
BIN
packages/db/local.db
Normal file
BIN
packages/db/local.db
Normal file
Binary file not shown.
BIN
packages/db/local.db-shm
Normal file
BIN
packages/db/local.db-shm
Normal file
Binary file not shown.
0
packages/db/local.db-wal
Normal file
0
packages/db/local.db-wal
Normal file
32
packages/db/package.json
Normal file
32
packages/db/package.json
Normal file
@@ -0,0 +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:"
|
||||
}
|
||||
}
|
||||
11
packages/db/src/index.ts
Normal file
11
packages/db/src/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { env } from "@kk/env/server";
|
||||
import { createClient } from "@libsql/client";
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
|
||||
import * as schema from "./schema";
|
||||
|
||||
const client = createClient({
|
||||
url: env.DATABASE_URL,
|
||||
});
|
||||
|
||||
export const db = drizzle({ client, schema });
|
||||
105
packages/db/src/schema/auth.ts
Normal file
105
packages/db/src/schema/auth.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { sqliteTable, text, integer, index } 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(),
|
||||
});
|
||||
|
||||
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)],
|
||||
);
|
||||
|
||||
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)],
|
||||
);
|
||||
|
||||
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)],
|
||||
);
|
||||
|
||||
export const userRelations = relations(user, ({ many }) => ({
|
||||
sessions: many(session),
|
||||
accounts: many(account),
|
||||
}));
|
||||
|
||||
export const sessionRelations = relations(session, ({ one }) => ({
|
||||
user: one(user, {
|
||||
fields: [session.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const accountRelations = relations(account, ({ one }) => ({
|
||||
user: one(user, {
|
||||
fields: [account.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
}));
|
||||
2
packages/db/src/schema/index.ts
Normal file
2
packages/db/src/schema/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./auth";
|
||||
export {};
|
||||
10
packages/db/tsconfig.json
Normal file
10
packages/db/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@kk/config/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
21
packages/env/package.json
vendored
Normal file
21
packages/env/package.json
vendored
Normal file
@@ -0,0 +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:"
|
||||
}
|
||||
}
|
||||
15
packages/env/src/server.ts
vendored
Normal file
15
packages/env/src/server.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import "dotenv/config";
|
||||
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,
|
||||
});
|
||||
9
packages/env/src/web.ts
vendored
Normal file
9
packages/env/src/web.ts
vendored
Normal file
@@ -0,0 +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,
|
||||
});
|
||||
3
packages/env/tsconfig.json
vendored
Normal file
3
packages/env/tsconfig.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@kk/config/tsconfig.base.json"
|
||||
}
|
||||
22
packages/infra/alchemy.run.ts
Normal file
22
packages/infra/alchemy.run.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import alchemy from "alchemy";
|
||||
import { TanStackStart } from "alchemy/cloudflare";
|
||||
import { config } from "dotenv";
|
||||
|
||||
config({ path: "./.env" });
|
||||
config({ path: "../../apps/web/.env" });
|
||||
|
||||
const app = await alchemy("kk");
|
||||
|
||||
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!,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Web -> ${web.url}`);
|
||||
|
||||
await app.finalize();
|
||||
19
packages/infra/package.json
Normal file
19
packages/infra/package.json
Normal file
@@ -0,0 +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:"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user