forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
1import { equals } from "@xata.io/client";
2import { ctx } from "context";
3import { and, eq } from "drizzle-orm";
4import { Hono } from "hono";
5import jwt from "jsonwebtoken";
6import { env } from "lib/env";
7import crypto from "node:crypto";
8import * as R from "ramda";
9import tables from "schema";
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.client.db.users.filter("did", equals(did)).getFirst();
27 if (!user) {
28 c.status(401);
29 return c.text("Unauthorized");
30 }
31
32 const size = +c.req.query("size") || 20;
33 const offset = +c.req.query("offset") || 0;
34
35 const apikeys = await ctx.db
36 .select()
37 .from(tables.apiKeys)
38 .where(eq(tables.apiKeys.userId, user.xata_id))
39 .limit(size)
40 .offset(offset)
41 .execute();
42
43 return c.json(apikeys.map((x) => R.omit(["userId"])(x)));
44});
45
46app.post("/", async (c) => {
47 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim();
48
49 if (!bearer || bearer === "null") {
50 c.status(401);
51 return c.text("Unauthorized");
52 }
53
54 const { did } = jwt.verify(bearer, env.JWT_SECRET, {
55 ignoreExpiration: true,
56 });
57
58 const user = await ctx.client.db.users.filter("did", equals(did)).getFirst();
59 if (!user) {
60 c.status(401);
61 return c.text("Unauthorized");
62 }
63
64 const body = await c.req.json();
65 const parsed = apiKeySchema.safeParse(body);
66
67 if (parsed.error) {
68 c.status(400);
69 return c.text("Invalid api key data: " + parsed.error.message);
70 }
71 const newApiKey = parsed.data;
72
73 const api_key = crypto.randomBytes(16).toString("hex");
74 const shared_secret = crypto.randomBytes(16).toString("hex");
75
76 const record = await ctx.client.db.api_keys.create({
77 ...newApiKey,
78 api_key,
79 shared_secret,
80 user_id: user.xata_id,
81 });
82
83 return c.json({
84 id: record.xata_id,
85 name: record.name,
86 description: record.description,
87 api_key: record.api_key,
88 shared_secret: record.shared_secret,
89 });
90});
91
92app.put("/:id", async (c) => {
93 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim();
94
95 if (!bearer || bearer === "null") {
96 c.status(401);
97 return c.text("Unauthorized");
98 }
99
100 const { did } = jwt.verify(bearer, env.JWT_SECRET, {
101 ignoreExpiration: true,
102 });
103
104 const user = await ctx.client.db.users.filter("did", equals(did)).getFirst();
105 if (!user) {
106 c.status(401);
107 return c.text("Unauthorized");
108 }
109
110 const data = await c.req.json();
111 const id = c.req.param("id");
112
113 const record = await ctx.db
114 .update(tables.apiKeys)
115 .set(data)
116 .where(
117 and(eq(tables.apiKeys.id, id), eq(tables.apiKeys.userId, user.xata_id)),
118 )
119 .execute();
120
121 return c.json({
122 id: record.xata_id,
123 name: record.name,
124 description: record.description,
125 api_key: record.api_key,
126 shared_secret: record.shared_secret,
127 });
128});
129
130app.delete("/:id", async (c) => {
131 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim();
132
133 if (!bearer || bearer === "null") {
134 c.status(401);
135 return c.text("Unauthorized");
136 }
137
138 const { did } = jwt.verify(bearer, env.JWT_SECRET, {
139 ignoreExpiration: true,
140 });
141
142 const user = await ctx.client.db.users.filter("did", equals(did)).getFirst();
143 if (!user) {
144 c.status(401);
145 return c.text("Unauthorized");
146 }
147
148 const id = c.req.param("id");
149
150 await ctx.db
151 .delete(tables.apiKeys)
152 .where(
153 and(eq(tables.apiKeys.id, id), eq(tables.apiKeys.userId, user.xata_id)),
154 )
155 .execute();
156
157 return c.json({ success: true });
158});
159
160export default app;