Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
}
}
}
30 changes: 11 additions & 19 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
15 changes: 15 additions & 0 deletions packages/api/src/context.ts
Original file line number Diff line number Diff line change
@@ -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<ReturnType<typeof createContext>>;
7 changes: 3 additions & 4 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
export { appRouter, type AppRouter } from "./root";
export { createContext, type Context } from "./context";
export { createTRPCRouter, publicProcedure, protectedProcedure } from "./trpc";
16 changes: 4 additions & 12 deletions packages/api/src/root.ts
Original file line number Diff line number Diff line change
@@ -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<Context> => {
return {};
};

// Root app router
export const appRouter = createTRPCRouter({
hello: helloRouter,
user: userRouter,
});

// Export API type
export type AppRouter = typeof appRouter;
43 changes: 41 additions & 2 deletions packages/api/src/routers/hello.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}),
});
117 changes: 117 additions & 0 deletions packages/api/src/routers/user.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}),
});
45 changes: 38 additions & 7 deletions packages/api/src/trpc.ts
Original file line number Diff line number Diff line change
@@ -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<Context>().create();
const t = initTRPC.context<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;
// 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);
26 changes: 17 additions & 9 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Loading