aboutsummaryrefslogtreecommitdiff
path: root/packages/web/src/server/api
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web/src/server/api')
-rw-r--r--packages/web/src/server/api/root.ts7
-rw-r--r--packages/web/src/server/api/routers/api-key.ts132
-rw-r--r--packages/web/src/server/api/routers/memory.ts14
-rw-r--r--packages/web/src/server/api/trpc.ts1
4 files changed, 142 insertions, 12 deletions
diff --git a/packages/web/src/server/api/root.ts b/packages/web/src/server/api/root.ts
index b0fd7ba..919ba21 100644
--- a/packages/web/src/server/api/root.ts
+++ b/packages/web/src/server/api/root.ts
@@ -1,13 +1,10 @@
+import { apiKeyRouter } from "~/server/api/routers/api-key";
import { memoryRouter } from "~/server/api/routers/memory";
import { postRouter } from "~/server/api/routers/post";
import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc";
-/**
- * This is the primary router for your server.
- *
- * All routers added in /api/routers should be manually added here.
- */
export const appRouter = createTRPCRouter({
+ apiKey: apiKeyRouter,
memory: memoryRouter,
post: postRouter,
});
diff --git a/packages/web/src/server/api/routers/api-key.ts b/packages/web/src/server/api/routers/api-key.ts
new file mode 100644
index 0000000..37c26d6
--- /dev/null
+++ b/packages/web/src/server/api/routers/api-key.ts
@@ -0,0 +1,132 @@
+import { extractKeyPrefix, generateApiKey, hashApiKey } from "@imemio/sdk";
+import { TRPCError } from "@trpc/server";
+import { z } from "zod";
+import { createClient } from "~/lib/supabase/server";
+import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
+
+export const apiKeyRouter = createTRPCRouter({
+ list: protectedProcedure.query(async ({ ctx: context }) => {
+ const supabase = await createClient();
+ const { data, error } = await supabase
+ .from("api_keys")
+ .select(
+ "id, name, key_prefix, last_used_at, expires_at, revoked_at, created_at",
+ )
+ .eq("user_id", context.user.id)
+ .is("revoked_at", null)
+ .order("created_at", { ascending: false });
+
+ if (error) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Failed to fetch API keys",
+ });
+ }
+
+ return data.map((key) => ({
+ id: key.id as string,
+ name: key.name as string,
+ keyPrefix: key.key_prefix as string,
+ lastUsedAt: key.last_used_at
+ ? new Date(key.last_used_at as string)
+ : null,
+ expiresAt: key.expires_at ? new Date(key.expires_at as string) : null,
+ createdAt: new Date(key.created_at as string),
+ }));
+ }),
+
+ create: protectedProcedure
+ .input(
+ z.object({
+ name: z.string().min(1).max(100),
+ expiresAt: z.date().optional(),
+ }),
+ )
+ .mutation(async ({ ctx: context, input }) => {
+ const supabase = await createClient();
+ const apiKey = generateApiKey();
+ const keyPrefix = extractKeyPrefix(apiKey);
+ const keyHash = await hashApiKey(apiKey);
+ const { data, error } = await supabase
+ .from("api_keys")
+ .insert({
+ user_id: context.user.id,
+ name: input.name,
+ key_prefix: keyPrefix,
+ key_hash: keyHash,
+ expires_at: input.expiresAt?.toISOString() ?? null,
+ })
+ .select("id, name, key_prefix, created_at")
+ .single();
+
+ if (error) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Failed to create API key",
+ });
+ }
+
+ return {
+ id: data.id as string,
+ name: data.name as string,
+ keyPrefix: data.key_prefix as string,
+ createdAt: new Date(data.created_at as string),
+ apiKey,
+ };
+ }),
+
+ revoke: protectedProcedure
+ .input(z.object({ id: z.string().uuid() }))
+ .mutation(async ({ ctx: context, input }) => {
+ const supabase = await createClient();
+ const { error, count } = await supabase
+ .from("api_keys")
+ .update({ revoked_at: new Date().toISOString() })
+ .eq("id", input.id)
+ .eq("user_id", context.user.id)
+ .is("revoked_at", null);
+
+ if (error) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Failed to revoke API key",
+ });
+ }
+
+ if (count === 0) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "API key not found",
+ });
+ }
+
+ return { success: true };
+ }),
+
+ delete: protectedProcedure
+ .input(z.object({ id: z.string().uuid() }))
+ .mutation(async ({ ctx: context, input }) => {
+ const supabase = await createClient();
+ const { error, count } = await supabase
+ .from("api_keys")
+ .delete({ count: "exact" })
+ .eq("id", input.id)
+ .eq("user_id", context.user.id);
+
+ if (error) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Failed to delete API key",
+ });
+ }
+
+ if (count === 0) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "API key not found",
+ });
+ }
+
+ return { success: true };
+ }),
+});
diff --git a/packages/web/src/server/api/routers/memory.ts b/packages/web/src/server/api/routers/memory.ts
index d1bb849..e6d206b 100644
--- a/packages/web/src/server/api/routers/memory.ts
+++ b/packages/web/src/server/api/routers/memory.ts
@@ -39,9 +39,9 @@ async function getOrCreateDefaultProject(
}
export const memoryRouter = createTRPCRouter({
- list: protectedProcedure.query(async ({ ctx }) => {
+ list: protectedProcedure.query(async ({ ctx: context }) => {
const supabaseClient = await createClient();
- const memoryStore = new SupabaseStore(supabaseClient, ctx.user.id);
+ const memoryStore = new SupabaseStore(supabaseClient, context.user.id);
const memories = await memoryStore.list();
return memories;
@@ -49,13 +49,13 @@ export const memoryRouter = createTRPCRouter({
create: protectedProcedure
.input(z.object({ content: z.string().min(1) }))
- .mutation(async ({ ctx, input }) => {
+ .mutation(async ({ context, input }) => {
const supabaseClient = await createClient();
const projectId = await getOrCreateDefaultProject(
supabaseClient,
- ctx.user.id,
+ context.user.id,
);
- const memoryStore = new SupabaseStore(supabaseClient, ctx.user.id);
+ const memoryStore = new SupabaseStore(supabaseClient, context.user.id);
const memory = await memoryStore.create({
content: input.content,
projectId,
@@ -66,9 +66,9 @@ export const memoryRouter = createTRPCRouter({
delete: protectedProcedure
.input(z.object({ id: z.string() }))
- .mutation(async ({ ctx, input }) => {
+ .mutation(async ({ context, input }) => {
const supabaseClient = await createClient();
- const memoryStore = new SupabaseStore(supabaseClient, ctx.user.id);
+ const memoryStore = new SupabaseStore(supabaseClient, context.user.id);
const deleteResult = await memoryStore.delete(input.id);
if (!deleteResult.success) {
diff --git a/packages/web/src/server/api/trpc.ts b/packages/web/src/server/api/trpc.ts
index 56dcb4c..62897a5 100644
--- a/packages/web/src/server/api/trpc.ts
+++ b/packages/web/src/server/api/trpc.ts
@@ -127,6 +127,7 @@ export const protectedProcedure = trpcInstance.procedure
return next({
ctx: {
+ ...ctx,
user: ctx.user,
},
});