forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
1import { ctx } from "context";
2import { and, eq } from "drizzle-orm";
3import { Hono } from "hono";
4import jwt from "jsonwebtoken";
5import { env } from "lib/env";
6import crypto from "node:crypto";
7import * as R from "ramda";
8import apiKeys from "schema/api-keys";
9import users from "schema/users";
10import { apiKeySchema } from "types/apikey";
11
12const app = new Hono();
13
14app.get("/", async (c) => {
15 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim();
16
17 if (!bearer || bearer === "null") {
18 c.status(401);
19 return c.text("Unauthorized");
20 }
21
22 const { did } = jwt.verify(bearer, env.JWT_SECRET, {
23 ignoreExpiration: true,
24 });
25
26 const user = await ctx.db
27 .select()
28 .from(users)
29 .where(eq(users.did, did))
30 .limit(1)
31 .then((rows) => rows[0]);
32
33 if (!user) {
34 c.status(401);
35 return c.text("Unauthorized");
36 }
37
38 const size = +c.req.query("size") || 20;
39 const offset = +c.req.query("offset") || 0;
40
41 const apikeysData = await ctx.db
42 .select()
43 .from(apiKeys)
44 .where(eq(apiKeys.userId, user.id))
45 .limit(size)
46 .offset(offset);
47
48 return c.json(apikeysData.map((x) => R.omit(["userId"])(x)));
49});
50
51app.post("/", async (c) => {
52 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim();
53
54 if (!bearer || bearer === "null") {
55 c.status(401);
56 return c.text("Unauthorized");
57 }
58
59 const { did } = jwt.verify(bearer, env.JWT_SECRET, {
60 ignoreExpiration: true,
61 });
62
63 const user = await ctx.db
64 .select()
65 .from(users)
66 .where(eq(users.did, did))
67 .limit(1)
68 .then((rows) => rows[0]);
69
70 if (!user) {
71 c.status(401);
72 return c.text("Unauthorized");
73 }
74
75 const body = await c.req.json();
76 const parsed = apiKeySchema.safeParse(body);
77
78 if (parsed.error) {
79 c.status(400);
80 return c.text("Invalid api key data: " + parsed.error.message);
81 }
82 const newApiKey = parsed.data;
83
84 if (!newApiKey.name) {
85 c.status(400);
86 return c.text("Missing required field: name");
87 }
88
89 const apiKey = crypto.randomBytes(16).toString("hex");
90 const sharedSecret = crypto.randomBytes(16).toString("hex");
91
92 const [record] = await ctx.db
93 .insert(apiKeys)
94 .values({
95 name: newApiKey.name,
96 description: newApiKey.description ?? "",
97 enabled: newApiKey.enabled ?? true,
98 apiKey,
99 sharedSecret,
100 userId: user.id,
101 })
102 .returning();
103
104 return c.json({
105 id: record.id,
106 name: record.name,
107 description: record.description,
108 api_key: record.apiKey,
109 shared_secret: record.sharedSecret,
110 });
111});
112
113app.put("/:id", async (c) => {
114 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim();
115
116 if (!bearer || bearer === "null") {
117 c.status(401);
118 return c.text("Unauthorized");
119 }
120
121 const { did } = jwt.verify(bearer, env.JWT_SECRET, {
122 ignoreExpiration: true,
123 });
124
125 const user = await ctx.db
126 .select()
127 .from(users)
128 .where(eq(users.did, did))
129 .limit(1)
130 .then((rows) => rows[0]);
131
132 if (!user) {
133 c.status(401);
134 return c.text("Unauthorized");
135 }
136
137 const data = await c.req.json();
138 const id = c.req.param("id");
139
140 const [record] = await ctx.db
141 .update(apiKeys)
142 .set(data)
143 .where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id)))
144 .returning();
145
146 return c.json({
147 id: record.id,
148 name: record.name,
149 description: record.description,
150 api_key: record.apiKey,
151 shared_secret: record.sharedSecret,
152 });
153});
154
155app.delete("/:id", async (c) => {
156 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim();
157
158 if (!bearer || bearer === "null") {
159 c.status(401);
160 return c.text("Unauthorized");
161 }
162
163 const { did } = jwt.verify(bearer, env.JWT_SECRET, {
164 ignoreExpiration: true,
165 });
166
167 const user = await ctx.db
168 .select()
169 .from(users)
170 .where(eq(users.did, did))
171 .limit(1)
172 .then((rows) => rows[0]);
173
174 if (!user) {
175 c.status(401);
176 return c.text("Unauthorized");
177 }
178
179 const id = c.req.param("id");
180
181 await ctx.db
182 .delete(apiKeys)
183 .where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id)));
184
185 return c.json({ success: true });
186});
187
188export default app;