@gigamusic/links 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/api/handlers.ts +14 -34
- package/src/api/auth.ts +0 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gigamusic/links",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Editable Linktree-style link pages for gigamusic artist sites. Ships a Prisma fragment, public + admin React components, admin API handler factories, and platform-detection helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@gigamusic/
|
|
42
|
-
"@gigamusic/
|
|
43
|
-
"@gigamusic/
|
|
44
|
-
"@gigamusic/
|
|
41
|
+
"@gigamusic/core": "0.2.0",
|
|
42
|
+
"@gigamusic/ui": "0.2.0",
|
|
43
|
+
"@gigamusic/config": "0.2.0",
|
|
44
|
+
"@gigamusic/db": "0.2.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@prisma/client": ">=6",
|
package/src/api/handlers.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { revalidateTag } from "next/cache";
|
|
2
2
|
import type { LinkPageQueries } from "../types";
|
|
3
|
-
import { verifyAdminSession } from "./auth";
|
|
4
3
|
import {
|
|
5
4
|
INVALID_SLUG_MESSAGE,
|
|
6
5
|
RESERVED_SLUGS,
|
|
@@ -22,9 +21,14 @@ export type RouteHandler<Params = Record<string, string>> = (
|
|
|
22
21
|
ctx: { params: Promise<Params> },
|
|
23
22
|
) => Promise<Response> | Response;
|
|
24
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Deps for the admin-side link-page handler factories.
|
|
26
|
+
*
|
|
27
|
+
* Auth: the package assumes the request has been gated by the consumer's
|
|
28
|
+
* `proxy.ts` / middleware. These handlers don't verify a session.
|
|
29
|
+
*/
|
|
25
30
|
export interface LinkPagesAdminDeps {
|
|
26
31
|
queries: LinkPageQueries;
|
|
27
|
-
adminSessionSecret: string;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
function json(body: unknown, init?: ResponseInit): Response {
|
|
@@ -37,14 +41,6 @@ function json(body: unknown, init?: ResponseInit): Response {
|
|
|
37
41
|
});
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
async function requireAdmin(
|
|
41
|
-
req: Request,
|
|
42
|
-
secret: string,
|
|
43
|
-
): Promise<Response | null> {
|
|
44
|
-
if (await verifyAdminSession(req, secret)) return null;
|
|
45
|
-
return json({ error: "Unauthorized" }, { status: 401 });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
interface LinkPageBody {
|
|
49
45
|
title?: string;
|
|
50
46
|
slug?: string;
|
|
@@ -73,16 +69,12 @@ export function createAdminLinkPagesHandlers(deps: LinkPagesAdminDeps): {
|
|
|
73
69
|
} {
|
|
74
70
|
return {
|
|
75
71
|
GET: async (req) => {
|
|
76
|
-
|
|
77
|
-
if (unauth) return unauth;
|
|
78
|
-
const pages = await deps.queries.listAllLinkPages();
|
|
72
|
+
const pages = await deps.queries.listAllLinkPages();
|
|
79
73
|
return json(pages);
|
|
80
74
|
},
|
|
81
75
|
|
|
82
76
|
POST: async (req) => {
|
|
83
|
-
|
|
84
|
-
if (unauth) return unauth;
|
|
85
|
-
const body = (await req.json()) as LinkPageBody;
|
|
77
|
+
const body = (await req.json()) as LinkPageBody;
|
|
86
78
|
const title = body.title?.trim();
|
|
87
79
|
const slug = body.slug?.trim().toLowerCase();
|
|
88
80
|
|
|
@@ -122,9 +114,7 @@ export function createAdminLinkPageByIdHandlers(deps: LinkPagesAdminDeps): {
|
|
|
122
114
|
} {
|
|
123
115
|
return {
|
|
124
116
|
GET: async (req, { params }) => {
|
|
125
|
-
|
|
126
|
-
if (unauth) return unauth;
|
|
127
|
-
const { id: idParam } = await params;
|
|
117
|
+
const { id: idParam } = await params;
|
|
128
118
|
const id = Number(idParam);
|
|
129
119
|
if (!Number.isFinite(id)) return json({ error: "Not found" }, { status: 404 });
|
|
130
120
|
const page = await deps.queries.getLinkPageById(id);
|
|
@@ -133,9 +123,7 @@ export function createAdminLinkPageByIdHandlers(deps: LinkPagesAdminDeps): {
|
|
|
133
123
|
},
|
|
134
124
|
|
|
135
125
|
PUT: async (req, { params }) => {
|
|
136
|
-
|
|
137
|
-
if (unauth) return unauth;
|
|
138
|
-
const { id: idParam } = await params;
|
|
126
|
+
const { id: idParam } = await params;
|
|
139
127
|
const id = Number(idParam);
|
|
140
128
|
if (!Number.isFinite(id)) return json({ error: "Not found" }, { status: 404 });
|
|
141
129
|
const body = (await req.json()) as LinkPageBody;
|
|
@@ -171,9 +159,7 @@ export function createAdminLinkPageByIdHandlers(deps: LinkPagesAdminDeps): {
|
|
|
171
159
|
},
|
|
172
160
|
|
|
173
161
|
DELETE: async (req, { params }) => {
|
|
174
|
-
|
|
175
|
-
if (unauth) return unauth;
|
|
176
|
-
const { id: idParam } = await params;
|
|
162
|
+
const { id: idParam } = await params;
|
|
177
163
|
const id = Number(idParam);
|
|
178
164
|
if (!Number.isFinite(id)) return json({ error: "Not found" }, { status: 404 });
|
|
179
165
|
await deps.queries.deleteLinkPage(id);
|
|
@@ -207,9 +193,7 @@ export function createAdminLinkPageItemsHandlers(
|
|
|
207
193
|
} {
|
|
208
194
|
return {
|
|
209
195
|
POST: async (req, { params }) => {
|
|
210
|
-
|
|
211
|
-
if (unauth) return unauth;
|
|
212
|
-
const { id: pageIdParam } = await params;
|
|
196
|
+
const { id: pageIdParam } = await params;
|
|
213
197
|
const pageId = Number(pageIdParam);
|
|
214
198
|
if (!Number.isFinite(pageId)) {
|
|
215
199
|
return json({ error: "Not found" }, { status: 404 });
|
|
@@ -227,9 +211,7 @@ export function createAdminLinkPageItemsHandlers(
|
|
|
227
211
|
},
|
|
228
212
|
|
|
229
213
|
PUT: async (req) => {
|
|
230
|
-
|
|
231
|
-
if (unauth) return unauth;
|
|
232
|
-
const body = (await req.json()) as LinkPageItemBody;
|
|
214
|
+
const body = (await req.json()) as LinkPageItemBody;
|
|
233
215
|
if (body.itemId == null) {
|
|
234
216
|
return json({ error: "itemId is required" }, { status: 400 });
|
|
235
217
|
}
|
|
@@ -248,9 +230,7 @@ export function createAdminLinkPageItemsHandlers(
|
|
|
248
230
|
},
|
|
249
231
|
|
|
250
232
|
DELETE: async (req) => {
|
|
251
|
-
|
|
252
|
-
if (unauth) return unauth;
|
|
253
|
-
const { searchParams } = new URL(req.url);
|
|
233
|
+
const { searchParams } = new URL(req.url);
|
|
254
234
|
const itemIdRaw = searchParams.get("itemId");
|
|
255
235
|
if (!itemIdRaw) {
|
|
256
236
|
return json({ error: "itemId is required" }, { status: 400 });
|
package/src/api/auth.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { verifyAdminSessionToken } from "@gigamusic/core";
|
|
2
|
-
|
|
3
|
-
const ADMIN_SESSION_COOKIE = "admin_session";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Stateless cookie verification for route handlers. Mirrors
|
|
7
|
-
* `@gigamusic/admin`'s `verifyAdminSession` so consumers can wire both
|
|
8
|
-
* packages to the same session secret without coupling them at the package
|
|
9
|
-
* level.
|
|
10
|
-
*/
|
|
11
|
-
export async function verifyAdminSession(
|
|
12
|
-
req: Request,
|
|
13
|
-
adminSessionSecret: string,
|
|
14
|
-
): Promise<boolean> {
|
|
15
|
-
const header = req.headers.get("cookie");
|
|
16
|
-
if (!header) return false;
|
|
17
|
-
const token = parseCookieHeader(header, ADMIN_SESSION_COOKIE);
|
|
18
|
-
if (!token) return false;
|
|
19
|
-
try {
|
|
20
|
-
await verifyAdminSessionToken(token, adminSessionSecret);
|
|
21
|
-
return true;
|
|
22
|
-
} catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function parseCookieHeader(header: string, name: string): string | null {
|
|
28
|
-
for (const raw of header.split(";")) {
|
|
29
|
-
const trimmed = raw.trim();
|
|
30
|
-
if (!trimmed.startsWith(`${name}=`)) continue;
|
|
31
|
-
return decodeURIComponent(trimmed.slice(name.length + 1));
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|