Skip to content

Commit 8ae30db

Browse files
committed
Add collections
1 parent 7e8844e commit 8ae30db

16 files changed

Lines changed: 1052 additions & 165 deletions

File tree

config/app.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export type AppConfig = z.infer<typeof appConfigSchema>;
150150
const resolvedPublicOrigin =
151151
typeof env.clientOrigin === "string" && env.clientOrigin.length > 0
152152
? env.clientOrigin
153-
: "http://localhost:3000";
153+
: "https://d2jam.com";
154154

155155
const defaultConfig: AppConfig = {
156156
appName: "Jamcore",
@@ -300,7 +300,7 @@ function getHostname(origin: string) {
300300
try {
301301
return new URL(origin).hostname;
302302
} catch {
303-
return "localhost";
303+
return "d2jam.com";
304304
}
305305
}
306306

config/env.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ const envSchema = z.object({
5757

5858
const parsed = envSchema.parse(process.env);
5959

60+
function isLoopbackOrigin(value: string | undefined) {
61+
if (!value) return false;
62+
try {
63+
const hostname = new URL(value).hostname.toLowerCase();
64+
return (
65+
hostname === "localhost" ||
66+
hostname === "127.0.0.1" ||
67+
hostname === "::1"
68+
);
69+
} catch {
70+
return false;
71+
}
72+
}
73+
6074
if (parsed.NODE_ENV === "production") {
6175
const missing = [
6276
!parsed.CLIENT_ORIGIN ? "CLIENT_ORIGIN" : null,
@@ -68,6 +82,17 @@ if (parsed.NODE_ENV === "production") {
6882
`Missing required production environment variables: ${missing.join(", ")}`,
6983
);
7084
}
85+
86+
const loopback = [
87+
isLoopbackOrigin(parsed.CLIENT_ORIGIN) ? "CLIENT_ORIGIN" : null,
88+
isLoopbackOrigin(parsed.FEDERATION_ORIGIN) ? "FEDERATION_ORIGIN" : null,
89+
].filter(Boolean);
90+
91+
if (loopback.length > 0) {
92+
throw new Error(
93+
`Production environment variables cannot use localhost or loopback origins: ${loopback.join(", ")}`,
94+
);
95+
}
7196
}
7297

7398
export const env = {

features/collections/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ export {
2121
listCollections,
2222
listCollectionsQuerySchema,
2323
removeCollectionItem,
24+
resolveCollectionMusicMetadata,
2425
respondCollectionCollaboratorInvite,
2526
respondCollectionCollaboratorSchema,
2627
updateCollection,
28+
updateCollectionItem,
29+
updateCollectionItemSchema,
2730
updateCollectionSchema,
2831
} from "./service.js";

features/collections/router.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,29 @@ import {
3131
listCollections,
3232
listCollectionsQuerySchema,
3333
removeCollectionItem,
34+
resolveCollectionMusicMetadata,
3435
respondCollectionCollaboratorInvite,
3536
respondCollectionCollaboratorSchema,
3637
updateCollection,
38+
updateCollectionItem,
39+
updateCollectionItemSchema,
3740
updateCollectionSchema,
3841
} from "./index.js";
3942

4043
const collectionParamsSchema = z.object({
4144
collectionId: z.string().trim().min(1),
4245
});
4346

44-
const collectionItemParamsSchema = collectionParamsSchema.extend({
45-
itemId: z.string().trim().min(1),
47+
const collectionItemParamsSchema = collectionParamsSchema.extend({
48+
itemId: z.coerce.number().int().positive(),
4649
});
4750

4851
const collectionCommentParamsSchema = collectionParamsSchema.extend({
49-
commentId: z.string().trim().min(1),
52+
commentId: z.coerce.number().int().positive(),
53+
});
54+
55+
const metadataQuerySchema = z.object({
56+
url: z.string().trim().url(),
5057
});
5158

5259
export function createCollectionsRouter() {
@@ -97,6 +104,16 @@ export function createCollectionsRouter() {
97104
}),
98105
);
99106

107+
router.get(
108+
"/metadata",
109+
authUserOptional,
110+
getUserOptional,
111+
asyncHandler(async (req, res) => {
112+
const { url } = parseQuery(req, metadataQuerySchema);
113+
res.json(await resolveCollectionMusicMetadata({ url }));
114+
}),
115+
);
116+
100117
router.get(
101118
"/:collectionId",
102119
authUserOptional,
@@ -313,5 +330,22 @@ export function createCollectionsRouter() {
313330
}),
314331
);
315332

333+
router.put(
334+
"/:collectionId/items/:itemId",
335+
authUser,
336+
getUser,
337+
asyncHandler(async (req, res) => {
338+
const { collectionId, itemId } = parseParams(req, collectionItemParamsSchema);
339+
const input = parseBody(req, updateCollectionItemSchema);
340+
const result = await updateCollectionItem({
341+
collectionId,
342+
itemId,
343+
actor: requireRequestUser(res),
344+
input,
345+
});
346+
res.json(result);
347+
}),
348+
);
349+
316350
return router;
317351
}

0 commit comments

Comments
 (0)