diff --git a/package.json b/package.json index 5ba2ff3..4c046b9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@tailwindcss/postcss": "^4.1.18", "@tanstack/react-query": "^5.90.12", "@trpc/client": "^11.7.2", - "@trpc/next": "^11.7.2", "@trpc/react-query": "^11.7.2", "@trpc/server": "^11.7.2", "@turbo/gen": "^2.1.3", @@ -40,20 +39,21 @@ "typescript": "^5.6.3", "zod": "^3.23.8" }, + "dependencies": { + "@tanstack/react-router": "^1.141.2", + "autoprefixer": "^10.4.22", + "chart.js": "^4.5.1", + "postcss": "^8.5.6" + }, "prettier": "@query/prettier-config", "pnpm": { "overrides": { + "next": "15.5.9", "react": "^18.3.1", "react-dom": "^18.3.1", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", "@types/node": "^22.10.1" } - }, - "dependencies": { - "@tanstack/react-router": "^1.141.2", - "autoprefixer": "^10.4.22", - "chart.js": "^4.5.1", - "postcss": "^8.5.6" } -} \ No newline at end of file +} diff --git a/packages/api/package.json b/packages/api/package.json index 3a2fed5..a819735 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -2,33 +2,25 @@ "name": "@query/api", "version": "0.0.0", "private": true, - "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", "exports": { - ".": "./src/index.ts", - "./client": "./src/client.ts", - "./trpc-server": "./src/trpc-server.ts", - "./server": "./src/index.ts", - "./context": "./src/context.ts", - "./middleware": "./src/middleware.ts", - "./trpc": "./src/trpc.ts" + ".": "./src/index.ts" }, "scripts": { "lint": "eslint .", - "typecheck": "tsc --noEmit" + "type-check": "tsc --noEmit" }, "dependencies": { - "@query/auth": "workspace:*", + "@trpc/server": "^11.0.0-rc.600", "@query/db": "workspace:*", - "@trpc/server": "^11.7.2", - "@trpc/client": "^11.7.2", - "@trpc/react-query": "^11.7.2", - "@trpc/next": "^11.7.2", - "@tanstack/react-query": "^5.90.12", - "zod": "^3.23.8", - "superjson": "^2.2.6" + "@query/auth": "workspace:*", + "superjson": "^2.2.1", + "zod": "^3.22.4" }, "devDependencies": { "@query/tsconfig": "workspace:*", - "typescript": "^5.6.3" + "@types/node": "^20", + "typescript": "^5" } -} +} \ No newline at end of file diff --git a/packages/api/src/context.ts b/packages/api/src/context.ts new file mode 100644 index 0000000..cc0626e --- /dev/null +++ b/packages/api/src/context.ts @@ -0,0 +1,15 @@ +import { auth } from "@query/auth"; +import { db } from "@query/db"; +import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"; + +export async function createContext(opts?: FetchCreateContextFnOptions) { + const session = await auth(); + + return { + db, + session, + userId: session?.user?.id, + }; +} + +export type Context = Awaited>; \ No newline at end of file diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index cf9a5ab..530296e 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,4 +1,3 @@ -// packages/api/src/index.ts -export { appRouter, createContext } from './root'; -export type { AppRouter, Context } from './root'; -export { trpc, createTRPCRouter, publicProcedure } from './trpc'; \ No newline at end of file +export { appRouter, type AppRouter } from "./root"; +export { createContext, type Context } from "./context"; +export { createTRPCRouter, publicProcedure, protectedProcedure } from "./trpc"; \ No newline at end of file diff --git a/packages/api/src/root.ts b/packages/api/src/root.ts index 7e13f9e..4c07937 100644 --- a/packages/api/src/root.ts +++ b/packages/api/src/root.ts @@ -1,18 +1,10 @@ -import { createTRPCRouter } from './trpc'; -import { helloRouter } from './routers/hello'; +import { createTRPCRouter } from "./trpc"; +import { helloRouter } from "./routers/hello"; +import { userRouter } from "./routers/user"; -// Context type -export type Context = {}; - -// Context creator -export const createContext = async (): Promise => { - return {}; -}; - -// Root app router export const appRouter = createTRPCRouter({ hello: helloRouter, + user: userRouter, }); -// Export API type export type AppRouter = typeof appRouter; \ No newline at end of file diff --git a/packages/api/src/routers/hello.ts b/packages/api/src/routers/hello.ts index a88dfce..095b3e1 100644 --- a/packages/api/src/routers/hello.ts +++ b/packages/api/src/routers/hello.ts @@ -1,7 +1,46 @@ -import { createTRPCRouter, publicProcedure } from '../trpc'; +import { z } from "zod"; +import { createTRPCRouter, publicProcedure, protectedProcedure } from "../trpc"; export const helloRouter = createTRPCRouter({ + // Public endpoint - anyone can call sayHello: publicProcedure.mutation(() => { - return { message: 'hello is this thing on hellooooo' }; + return { + message: "You should sign in 😁", + timestamp: new Date().toISOString(), + }; }), + + // Public endpoint with input + greetPublic: publicProcedure + .input(z.object({ name: z.string().min(1) })) + .query(({ input }) => { + return { + message: `Hello ${input.name}! Welcome to our app! 🎉`, + }; + }), + + // Protected endpoint - only authenticated users + sayHelloAuth: protectedProcedure.mutation(({ ctx }) => { + // ctx.session.user is guaranteed to exist here due to middleware + const user = ctx.session.user; + + return { + message: `Hello ${user.name || user.email}! 🎉`, + user: { + id: user.id, + email: user.email, + name: user.name, + }, + }; + }), + + // Protected with input + greet: protectedProcedure + .input(z.object({ name: z.string().min(1) })) + .mutation(({ ctx, input }) => { + return { + message: `Hello ${input.name}, from ${ctx.session.user.email}!`, + userId: ctx.userId, + }; + }), }); \ No newline at end of file diff --git a/packages/api/src/routers/user.ts b/packages/api/src/routers/user.ts new file mode 100644 index 0000000..cd08d1e --- /dev/null +++ b/packages/api/src/routers/user.ts @@ -0,0 +1,117 @@ +import { z } from "zod"; +import { TRPCError } from "@trpc/server"; +import { createTRPCRouter, publicProcedure, protectedProcedure } from "../trpc"; +import { users } from "@query/db"; +import { eq } from "@query/db"; + +export const userRouter = createTRPCRouter({ + // Get current authenticated user (protected) + me: protectedProcedure.query(async ({ ctx }) => { + // ctx.userId is guaranteed to exist in protected procedures + const user = await ctx.db.query.users.findFirst({ + where: eq(users.id, ctx.userId!), + }); + + if (!user) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found", + }); + } + + return { + id: user.id, + email: user.email, + name: user.name, + image: user.image, + emailVerified: user.emailVerified, + }; + }), + + // Get all users (public, for demo - in production you'd protect this) + list: publicProcedure.query(async ({ ctx }) => { + const allUsers = await ctx.db.query.users.findMany({ + columns: { + id: true, + email: true, + name: true, + image: true, + }, + }); + + return allUsers; + }), + + // Get user by ID (public) + getById: publicProcedure + .input(z.object({ id: z.string() })) + .query(async ({ ctx, input }) => { + const user = await ctx.db.query.users.findFirst({ + where: eq(users.id, input.id), + columns: { + id: true, + email: true, + name: true, + image: true, + }, + }); + + if (!user) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found", + }); + } + + return user; + }), + + // Update user profile (protected) + updateProfile: protectedProcedure + .input( + z.object({ + name: z.string().min(1).max(100).optional(), + image: z.string().url().optional(), + }) + ) + .mutation(async ({ ctx, input }) => { + const [updatedUser] = await ctx.db + .update(users) + .set({ + name: input.name, + image: input.image, + }) + .where(eq(users.id, ctx.userId!)) + .returning(); + + return { + success: true, + user: updatedUser, + }; + }), + + // Delete own account (protected) + deleteAccount: protectedProcedure.mutation(async ({ ctx }) => { + await ctx.db.delete(users).where(eq(users.id, ctx.userId!)); + + return { + success: true, + message: "Account deleted successfully", + }; + }), + + // Get user stats (protected) + stats: protectedProcedure.query(async ({ ctx }) => { + const user = await ctx.db.query.users.findFirst({ + where: eq(users.id, ctx.userId!), + }); + + const totalUsers = await ctx.db.query.users.findMany(); + + return { + accountCreated: user?.emailVerified || new Date(), + totalUsers: totalUsers.length, + userId: ctx.userId, + }; + }), +}); \ No newline at end of file diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index 46f9dc2..7effec5 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -1,12 +1,43 @@ -// packages/api/src/trpc.ts -import { initTRPC } from '@trpc/server'; -import { Context } from './root'; +import { initTRPC, TRPCError } from "@trpc/server"; +import superjson from "superjson"; +import { ZodError } from "zod"; +import type { Context } from "./context"; -export const t = initTRPC.context().create(); +const t = initTRPC.context().create({ + transformer: superjson, + errorFormatter({ shape, error }) { + return { + ...shape, + data: { + ...shape.data, + zodError: + error.cause instanceof ZodError ? error.cause.flatten() : null, + }, + }; + }, +}); +// Base router and procedure export const createTRPCRouter = t.router; - export const publicProcedure = t.procedure; -// Export trpc instance for client usage -export const trpc = t; \ No newline at end of file +// Middleware to check if user is authenticated +const isAuthed = t.middleware(({ ctx, next }) => { + if (!ctx.session?.user || !ctx.userId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You must be logged in to access this resource", + }); + } + + return next({ + ctx: { + ...ctx, + session: { ...ctx.session, user: ctx.session.user }, + userId: ctx.userId, // Now guaranteed to be string, not string | undefined + }, + }); +}); + +// Protected procedure - requires authentication +export const protectedProcedure = t.procedure.use(isAuthed); \ No newline at end of file diff --git a/packages/auth/package.json b/packages/auth/package.json index 9f50c0b..d86e191 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -2,19 +2,27 @@ "name": "@query/auth", "version": "0.0.0", "private": true, - "type": "module", - "main": "src/index.ts", + "main": "./src/index.ts", + "types": "./src/index.ts", "exports": { - ".": "./src/index.ts", - "./hooks": "./src/hooks.ts" + ".": "./src/index.ts" }, - "devDependencies": { - "typescript": "^5.6.3" + "scripts": { + "lint": "eslint .", + "type-check": "tsc --noEmit" }, "dependencies": { - "uuid": "^9.0.0", - "zod": "^3.23.8", + "next-auth": "^5.0.0-beta.25", + "@auth/drizzle-adapter": "^1.7.1", "@query/db": "workspace:*", - "@tanstack/react-query": "^5.90.12" + "drizzle-orm": "^0.36.4" + }, + "peerDependencies": { + "next": ">=15.0.0" + }, + "devDependencies": { + "@query/tsconfig": "workspace:*", + "@types/node": "^20", + "typescript": "^5" } } diff --git a/packages/auth/src/adapter.ts b/packages/auth/src/adapter.ts new file mode 100644 index 0000000..f8b0a81 --- /dev/null +++ b/packages/auth/src/adapter.ts @@ -0,0 +1,9 @@ +import { DrizzleAdapter } from "@auth/drizzle-adapter"; +import { db, users, accounts, sessions, verificationTokens } from "@query/db"; + +export const adapter = DrizzleAdapter(db, { + usersTable: users, + accountsTable: accounts, + sessionsTable: sessions, + verificationTokensTable: verificationTokens, +}); \ No newline at end of file diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts new file mode 100644 index 0000000..cbe57dd --- /dev/null +++ b/packages/auth/src/auth.ts @@ -0,0 +1,8 @@ +import NextAuth from "next-auth"; +import { authConfig } from "./config"; +import { adapter } from "./adapter"; + +export const { handlers, auth, signIn, signOut } = NextAuth({ + ...authConfig, + adapter, +}); \ No newline at end of file diff --git a/packages/auth/src/config.ts b/packages/auth/src/config.ts new file mode 100644 index 0000000..6a09bc2 --- /dev/null +++ b/packages/auth/src/config.ts @@ -0,0 +1,51 @@ +import type { NextAuthConfig } from "next-auth"; +import GoogleProvider from "next-auth/providers/google"; + +export const authConfig: NextAuthConfig = { + providers: [ + GoogleProvider({ + clientId: process.env.GOOGLE_CLIENT_ID!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + authorization: { + params: { + prompt: "consent", + access_type: "offline", + response_type: "code", + }, + }, + }), + ], + pages: { + signIn: "/", + error: "/auth/error", + }, + callbacks: { + async session({ session, token }) { + if (token?.sub && session.user) { + session.user.id = token.sub; + } + return session; + }, + async jwt({ token, user }) { + if (user) { + token.sub = user.id; + } + return token; + }, + async redirect({ url, baseUrl }) { + if (url === baseUrl || url === `${baseUrl}/`) { + return `${baseUrl}/dashboard`; + } + + if (url.startsWith(baseUrl)) { + return url; + } + return baseUrl; + }, + }, + session: { + strategy: "jwt", // Changed from "database" to "jwt" for better compatibility + maxAge: 30 * 24 * 60 * 60, // 30 days + }, + debug: process.env.NODE_ENV === "development", +}; \ No newline at end of file diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index 022e874..4d21925 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -1,120 +1,4 @@ -import { v4 as uuidv4 } from 'uuid'; - -let inMemoryUsers: Record = {}; -let inMemorySessions: Record = {}; - -async function tryDb() { - try { - // dynamic import so packages/db doesn't need to resolve at build time - // (allows dev without Postgres) - // eslint-disable-next-line @typescript-eslint/no-var-requires - const mod = await import('@query/db'); - // also import schema - const schema = await import('@query/db/schema'); - return { db: mod.db, users: schema.users, sessions: schema.sessions }; - } catch (err) { - return null; - } -} - -export async function upsertUserFromGoogle(profile: { email: string; name?: string; image?: string; email_verified?: string | null }) { - const dbInfo = await tryDb(); - if (!dbInfo) { - // fallback to in-memory - let u = Object.values(inMemoryUsers).find((x: any) => x.email === profile.email); - if (u) { - u = { ...u, name: profile.name ?? u.name, image: profile.image ?? u.image }; - inMemoryUsers[u.id] = u; - return u; - } - const id = uuidv4(); - const newUser = { id, name: profile.name, email: profile.email, image: profile.image }; - inMemoryUsers[id] = newUser; - return newUser; - } - - const { db, users } = dbInfo; - const existing = await db.select().from(users).where(users.email.eq(profile.email)).then(r => r[0]); - if (existing) { - await db.update(users).set({ name: profile.name ?? existing.name, image: profile.image ?? existing.image }).where(users.id.eq(existing.id)); - return { ...existing, name: profile.name ?? existing.name, image: profile.image ?? existing.image }; - } - - const res = await db.insert(users).values({ id: uuidv4(), name: profile.name, email: profile.email, image: profile.image }).returning(); - return res[0]; -} - -export async function createSession(userId: string) { - const dbInfo = await tryDb(); - const token = uuidv4(); - const expires = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30); // 30 days - if (!dbInfo) { - const id = uuidv4(); - inMemorySessions[token] = { id, session_token: token, user_id: userId, expires }; - return { token, expires }; - } - const { db, sessions } = dbInfo; - await db.insert(sessions).values({ id: uuidv4(), session_token: token, user_id: userId, expires }).run(); - return { token, expires }; -} - -export async function getSessionByToken(token: string) { - const dbInfo = await tryDb(); - if (!dbInfo) { - const s = inMemorySessions[token]; - if (!s) return null; - const u = inMemoryUsers[s.user_id]; - return { session: s, user: u }; - } - const { db, sessions, users } = dbInfo; - const s = await db.select().from(sessions).where(sessions.session_token.eq(token)).then(r => r[0]); - if (!s) return null; - const u = await db.select().from(users).where(users.id.eq(s.user_id)).then(r => r[0]); - return { session: s, user: u }; -} - -export async function deleteSession(token: string) { - const dbInfo = await tryDb(); - if (!dbInfo) { - delete inMemorySessions[token]; - return; - } - const { db, sessions } = dbInfo; - await db.delete(sessions).where(sessions.session_token.eq(token)).run(); -} - -export function getTokenFromRequest(req: Request) { - const cookies = req.headers.get('cookie') ?? ''; - const match = /(?:^|; )query_session=([^;]+)/.exec(cookies); - return match?.[1] ?? null; -} - -export async function getSessionFromRequest(req: Request) { - const token = getTokenFromRequest(req); - if (!token) return null; - return getSessionByToken(token); -} - -export function setSessionCookie(res: any, token: string) { - try { - res.cookies.set('query_session', token, { - httpOnly: true, - path: '/', - maxAge: 60 * 60 * 24 * 30, - sameSite: 'lax', - secure: process.env.NODE_ENV === 'production', - }); - } catch (err) { - const prev = res.headers?.get?.('set-cookie') ?? ''; - res.headers?.set?.('set-cookie', `${prev}; query_session=${token}; Path=/; HttpOnly`); - } -} - -export function clearSessionCookie(res: any) { - try { - res.cookies.set('query_session', '', { path: '/', maxAge: 0 }); - } catch (err) { - res.headers?.set?.('set-cookie', 'query_session=; Path=/; Max-Age=0'); - } -} - +export { auth, signIn, signOut, handlers } from "./auth"; +export { authConfig } from "./config"; +export { adapter } from "./adapter"; +export { getSession, requireAuth, getCurrentUserId } from "./utils"; \ No newline at end of file diff --git a/packages/auth/src/utils.ts b/packages/auth/src/utils.ts new file mode 100644 index 0000000..a193eac --- /dev/null +++ b/packages/auth/src/utils.ts @@ -0,0 +1,29 @@ +import { auth } from "./auth"; + +/** + * Get the current session server-side + */ +export async function getSession() { + return await auth(); +} + +/** + * Require authentication - throws if not authenticated + */ +export async function requireAuth() { + const session = await getSession(); + if (!session?.user) { + throw new Error("Unauthorized"); + } + // TypeScript now knows session.user exists + return session; +} + +/** + * Get current user ID + */ +export async function getCurrentUserId() { + const session = await requireAuth(); + // Using non-null assertion since requireAuth guarantees session.user exists + return session.user!.id; +} diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json new file mode 100644 index 0000000..ff8a2a1 --- /dev/null +++ b/packages/auth/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@query/tsconfig/base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "moduleResolution": "bundler", + "lib": ["ES2015", "DOM", "DOM.Iterable"] + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts index 22e6524..1a8dafd 100644 --- a/packages/db/drizzle.config.ts +++ b/packages/db/drizzle.config.ts @@ -1,7 +1,9 @@ import { defineConfig } from "drizzle-kit"; import * as dotenv from "dotenv"; +import path from "path"; -dotenv.config(); +// Load .env from root (two levels up) +dotenv.config({ path: path.resolve(__dirname, "../../.env") }); if (!process.env.DATABASE_URL) { throw new Error("DATABASE_URL is not defined in .env file"); diff --git a/packages/db/index.ts b/packages/db/index.ts deleted file mode 100644 index 765fc67..0000000 --- a/packages/db/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Pool } from "pg"; -import { drizzle } from "drizzle-orm/node-postgres"; -import { env } from "./src/env"; - -const pool = new Pool({ - connectionString: env.DATABASE_URL, -}); - -export const db = drizzle(pool); diff --git a/packages/db/package.json b/packages/db/package.json index c3384f0..3a40f00 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -2,25 +2,27 @@ "name": "@query/db", "version": "0.0.0", "private": true, - "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", "exports": { ".": "./src/index.ts" }, + "scripts": { + "migrate:push": "drizzle-kit push", + "migrate:generate": "drizzle-kit generate", + "studio": "drizzle-kit studio" + }, "dependencies": { "@t3-oss/env-nextjs": "^0.13.10", - "drizzle-orm": "^0.33.0", - "postgres": "^3.4.4", + "drizzle-orm": "^0.36.4", + "pg": "^8.11.3", + "postgres": "^3.4.3", "zod": "^3.25.76" }, "devDependencies": { - "@types/pg": "^8.16.0", - "dotenv": "^17.2.3", - "drizzle-kit": "^0.24.0", - "pg": "^8.16.3", - "typescript": "^5.2.2" - }, - "scripts": { - "migrate:push": "drizzle-kit push", - "migrate:dev": "drizzle-kit dev" + "@query/tsconfig": "workspace:*", + "@types/pg": "^8.10.9", + "drizzle-kit": "^0.28.1", + "typescript": "^5" } -} +} \ No newline at end of file diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts new file mode 100644 index 0000000..d7871ff --- /dev/null +++ b/packages/db/src/client.ts @@ -0,0 +1,13 @@ +import { drizzle } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; +import * as schema from "./schemas"; +import * as dotenv from "dotenv"; +import path from "path"; + +dotenv.config({ path: path.resolve(__dirname, "../../../.env") }); + +const pool = new Pool({ + connectionString: process.env.DATABASE_URL, +}); + +export const db = drizzle(pool, { schema }); \ No newline at end of file diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts new file mode 100644 index 0000000..3cd73d6 --- /dev/null +++ b/packages/db/src/index.ts @@ -0,0 +1,4 @@ +export * from "drizzle-orm"; +export { db } from "./client"; +export * from "./schemas"; +export { users, accounts, sessions, verificationTokens } from "./schemas/auth"; \ No newline at end of file diff --git a/packages/db/src/schemas/auth.ts b/packages/db/src/schemas/auth.ts new file mode 100644 index 0000000..eb4b8a1 --- /dev/null +++ b/packages/db/src/schemas/auth.ts @@ -0,0 +1,54 @@ +import { pgTable, text, timestamp, primaryKey, integer } from "drizzle-orm/pg-core"; +import type { AdapterAccount } from "next-auth/adapters"; + +export const users = pgTable("user", { + id: text("id").notNull().primaryKey(), + name: text("name"), + email: text("email").notNull(), + emailVerified: timestamp("emailVerified", { mode: "date" }), + image: text("image"), +}); + +export const accounts = pgTable( + "account", + { + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + type: text("type").$type().notNull(), + provider: text("provider").notNull(), + providerAccountId: text("providerAccountId").notNull(), + refresh_token: text("refresh_token"), + access_token: text("access_token"), + expires_at: integer("expires_at"), + token_type: text("token_type"), + scope: text("scope"), + id_token: text("id_token"), + session_state: text("session_state"), + }, + (account) => ({ + compoundKey: primaryKey({ + columns: [account.provider, account.providerAccountId], + }), + }) +); + +export const sessions = pgTable("session", { + sessionToken: text("sessionToken").notNull().primaryKey(), + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + expires: timestamp("expires", { mode: "date" }).notNull(), +}); + +export const verificationTokens = pgTable( + "verificationToken", + { + identifier: text("identifier").notNull(), + token: text("token").notNull(), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (vt) => ({ + compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), + }) +); \ No newline at end of file diff --git a/packages/db/src/schemas/index.ts b/packages/db/src/schemas/index.ts new file mode 100644 index 0000000..fb50d0a --- /dev/null +++ b/packages/db/src/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./auth"; +export * from "./users"; \ No newline at end of file diff --git a/packages/db/src/schemas/users.ts b/packages/db/src/schemas/users.ts index 136bde5..a02f4a7 100644 --- a/packages/db/src/schemas/users.ts +++ b/packages/db/src/schemas/users.ts @@ -1,7 +1,15 @@ -import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core"; +import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; -export const users = pgTable("users", { - id: serial("id").primaryKey(), - email: text("email").notNull().unique(), +export const userProfiles = pgTable("user_profile", { + id: uuid("id").defaultRandom().primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + bio: text("bio"), + website: text("website"), + location: text("location"), createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").defaultNow().notNull(), }); + +import { users } from "./auth"; \ No newline at end of file diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json index fa77613..ff8a2a1 100644 --- a/packages/db/tsconfig.json +++ b/packages/db/tsconfig.json @@ -1,25 +1,13 @@ { - "extends": "@query/tsconfig/nextjs.json", + "extends": "@query/tsconfig/base.json", "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] }, - "jsx": "react-jsx", - "strict": true, - "esModuleInterop": true, - "moduleResolution": "bundler", // Changed from "node" - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true + "moduleResolution": "bundler", + "lib": ["ES2015", "DOM", "DOM.Iterable"] }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules", - ".next", - "dist" - ] + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85cde81..1be4263 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: + next: 15.5.9 react: ^18.3.1 react-dom: ^18.3.1 '@types/react': ^18.3.1 @@ -49,9 +50,6 @@ importers: '@trpc/client': specifier: ^11.7.2 version: 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) - '@trpc/next': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) '@trpc/react-query': specifier: ^11.7.2 version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -85,52 +83,52 @@ importers: '@query/db': specifier: workspace:* version: link:../db - '@tanstack/react-query': - specifier: ^5.90.12 - version: 5.90.12(react@18.3.1) - '@trpc/client': - specifier: ^11.7.2 - version: 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) - '@trpc/next': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) - '@trpc/react-query': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) '@trpc/server': - specifier: ^11.7.2 + specifier: ^11.0.0-rc.600 version: 11.7.2(typescript@5.9.3) superjson: - specifier: ^2.2.6 + specifier: ^2.2.1 version: 2.2.6 zod: - specifier: ^3.23.8 + specifier: ^3.22.4 version: 3.25.76 devDependencies: '@query/tsconfig': specifier: workspace:* version: link:../../tooling/typescript + '@types/node': + specifier: ^22.10.1 + version: 22.19.2 typescript: - specifier: ^5.6.3 + specifier: ^5 version: 5.9.3 packages/auth: dependencies: + '@auth/drizzle-adapter': + specifier: ^1.7.1 + version: 1.11.1 '@query/db': specifier: workspace:* version: link:../db - '@tanstack/react-query': - specifier: ^5.90.12 - version: 5.90.12(react@18.3.1) - uuid: - specifier: ^9.0.0 - version: 9.0.1 - zod: - specifier: ^3.23.8 - version: 3.25.76 + drizzle-orm: + specifier: ^0.36.4 + version: 0.36.4(@types/pg@8.16.0)(@types/react@18.3.27)(pg@8.16.3)(postgres@3.4.7)(react@18.3.1) + next: + specifier: 15.5.9 + version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + next-auth: + specifier: ^5.0.0-beta.25 + version: 5.0.0-beta.30(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react@18.3.1) devDependencies: + '@query/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript + '@types/node': + specifier: ^22.10.1 + version: 22.19.2 typescript: - specifier: ^5.6.3 + specifier: ^5 version: 5.9.3 packages/db: @@ -139,29 +137,29 @@ importers: specifier: ^0.13.10 version: 0.13.10(typescript@5.9.3)(zod@3.25.76) drizzle-orm: - specifier: ^0.33.0 - version: 0.33.0(@types/pg@8.16.0)(@types/react@18.3.27)(pg@8.16.3)(postgres@3.4.7)(react@18.3.1) + specifier: ^0.36.4 + version: 0.36.4(@types/pg@8.16.0)(@types/react@18.3.27)(pg@8.16.3)(postgres@3.4.7)(react@18.3.1) + pg: + specifier: ^8.11.3 + version: 8.16.3 postgres: - specifier: ^3.4.4 + specifier: ^3.4.3 version: 3.4.7 zod: specifier: ^3.25.76 version: 3.25.76 devDependencies: + '@query/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript '@types/pg': - specifier: ^8.16.0 + specifier: ^8.10.9 version: 8.16.0 - dotenv: - specifier: ^17.2.3 - version: 17.2.3 drizzle-kit: - specifier: ^0.24.0 - version: 0.24.2 - pg: - specifier: ^8.16.3 - version: 8.16.3 + specifier: ^0.28.1 + version: 0.28.1 typescript: - specifier: ^5.2.2 + specifier: ^5 version: 5.9.3 packages/ui: @@ -217,10 +215,10 @@ importers: version: 4.5.1 geist: specifier: ^1.5.1 - version: 1.5.1(next@16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0)) + version: 1.5.1(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0)) next: - specifier: ^16.0.8 - version: 16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + specifier: 15.5.9 + version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -303,9 +301,6 @@ importers: '@query/db': specifier: workspace:* version: link:../../packages/db - '@query/ui': - specifier: workspace:* - version: link:../../packages/ui '@tanstack/react-query': specifier: ^5.90.12 version: 5.90.12(react@18.3.1) @@ -314,7 +309,7 @@ importers: version: 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) '@trpc/next': specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) '@trpc/react-query': specifier: ^11.7.2 version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -322,8 +317,11 @@ importers: specifier: ^11.7.2 version: 11.7.2(typescript@5.9.3) next: - specifier: ^16.0.10 - version: 16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + specifier: 15.5.9 + version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + next-auth: + specifier: ^5.0.0-beta.25 + version: 5.0.0-beta.30(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -331,24 +329,12 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) superjson: - specifier: ^2.2.6 + specifier: ^2.2.1 version: 2.2.6 three: - specifier: ^0.182.0 - version: 0.182.0 + specifier: ^0.128.0 + version: 0.128.0 devDependencies: - '@query/eslint-config': - specifier: workspace:* - version: link:../../tooling/eslint - '@query/tailwind-config': - specifier: workspace:* - version: link:../../tooling/tailwind - '@query/tsconfig': - specifier: workspace:* - version: link:../../tooling/typescript - '@tailwindcss/postcss': - specifier: ^4.1.18 - version: 4.1.18 '@types/node': specifier: ^22.10.1 version: 22.19.2 @@ -359,19 +345,10 @@ importers: specifier: ^18.3.1 version: 18.3.7(@types/react@18.3.27) '@types/three': - specifier: ^0.182.0 - version: 0.182.0 - eslint: - specifier: ^9.33.0 - version: 9.39.1(jiti@2.6.1) - postcss: - specifier: ^8.5.3 - version: 8.5.6 - tailwindcss: - specifier: ^4.1.5 - version: 4.1.18 + specifier: ^0.128.0 + version: 0.128.0 typescript: - specifier: 5.9.3 + specifier: ^5.6.3 version: 5.9.3 tooling/eslint: @@ -448,8 +425,8 @@ importers: specifier: ^4.1.18 version: 4.1.18 next: - specifier: ^16.0.8 - version: 16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + specifier: 15.5.9 + version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) postcss: specifier: ^8.5.3 version: 8.5.6 @@ -478,6 +455,37 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@auth/core@0.41.0': + resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + '@auth/core@0.41.1': + resolution: {integrity: sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^7.0.7 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + '@auth/drizzle-adapter@1.11.1': + resolution: {integrity: sha512-cQTvDZqsyF7RPhDm/B6SvqdVP9EzQhy3oM4Muu7fjjmSYFLbSR203E6dH631ZHSKDn2b4WZkfMnjPDzRsPSAeA==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -523,9 +531,6 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@dimforge/rapier3d-compat@0.12.0': - resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} - '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -1069,56 +1074,56 @@ packages: '@next/bundle-analyzer@16.0.10': resolution: {integrity: sha512-AHA6ZomhQuRsJtkoRvsq+hIuwA6F26mQzQT8ICcc2dL3BvHRcWOA+EiFr+BgWFY++EE957xVDqMIJjLApyxnwA==} - '@next/env@16.0.10': - resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==} + '@next/env@15.5.9': + resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==} '@next/eslint-plugin-next@16.0.10': resolution: {integrity: sha512-b2NlWN70bbPLmfyoLvvidPKWENBYYIe017ZGUpElvQjDytCWgxPJx7L9juxHt0xHvNVA08ZHJdOyhGzon/KJuw==} - '@next/swc-darwin-arm64@16.0.10': - resolution: {integrity: sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==} + '@next/swc-darwin-arm64@15.5.7': + resolution: {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.10': - resolution: {integrity: sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==} + '@next/swc-darwin-x64@15.5.7': + resolution: {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.10': - resolution: {integrity: sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==} + '@next/swc-linux-arm64-gnu@15.5.7': + resolution: {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.0.10': - resolution: {integrity: sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==} + '@next/swc-linux-arm64-musl@15.5.7': + resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@16.0.10': - resolution: {integrity: sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==} + '@next/swc-linux-x64-gnu@15.5.7': + resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.0.10': - resolution: {integrity: sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==} + '@next/swc-linux-x64-musl@15.5.7': + resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@16.0.10': - resolution: {integrity: sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==} + '@next/swc-win32-arm64-msvc@15.5.7': + resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.10': - resolution: {integrity: sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==} + '@next/swc-win32-x64-msvc@15.5.7': + resolution: {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1135,6 +1140,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -1400,7 +1408,7 @@ packages: '@trpc/client': 11.7.2 '@trpc/react-query': 11.7.2 '@trpc/server': 11.7.2 - next: '*' + next: 15.5.9 react: ^18.3.1 react-dom: ^18.3.1 typescript: '>=5.7.2' @@ -1445,9 +1453,6 @@ packages: resolution: {integrity: sha512-RLOjHQIZeCGwVBCRr8pfyshUJn2wx8s5n1H0O9fhpsL6qaWKi9gKoUJMr8BMVJApJGrspJ/QkQzsL8gLicpZbQ==} hasBin: true - '@tweenjs/tween.js@23.1.3': - resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} - '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} @@ -1493,11 +1498,8 @@ packages: '@types/react@18.3.27': resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} - '@types/stats.js@0.17.4': - resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} - - '@types/three@0.182.0': - resolution: {integrity: sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==} + '@types/three@0.128.0': + resolution: {integrity: sha512-Jwq5XYUkzAcPTo34hlGAQGUyAI0b2F3aCCFWG/v7ZhJBEG5HGcusMSr70GhDlT8Gs0f02QnSPZ2RCA1MrCOa/w==} '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} @@ -1505,9 +1507,6 @@ packages: '@types/tinycolor2@1.4.6': resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} - '@types/webxr@0.5.24': - resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} - '@typescript-eslint/eslint-plugin@8.49.0': resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1567,9 +1566,6 @@ packages: resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@webgpu/types@0.1.68': - resolution: {integrity: sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA==} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1944,22 +1940,19 @@ packages: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} - engines: {node: '>=12'} - - drizzle-kit@0.24.2: - resolution: {integrity: sha512-nXOaTSFiuIaTMhS8WJC2d4EBeIcN9OSt2A2cyFbQYBAZbi7lRsVGJNqDpEwPqYfJz38yxbY/UtbvBBahBfnExQ==} + drizzle-kit@0.28.1: + resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==} hasBin: true - drizzle-orm@0.33.0: - resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} + drizzle-orm@0.36.4: + resolution: {integrity: sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' - '@neondatabase/serverless': '>=0.1' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 '@planetscale/database': '>=1' @@ -1973,7 +1966,7 @@ packages: '@xata.io/client': '*' better-sqlite3: '>=7' bun-types: '*' - expo-sqlite: '>=13.2.0' + expo-sqlite: '>=14.0.0' knex: '*' kysely: '*' mysql2: '>=2' @@ -1992,6 +1985,8 @@ packages: optional: true '@libsql/client': optional: true + '@libsql/client-wasm': + optional: true '@neondatabase/serverless': optional: true '@op-engineering/op-sqlite': @@ -2267,9 +2262,6 @@ packages: picomatch: optional: true - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -2320,7 +2312,7 @@ packages: geist@1.5.1: resolution: {integrity: sha512-mAHZxIsL2o3ZITFaBVFBnwyDOw+zNLYum6A6nIjpzCGIO8QtC3V76XF2RnZTyLx1wlDTmMDy8jg3Ib52MIjGvQ==} peerDependencies: - next: '>=13.2.0' + next: 15.5.9 generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} @@ -2662,6 +2654,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2834,9 +2829,6 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - meshoptimizer@0.22.0: - resolution: {integrity: sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2888,9 +2880,25 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - next@16.0.10: - resolution: {integrity: sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==} - engines: {node: '>=20.9.0'} + next-auth@5.0.0-beta.30: + resolution: {integrity: sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + next: 15.5.9 + nodemailer: ^7.0.7 + react: ^18.3.1 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + next@15.5.9: + resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -2930,6 +2938,9 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + oauth4webapi@3.8.3: + resolution: {integrity: sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3128,6 +3139,14 @@ packages: resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} engines: {node: '>=12'} + preact-render-to-string@6.5.11: + resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} + peerDependencies: + preact: '>=10' + + preact@10.24.3: + resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3536,8 +3555,8 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - three@0.182.0: - resolution: {integrity: sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==} + three@0.128.0: + resolution: {integrity: sha512-i0ap/E+OaSfzw7bD1TtYnPo3VEplkl70WX5fZqZnfZsE3k3aSFudqrrC9ldFZfYFkn1zwDmBcdGfiIm/hnbyZA==} through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -3707,10 +3726,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -3792,6 +3807,30 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@auth/core@0.41.0': + dependencies: + '@panva/hkdf': 1.2.1 + jose: 6.1.3 + oauth4webapi: 3.8.3 + preact: 10.24.3 + preact-render-to-string: 6.5.11(preact@10.24.3) + + '@auth/core@0.41.1': + dependencies: + '@panva/hkdf': 1.2.1 + jose: 6.1.3 + oauth4webapi: 3.8.3 + preact: 10.24.3 + preact-render-to-string: 6.5.11(preact@10.24.3) + + '@auth/drizzle-adapter@1.11.1': + dependencies: + '@auth/core': 0.41.1 + transitivePeerDependencies: + - '@simplewebauthn/browser' + - '@simplewebauthn/server' + - nodemailer + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -3847,8 +3886,6 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@dimforge/rapier3d-compat@0.12.0': {} - '@discoveryjs/json-ext@0.5.7': {} '@drizzle-team/brocli@0.10.2': {} @@ -4216,34 +4253,34 @@ snapshots: - bufferutil - utf-8-validate - '@next/env@16.0.10': {} + '@next/env@15.5.9': {} '@next/eslint-plugin-next@16.0.10': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.0.10': + '@next/swc-darwin-arm64@15.5.7': optional: true - '@next/swc-darwin-x64@16.0.10': + '@next/swc-darwin-x64@15.5.7': optional: true - '@next/swc-linux-arm64-gnu@16.0.10': + '@next/swc-linux-arm64-gnu@15.5.7': optional: true - '@next/swc-linux-arm64-musl@16.0.10': + '@next/swc-linux-arm64-musl@15.5.7': optional: true - '@next/swc-linux-x64-gnu@16.0.10': + '@next/swc-linux-x64-gnu@15.5.7': optional: true - '@next/swc-linux-x64-musl@16.0.10': + '@next/swc-linux-x64-musl@15.5.7': optional: true - '@next/swc-win32-arm64-msvc@16.0.10': + '@next/swc-win32-arm64-msvc@15.5.7': optional: true - '@next/swc-win32-x64-msvc@16.0.10': + '@next/swc-win32-x64-msvc@15.5.7': optional: true '@nodelib/fs.scandir@2.1.5': @@ -4258,6 +4295,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@panva/hkdf@1.2.1': {} + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -4463,11 +4502,11 @@ snapshots: '@trpc/server': 11.7.2(typescript@5.9.3) typescript: 5.9.3 - '@trpc/next@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': + '@trpc/next@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: '@trpc/client': 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) '@trpc/server': 11.7.2(typescript@5.9.3) - next: 16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + next: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) typescript: 5.9.3 @@ -4532,8 +4571,6 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@tweenjs/tween.js@23.1.3': {} - '@types/eslint@9.6.1': dependencies: '@types/estree': 1.0.8 @@ -4588,17 +4625,7 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 - '@types/stats.js@0.17.4': {} - - '@types/three@0.182.0': - dependencies: - '@dimforge/rapier3d-compat': 0.12.0 - '@tweenjs/tween.js': 23.1.3 - '@types/stats.js': 0.17.4 - '@types/webxr': 0.5.24 - '@webgpu/types': 0.1.68 - fflate: 0.8.2 - meshoptimizer: 0.22.0 + '@types/three@0.128.0': {} '@types/through@0.0.33': dependencies: @@ -4606,8 +4633,6 @@ snapshots: '@types/tinycolor2@1.4.6': {} - '@types/webxr@0.5.24': {} - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4699,8 +4724,6 @@ snapshots: '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 - '@webgpu/types@0.1.68': {} - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -5106,9 +5129,7 @@ snapshots: dotenv@16.0.3: {} - dotenv@17.2.3: {} - - drizzle-kit@0.24.2: + drizzle-kit@0.28.1: dependencies: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 @@ -5117,7 +5138,7 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.33.0(@types/pg@8.16.0)(@types/react@18.3.27)(pg@8.16.3)(postgres@3.4.7)(react@18.3.1): + drizzle-orm@0.36.4(@types/pg@8.16.0)(@types/react@18.3.27)(pg@8.16.3)(postgres@3.4.7)(react@18.3.1): optionalDependencies: '@types/pg': 8.16.0 '@types/react': 18.3.27 @@ -5533,8 +5554,6 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - fflate@0.8.2: {} - figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -5586,9 +5605,9 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.5.1(next@16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0)): + geist@1.5.1(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0)): dependencies: - next: 16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + next: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) generator-function@2.0.1: {} @@ -5963,6 +5982,8 @@ snapshots: jiti@2.6.1: {} + jose@6.1.3: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -6103,8 +6124,6 @@ snapshots: merge2@1.4.1: {} - meshoptimizer@0.22.0: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -6142,9 +6161,15 @@ snapshots: netmask@2.0.2: {} - next@16.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0): + next-auth@5.0.0-beta.30(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react@18.3.1): dependencies: - '@next/env': 16.0.10 + '@auth/core': 0.41.0 + next: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + react: 18.3.1 + + next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0): + dependencies: + '@next/env': 15.5.9 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001760 postcss: 8.4.31 @@ -6152,14 +6177,14 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.10 - '@next/swc-darwin-x64': 16.0.10 - '@next/swc-linux-arm64-gnu': 16.0.10 - '@next/swc-linux-arm64-musl': 16.0.10 - '@next/swc-linux-x64-gnu': 16.0.10 - '@next/swc-linux-x64-musl': 16.0.10 - '@next/swc-win32-arm64-msvc': 16.0.10 - '@next/swc-win32-x64-msvc': 16.0.10 + '@next/swc-darwin-arm64': 15.5.7 + '@next/swc-darwin-x64': 15.5.7 + '@next/swc-linux-arm64-gnu': 15.5.7 + '@next/swc-linux-arm64-musl': 15.5.7 + '@next/swc-linux-x64-gnu': 15.5.7 + '@next/swc-linux-x64-musl': 15.5.7 + '@next/swc-win32-arm64-msvc': 15.5.7 + '@next/swc-win32-x64-msvc': 15.5.7 sass: 1.96.0 sharp: 0.34.5 transitivePeerDependencies: @@ -6194,6 +6219,8 @@ snapshots: dependencies: path-key: 3.1.1 + oauth4webapi@3.8.3: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -6414,6 +6441,12 @@ snapshots: postgres@3.4.7: {} + preact-render-to-string@6.5.11(preact@10.24.3): + dependencies: + preact: 10.24.3 + + preact@10.24.3: {} + prelude-ls@1.2.1: {} prettier-plugin-tailwindcss@0.6.14(@ianvs/prettier-plugin-sort-imports@4.7.0(prettier@3.7.4))(prettier@3.7.4): @@ -6843,7 +6876,7 @@ snapshots: tapable@2.3.0: {} - three@0.182.0: {} + three@0.128.0: {} through@2.3.8: {} @@ -7020,8 +7053,6 @@ snapshots: util-deprecate@1.0.2: {} - uuid@9.0.1: {} - v8-compile-cache-lib@3.0.1: {} validate-npm-package-name@5.0.1: {} diff --git a/sites/portal/next.config.mjs b/sites/portal/next.config.mjs index b54289c..888e8fa 100644 --- a/sites/portal/next.config.mjs +++ b/sites/portal/next.config.mjs @@ -1,11 +1,14 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + /** @type {import('next').NextConfig} */ const nextConfig = { transpilePackages: ['@query/db', '@query/auth', '@query/api', '@query/ui'], - experimental: { - turbopack: { - root: '../../', - }, - }, + output: 'standalone', + outputFileTracingRoot: path.join(__dirname, '../../'), }; -export default nextConfig; +export default nextConfig; \ No newline at end of file diff --git a/sites/portal/package.json b/sites/portal/package.json index 8f9eb13..2d3a8f5 100644 --- a/sites/portal/package.json +++ b/sites/portal/package.json @@ -1,43 +1,38 @@ { "name": "portal", "version": "1.0.0", - "type": "module", "private": true, "scripts": { - "dev": "next dev --port 3002", + "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint --max-warnings 0", - "check-types": "tsc --noEmit" + "lint": "next lint" }, "dependencies": { + "next": "15.5.9", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@query/api": "workspace:*", "@query/auth": "workspace:*", "@query/db": "workspace:*", - "@query/ui": "workspace:*", + "@tanstack/react-query": "^5.90.12", + "@trpc/client": "^11.7.2", - "@trpc/next": "^11.7.2", "@trpc/react-query": "^11.7.2", "@trpc/server": "^11.7.2", - "next": "^16.0.10", - "react": "^19.2.1", - "react-dom": "^19.2.1", - "superjson": "^2.2.6", - "three": "^0.182.0" + "@trpc/next": "^11.7.2", + + "next-auth": "^5.0.0-beta.25", + "superjson": "^2.2.1", + "three": "^0.128.0" }, "devDependencies": { - "@query/eslint-config": "workspace:*", - "@query/tailwind-config": "workspace:*", - "@query/tsconfig": "workspace:*", - "@tailwindcss/postcss": "^4.1.18", - "@types/node": "^22.19.2", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.1.1", - "@types/three": "^0.182.0", - "eslint": "^9.33.0", - "postcss": "^8.5.3", - "tailwindcss": "^4.1.5", - "typescript": "5.9.3" + "@types/node": "^22.10.1", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", + "@types/three": "^0.128.0", + "typescript": "^5.6.3" } -} \ No newline at end of file +} diff --git a/sites/portal/src/app/api/auth/[...nextauth]/route.ts b/sites/portal/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..e560c50 --- /dev/null +++ b/sites/portal/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@query/auth"; + +export const { GET, POST } = handlers; \ No newline at end of file diff --git a/sites/portal/src/app/api/trpc/[trpc].ts b/sites/portal/src/app/api/trpc/[trpc].ts index 7d5bf7f..2abe53b 100644 --- a/sites/portal/src/app/api/trpc/[trpc].ts +++ b/sites/portal/src/app/api/trpc/[trpc].ts @@ -1,8 +1,19 @@ // sites/portal/pages/api/trpc/[trpc].ts import { createNextApiHandler } from '@trpc/server/adapters/next'; -import { appRouter, createContext } from '@query/api'; +import { appRouter } from '@query/api'; +import { auth } from '@query/auth'; +import { db } from '@query/db'; +import type { NextApiRequest, NextApiResponse } from 'next'; export default createNextApiHandler({ router: appRouter, - createContext, + createContext: async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) => { + const session = await auth(); + + return { + db, + session, + userId: session?.user?.id, + }; + }, }); \ No newline at end of file diff --git a/sites/portal/src/app/api/trpc/[trpc]/route.ts b/sites/portal/src/app/api/trpc/[trpc]/route.ts index cd83888..1b5e6a2 100644 --- a/sites/portal/src/app/api/trpc/[trpc]/route.ts +++ b/sites/portal/src/app/api/trpc/[trpc]/route.ts @@ -1,4 +1,4 @@ -// sites/portal/app/api/trpc/[trpc]/route.ts +// sites/portal/src/app/api/trpc/[trpc]/route.ts import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { appRouter, createContext } from '@query/api'; diff --git a/sites/portal/src/app/dashboard/page.tsx b/sites/portal/src/app/dashboard/page.tsx new file mode 100644 index 0000000..c242a9c --- /dev/null +++ b/sites/portal/src/app/dashboard/page.tsx @@ -0,0 +1,227 @@ +'use client'; + +import { useSession, signOut } from 'next-auth/react'; +import { trpc } from '@/lib/trpc'; +import { useRouter } from 'next/navigation'; +import { useEffect, useState, useMemo } from 'react'; + +type DashboardMode = 'CLUB' | 'HACKLYTICS'; + +export default function Dashboard() { + const { data: session, status } = useSession(); + const router = useRouter(); + + // UI States + const [hasEntered, setHasEntered] = useState(false); + const [mode, setMode] = useState('CLUB'); + const [isCheckingIn, setIsCheckingIn] = useState(false); + const [checkInStatus, setCheckInStatus] = useState<'IDLE' | 'SCANNING' | 'SUCCESS'>('IDLE'); + + // Extract Last Name from Google Session + const userIdentifier = useMemo(() => { + if (!session?.user?.name) return "GUEST"; + const names = session.user.name.trim().split(' '); + return names.length > 1 ? names[names.length - 1].toUpperCase() : names[0].toUpperCase(); + }, [session]); + + useEffect(() => { + if (status === 'unauthenticated') router.push('/'); + }, [status, router]); + + // Simulated Check-in Logic + const handleCheckIn = () => { + setCheckInStatus('SCANNING'); + setTimeout(() => { + setCheckInStatus('SUCCESS'); + setTimeout(() => { + setCheckInStatus('IDLE'); + setIsCheckingIn(false); + }, 3000); + }, 1500); + }; + + if (status === 'loading') { + return ( +
+
Syncing Identity...
+
+ ); + } + + if (!session) return null; + + // --- WELCOME SCREEN VIEW --- + if (!hasEntered) { + return ( +
+
+
+
+

Identity Verified

+

+ Welcome,
+ + {session.user?.name?.split(' ')[0] || 'User'}. + +

+
+ +
+
+ ); + } + + return ( +
+ +
+
+
+ +
+ +
+
+
+ +
+

{session.user?.name}

+

Access_Level: Member

+
+
+ +
+

Switch Module

+ + +
+
+ + +
+ +
+ +
+ +
+
+

+ {mode === 'CLUB' ? 'Club Ecosystem' : 'Hacklytics Core'} +

+

+ Status // {isCheckingIn ? 'CHECK_IN_MODE' : `System_Node_${mode}`} +

+
+
+
+ + {mode === 'CLUB' && ( +
+ {!isCheckingIn ? ( + <> +
+
+

Global Rank

+

#14

+
+
+

Active Credits

+

450XP

+
+
+ +
+

Present at a general body meeting?

+ +
+ + ) : ( +
+
+ {checkInStatus === 'IDLE' && ( + <> +

Awaiting Proximity Confirmation

+ + + )} + {checkInStatus === 'SCANNING' && ( +
+
+

Verifying Node Location...

+
+ )} + {checkInStatus === 'SUCCESS' && ( +
+
+

Attendance Logged

+

NODE: GT_MEETING_RM_04

+
+ )} +
+ +
+ )} +
+ )} + + {mode === 'HACKLYTICS' && ( +
+
+

Handshake Pending

+

+ Your registration for Hacklytics 2026 is currently in the validation queue. + Node verification in progress. +

+
+ +
+ )} +
+ +
+ TERMINAL: {isCheckingIn ? 'INITIALIZING_LOC_CHECK' : `MODE_ACTIVE: ${mode}`} // TIME: {new Date().toLocaleTimeString()} +
+
+
+ + {/* FOOTER REFLECTS LAST NAME ARCHIVE */} +
+
{userIdentifier}_ARCHIVE // V4.0
+
GT_PORTAL_ENCRYPTED
+
+
+ ); +} \ No newline at end of file diff --git a/sites/portal/src/app/layout.tsx b/sites/portal/src/app/layout.tsx index f2be803..eaf15fd 100644 --- a/sites/portal/src/app/layout.tsx +++ b/sites/portal/src/app/layout.tsx @@ -1,10 +1,8 @@ -import './globals.css'; import { Providers } from './providers'; +import './globals.css'; -export const metadata = { - title: 'Portal', - description: 'A modern TRPC + Next.js portal', -}; +export const dynamic = 'force-dynamic'; +export const revalidate = 0; export default function RootLayout({ children, @@ -13,9 +11,9 @@ export default function RootLayout({ }) { return ( - + {children} ); -} +} \ No newline at end of file diff --git a/sites/portal/src/app/page.tsx b/sites/portal/src/app/page.tsx index 0e5549f..87209d0 100644 --- a/sites/portal/src/app/page.tsx +++ b/sites/portal/src/app/page.tsx @@ -1,21 +1,71 @@ 'use client'; import React, { useState, useEffect } from 'react'; -import { trpc } from '../lib/trpc'; -import Link from "next/link"; +import { useSession, signIn } from 'next-auth/react'; +import { trpc } from '@/lib/trpc'; +import { useRouter } from 'next/navigation'; export default function Home() { - const { mutate, data, isPending } = trpc.hello.sayHello.useMutation(); - const [logs, setLogs] = useState(["Initializing terminal...", "Waiting for user input..."]); - - const handleClick = () => { - setLogs(prev => [...prev.slice(-4), "> Executing: sayHello.mutate()"]); - mutate(undefined, { - onSuccess: () => setLogs(prev => [...prev.slice(-4), "> Success: Response received"]), - onError: () => setLogs(prev => [...prev.slice(-4), "> Error: Connection failed"]) + const { data: session, status } = useSession(); + const router = useRouter(); + const [mounted, setMounted] = useState(false); + + // Terminal logs initialized with system startup messages + const [logs, setLogs] = useState([ + "Initializing terminal...", + "System check: OK", + "Loading background modules..." + ]); + + // tRPC Mutation + const { mutate: sayHello, isPending: helloLoading } = + trpc.hello.sayHello.useMutation({ + onSuccess: (res) => { + setLogs(prev => [...prev.slice(-4), `> Response: ${res.message}`]); + }, + onError: () => { + setLogs(prev => [...prev.slice(-4), "> Error: Connection failed"]); + } }); + + // Handle mounting and initial silent background status check + useEffect(() => { + setMounted(true); + + // Simulating background system discovery + const timeout = setTimeout(() => { + setLogs(prev => [...prev.slice(-4), "> Network: Established", "> Session: Awaiting user..."]); + }, 800); + + return () => clearTimeout(timeout); + }, []); + + // SILENT BACKGROUND REDIRECT + // Instead of a loading screen, we watch for 'authenticated' status in the background + useEffect(() => { + if (status === 'authenticated' && session) { + setLogs(prev => [...prev.slice(-4), "> Auth success. Handshaking...", "> Redirecting to secure node..."]); + const redirectTimeout = setTimeout(() => router.push('/dashboard'), 1200); + return () => clearTimeout(redirectTimeout); + } + }, [status, session, router]); + + const handleTestEndpoint = () => { + setLogs(prev => [...prev.slice(-4), "> Executing: public.sayHello()"]); + sayHello(); + }; + + const handleSignIn = () => { + setLogs(prev => [...prev.slice(-4), "> Initializing OAuth Handshake..."]); + signIn('google', { callbackUrl: '/dashboard' }); }; + // If not mounted, return an empty shell to prevent hydration flicker + // but keep the background color so it feels like it's loading "in the dark" + if (!mounted) return
; + + const isRedirecting = status === 'authenticated'; + return (
@@ -44,7 +94,7 @@ export default function Home() {

- The collective intelligence of Georgia Tech's largest data science community. Authenticate below. + The collective intelligence of Georgia Tech's largest data science community. Authenticate to access your dashboard.

{/* TERMINAL OUTPUT BOX */} @@ -55,12 +105,8 @@ export default function Home() { {log}

))} - {isPending &&

{'>'} Awaiting server response...

} - {data && ( -
-

Incoming Stream

-

"{data.message}"

-
+ {(helloLoading || isRedirecting || status === 'loading') && ( +

{'>'} {status === 'loading' ? 'Syncing session...' : 'Processing request...'}

)}
@@ -68,11 +114,19 @@ export default function Home() {
+ +
@@ -82,34 +136,38 @@ export default function Home() {
- {/* Rotating border effect */}
DSGT Core
- {/* Visual HUD Metadata */}
-

Core Operational

+

+ {isRedirecting ? "Authentication Verified" : status === 'loading' ? "Synchronizing..." : "Core Operational"} +

-
LATENCY: 24MS -
ENCRYPT: AES-256 -
LOAD: 0.04% + +
+ STATUS: {status.toUpperCase()} + + +
+ REGION: ATL-08 +
- {/* FOOTER BAR */}
-
Internal Terminal // Query Engine
+
Internal Terminal // Auth Gateway
Access Node: 0812-ATL
diff --git a/sites/portal/src/app/providers.tsx b/sites/portal/src/app/providers.tsx index 3cd9380..f0c7414 100644 --- a/sites/portal/src/app/providers.tsx +++ b/sites/portal/src/app/providers.tsx @@ -1,27 +1,44 @@ 'use client'; +import { SessionProvider } from 'next-auth/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { httpBatchLink } from '@trpc/client'; import { useState } from 'react'; -import { trpc } from '../lib/trpc'; +import superjson from 'superjson'; +import { trpc } from '@/lib/trpc'; export function Providers({ children }: { children: React.ReactNode }) { - const [queryClient] = useState(() => new QueryClient()); + const [queryClient] = useState(() => new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, // 1 minute + }, + }, + })); + const [trpcClient] = useState(() => trpc.createClient({ links: [ httpBatchLink({ url: '/api/trpc', + transformer: superjson, + headers() { + return { + // Cookies are automatically sent by the browser + }; + }, }), ], }) ); return ( - + - {children} + + {children} + - + ); } \ No newline at end of file diff --git a/sites/portal/src/components/Background.tsx b/sites/portal/src/components/Background.tsx new file mode 100644 index 0000000..d7225fd --- /dev/null +++ b/sites/portal/src/components/Background.tsx @@ -0,0 +1,28 @@ +// components/Background.tsx +import React from 'react'; + +const Background = ({ className = "" }: { className?: string }) => { + return ( +
+ {/* Primary Glow */} +
+ + {/* Secondary Orbital Glows */} +
+
+ + {/* Technical Grid Overlay */} +
+
+ ); +}; + +export default Background; \ No newline at end of file diff --git a/sites/portal/src/lib/trpc.ts b/sites/portal/src/lib/trpc.ts deleted file mode 100644 index b714b57..0000000 --- a/sites/portal/src/lib/trpc.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createTRPCReact } from '@trpc/react-query'; -import type { AppRouter } from '@query/api'; - -export const trpc = createTRPCReact(); \ No newline at end of file diff --git a/sites/portal/src/lib/trpc.tsx b/sites/portal/src/lib/trpc.tsx new file mode 100644 index 0000000..2988363 --- /dev/null +++ b/sites/portal/src/lib/trpc.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { createTRPCReact } from "@trpc/react-query"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { httpBatchLink } from "@trpc/client"; +import { useState } from "react"; +import type { AppRouter } from "@query/api"; +import superjson from "superjson"; + +export const trpc = createTRPCReact(); + +export function TRPCProvider({ children }: { children: React.ReactNode }) { + const [queryClient] = useState(() => new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, // 1 minute + }, + }, + })); + + const [trpcClient] = useState(() => + trpc.createClient({ + links: [ + httpBatchLink({ + url: "/api/trpc", + transformer: superjson, + headers() { + return { + // Cookies are automatically sent by the browser + }; + }, + }), + ], + }) + ); + + return ( + + + {children} + + + ); +} \ No newline at end of file diff --git a/sites/portal/tsconfig.json b/sites/portal/tsconfig.json index d8e7e63..cea8229 100644 --- a/sites/portal/tsconfig.json +++ b/sites/portal/tsconfig.json @@ -1,13 +1,41 @@ { - "extends": "@query/tsconfig/nextjs.json", "compilerOptions": { - "baseUrl": ".", + "jsx": "preserve", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "types": [], + "plugins": [ + { + "name": "next" + } + ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] }, - "jsx": "react-jsx", - "lib": ["ES2015", "DOM", "DOM.Iterable"] + "target": "ES2017" }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] -} \ No newline at end of file + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +}