@gigamusic/links 1.0.0 → 2.0.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/README.md +41 -11
- package/package.json +14 -10
- package/src/index.ts +1 -0
- package/src/queries/link-pages.ts +72 -77
- package/src/schema/index.ts +2 -0
- package/src/schema/link-pages.ts +41 -0
- package/src/schema/relations.ts +12 -0
- package/src/server.ts +1 -0
- package/src/types.ts +5 -23
- package/prisma/_typegen.prisma +0 -29
- package/prisma/link-page.prisma +0 -54
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @gigamusic/links
|
|
2
2
|
|
|
3
|
-
Linktree-style per-release link pages for the gigamusic platform. Ships
|
|
3
|
+
Linktree-style per-release link pages for the gigamusic platform. Ships a Drizzle schema (`linkPages`, `linkPageItems`), a typed queries factory, admin API handler factories, platform-detection helpers, and a small SVG `LinkPlatformIcon` component.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import {
|
|
@@ -19,18 +19,48 @@ import {
|
|
|
19
19
|
} from "@gigamusic/links/server";
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
The public link-page UI is the consumer's responsibility — fetch with `createLinkPageQueries(
|
|
22
|
+
The public link-page UI is the consumer's responsibility — fetch with `createLinkPageQueries(db).getPublicLinkPageBySlug(slug)` and render whatever you want.
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Schema subpath
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
`@gigamusic/links/schema` exports the table objects and relations:
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
```ts
|
|
29
|
+
import {
|
|
30
|
+
linkPages,
|
|
31
|
+
linkPageItems,
|
|
32
|
+
linkPagesRelations,
|
|
33
|
+
linkPageItemsRelations,
|
|
34
|
+
linkPageColumns,
|
|
35
|
+
linkPageItemColumns,
|
|
36
|
+
} from "@gigamusic/links/schema";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Merge with `@gigamusic/db/schema` when constructing your Drizzle instance:
|
|
29
40
|
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
```ts
|
|
42
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
43
|
+
import { Pool } from "pg";
|
|
44
|
+
import * as dbSchema from "@gigamusic/db/schema";
|
|
45
|
+
import * as linksSchema from "@gigamusic/links/schema";
|
|
46
|
+
|
|
47
|
+
const schema = { ...dbSchema, ...linksSchema };
|
|
48
|
+
export const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }), { schema });
|
|
36
49
|
```
|
|
50
|
+
|
|
51
|
+
## Release back-reference
|
|
52
|
+
|
|
53
|
+
`linkPages.releaseId` is a nullable FK to `releases.id` from `@gigamusic/db`. Drizzle relations are declared independently on each side, so this package's `linkPagesRelations` already declares the `linkPage.release` direction. If you want a `release.linkPages` back-reference (for `db.query.releases.findFirst({ with: { linkPages: true } })`), add it in your own schema file by composing onto the base `releasesRelations`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { relations } from "drizzle-orm";
|
|
57
|
+
import { releases } from "@gigamusic/db/schema";
|
|
58
|
+
import { linkPages } from "@gigamusic/links/schema";
|
|
59
|
+
|
|
60
|
+
export const releasesRelations = relations(releases, ({ many }) => ({
|
|
61
|
+
// ...keep the existing many() relations from @gigamusic/db/schema if you need them...
|
|
62
|
+
linkPages: many(linkPages),
|
|
63
|
+
}));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Because relations are TS modules, you compose them once and never re-apply patches on package upgrades.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gigamusic/links",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Editable Linktree-style link pages for gigamusic artist sites. Ships a
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Editable Linktree-style link pages for gigamusic artist sites. Ships a Drizzle schema, admin API handler factories, and platform-detection helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
"./server": {
|
|
18
18
|
"types": "./src/server.ts",
|
|
19
19
|
"default": "./src/server.ts"
|
|
20
|
+
},
|
|
21
|
+
"./schema": {
|
|
22
|
+
"types": "./src/schema/index.ts",
|
|
23
|
+
"default": "./src/schema/index.ts"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
"files": [
|
|
23
27
|
"src",
|
|
24
|
-
"prisma",
|
|
25
28
|
"README.md"
|
|
26
29
|
],
|
|
27
30
|
"publishConfig": {
|
|
@@ -29,29 +32,30 @@
|
|
|
29
32
|
},
|
|
30
33
|
"dependencies": {
|
|
31
34
|
"@gigamusic/core": "1.0.0",
|
|
32
|
-
"@gigamusic/db": "
|
|
35
|
+
"@gigamusic/db": "2.0.0"
|
|
33
36
|
},
|
|
34
37
|
"peerDependencies": {
|
|
35
|
-
"
|
|
38
|
+
"drizzle-orm": ">=0.45",
|
|
36
39
|
"next": ">=15",
|
|
37
40
|
"react": ">=18 <20",
|
|
38
41
|
"react-dom": ">=18 <20"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
|
-
"@prisma/client": "^6.1.0",
|
|
42
44
|
"@types/node": "^22.10.5",
|
|
45
|
+
"@types/pg": "^8.11.10",
|
|
43
46
|
"@types/react": "^19.0.0",
|
|
47
|
+
"drizzle-kit": "^0.31.10",
|
|
48
|
+
"drizzle-orm": "^0.45.2",
|
|
44
49
|
"next": "^16.0.0",
|
|
45
|
-
"
|
|
50
|
+
"pg": "^8.21.0",
|
|
46
51
|
"react": "^19.0.0",
|
|
47
52
|
"typescript": "^5.7.3",
|
|
48
53
|
"vitest": "^2.1.8"
|
|
49
54
|
},
|
|
50
55
|
"scripts": {
|
|
51
56
|
"lint": "eslint src",
|
|
52
|
-
"test": "
|
|
53
|
-
"typecheck": "
|
|
54
|
-
"prisma:generate": "prisma generate --schema=./prisma",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
55
59
|
"clean": "rm -rf .turbo *.tsbuildinfo"
|
|
56
60
|
}
|
|
57
61
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type {
|
|
|
14
14
|
|
|
15
15
|
// ── Queries factory ─────────────────────────────────────────────────────
|
|
16
16
|
export { createLinkPageQueries } from "./queries/link-pages";
|
|
17
|
+
export type { LinkPagesDb } from "./queries/link-pages";
|
|
17
18
|
|
|
18
19
|
// ── Platform detection ──────────────────────────────────────────────────
|
|
19
20
|
export { detectLinkPlatform, PLATFORM_LABELS } from "./platforms/detect";
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { and, asc, desc, eq, max } from "drizzle-orm";
|
|
2
|
+
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
3
|
+
import type * as gigamusicDbSchema from "@gigamusic/db/schema";
|
|
4
|
+
import type * as linksSchema from "../schema/index.js";
|
|
5
|
+
import { linkPageItems, linkPages } from "../schema/index.js";
|
|
2
6
|
import type {
|
|
3
7
|
LinkPage,
|
|
4
8
|
LinkPageInput,
|
|
@@ -8,38 +12,42 @@ import type {
|
|
|
8
12
|
LinkPageWithItems,
|
|
9
13
|
} from "../types";
|
|
10
14
|
|
|
15
|
+
type RequiredSchema = typeof gigamusicDbSchema & typeof linksSchema;
|
|
16
|
+
|
|
17
|
+
/** Drizzle DB type the link-pages factory needs — a superset that includes both link-pages tables and `releases`. */
|
|
18
|
+
export type LinkPagesDb = NodePgDatabase<RequiredSchema>;
|
|
19
|
+
|
|
11
20
|
/**
|
|
12
|
-
* Factory that binds a
|
|
21
|
+
* Factory that binds a Drizzle database into the `LinkPageQueries` contract.
|
|
13
22
|
* Consumers typically merge the result with `@gigamusic/db`'s `createQueries`.
|
|
14
23
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
24
|
+
* The `db` instance must have been built with a schema that includes the
|
|
25
|
+
* union of `@gigamusic/db/schema` and `@gigamusic/links/schema`; otherwise
|
|
26
|
+
* the relational `with: { release: ... }` clause won't resolve at runtime.
|
|
17
27
|
*/
|
|
18
|
-
export function createLinkPageQueries(db:
|
|
28
|
+
export function createLinkPageQueries(db: LinkPagesDb): LinkPageQueries {
|
|
19
29
|
return {
|
|
20
30
|
listPublishedLinkPages: async (): Promise<LinkPage[]> => {
|
|
21
|
-
const rows = await db.
|
|
22
|
-
where:
|
|
23
|
-
orderBy:
|
|
31
|
+
const rows = await db.query.linkPages.findMany({
|
|
32
|
+
where: eq(linkPages.isPublished, true),
|
|
33
|
+
orderBy: desc(linkPages.updatedAt),
|
|
24
34
|
});
|
|
25
|
-
return rows
|
|
35
|
+
return rows;
|
|
26
36
|
},
|
|
27
37
|
|
|
28
38
|
listAllLinkPages: async (): Promise<LinkPage[]> => {
|
|
29
|
-
const rows = await db.
|
|
30
|
-
orderBy:
|
|
39
|
+
const rows = await db.query.linkPages.findMany({
|
|
40
|
+
orderBy: desc(linkPages.updatedAt),
|
|
31
41
|
});
|
|
32
|
-
return rows
|
|
42
|
+
return rows;
|
|
33
43
|
},
|
|
34
44
|
|
|
35
|
-
getPublicLinkPageBySlug: async (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
where: { slug, isPublished: true },
|
|
40
|
-
include: {
|
|
45
|
+
getPublicLinkPageBySlug: async (slug: string): Promise<LinkPageWithItems | null> => {
|
|
46
|
+
const row = await db.query.linkPages.findFirst({
|
|
47
|
+
where: and(eq(linkPages.slug, slug), eq(linkPages.isPublished, true)),
|
|
48
|
+
with: {
|
|
41
49
|
release: {
|
|
42
|
-
|
|
50
|
+
columns: {
|
|
43
51
|
id: true,
|
|
44
52
|
name: true,
|
|
45
53
|
slug: true,
|
|
@@ -48,20 +56,20 @@ export function createLinkPageQueries(db: PrismaClient): LinkPageQueries {
|
|
|
48
56
|
},
|
|
49
57
|
},
|
|
50
58
|
items: {
|
|
51
|
-
where:
|
|
52
|
-
orderBy:
|
|
59
|
+
where: eq(linkPageItems.isVisible, true),
|
|
60
|
+
orderBy: asc(linkPageItems.position),
|
|
53
61
|
},
|
|
54
62
|
},
|
|
55
63
|
});
|
|
56
|
-
return row as unknown as LinkPageWithItems | null;
|
|
64
|
+
return (row ?? null) as unknown as LinkPageWithItems | null;
|
|
57
65
|
},
|
|
58
66
|
|
|
59
67
|
getLinkPageById: async (id: number): Promise<LinkPageWithItems | null> => {
|
|
60
|
-
const row = await db.
|
|
61
|
-
where:
|
|
62
|
-
|
|
68
|
+
const row = await db.query.linkPages.findFirst({
|
|
69
|
+
where: eq(linkPages.id, id),
|
|
70
|
+
with: {
|
|
63
71
|
release: {
|
|
64
|
-
|
|
72
|
+
columns: {
|
|
65
73
|
id: true,
|
|
66
74
|
name: true,
|
|
67
75
|
slug: true,
|
|
@@ -69,67 +77,60 @@ export function createLinkPageQueries(db: PrismaClient): LinkPageQueries {
|
|
|
69
77
|
isPublished: true,
|
|
70
78
|
},
|
|
71
79
|
},
|
|
72
|
-
items: { orderBy:
|
|
80
|
+
items: { orderBy: asc(linkPageItems.position) },
|
|
73
81
|
},
|
|
74
82
|
});
|
|
75
|
-
return row as unknown as LinkPageWithItems | null;
|
|
83
|
+
return (row ?? null) as unknown as LinkPageWithItems | null;
|
|
76
84
|
},
|
|
77
85
|
|
|
78
86
|
createLinkPage: async (input: LinkPageInput): Promise<LinkPage> => {
|
|
79
|
-
const row = await db
|
|
80
|
-
|
|
87
|
+
const [row] = await db
|
|
88
|
+
.insert(linkPages)
|
|
89
|
+
.values({
|
|
81
90
|
title: input.title,
|
|
82
91
|
slug: input.slug,
|
|
83
92
|
description: input.description ?? null,
|
|
84
93
|
releaseId: input.releaseId ?? null,
|
|
85
94
|
coverImageUrl: input.coverImageUrl ?? null,
|
|
86
95
|
isPublished: input.isPublished ?? true,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return row
|
|
96
|
+
})
|
|
97
|
+
.returning();
|
|
98
|
+
return row!;
|
|
90
99
|
},
|
|
91
100
|
|
|
92
|
-
updateLinkPage: async (
|
|
93
|
-
id: number,
|
|
94
|
-
input: Partial<LinkPageInput>,
|
|
95
|
-
): Promise<LinkPage> => {
|
|
101
|
+
updateLinkPage: async (id: number, input: Partial<LinkPageInput>): Promise<LinkPage> => {
|
|
96
102
|
const data: Record<string, unknown> = {};
|
|
97
103
|
if (input.title !== undefined) data.title = input.title;
|
|
98
104
|
if (input.slug !== undefined) data.slug = input.slug;
|
|
99
105
|
if (input.description !== undefined) data.description = input.description;
|
|
100
106
|
if (input.releaseId !== undefined) data.releaseId = input.releaseId;
|
|
101
|
-
if (input.coverImageUrl !== undefined)
|
|
102
|
-
data.coverImageUrl = input.coverImageUrl;
|
|
107
|
+
if (input.coverImageUrl !== undefined) data.coverImageUrl = input.coverImageUrl;
|
|
103
108
|
if (input.isPublished !== undefined) data.isPublished = input.isPublished;
|
|
104
|
-
const row = await db.
|
|
105
|
-
|
|
106
|
-
data,
|
|
107
|
-
});
|
|
108
|
-
return row as unknown as LinkPage;
|
|
109
|
+
const [row] = await db.update(linkPages).set(data).where(eq(linkPages.id, id)).returning();
|
|
110
|
+
return row!;
|
|
109
111
|
},
|
|
110
112
|
|
|
111
113
|
deleteLinkPage: async (id: number): Promise<void> => {
|
|
112
|
-
await db.
|
|
114
|
+
await db.delete(linkPages).where(eq(linkPages.id, id));
|
|
113
115
|
},
|
|
114
116
|
|
|
115
|
-
addLinkPageItem: async (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
data: {
|
|
117
|
+
addLinkPageItem: async (pageId: number, input: LinkPageItemInput): Promise<LinkPageItem> => {
|
|
118
|
+
const [maxRow] = await db
|
|
119
|
+
.select({ max: max(linkPageItems.position) })
|
|
120
|
+
.from(linkPageItems)
|
|
121
|
+
.where(eq(linkPageItems.pageId, pageId));
|
|
122
|
+
const nextPosition = input.position ?? ((maxRow?.max ?? -1) + 1);
|
|
123
|
+
const [row] = await db
|
|
124
|
+
.insert(linkPageItems)
|
|
125
|
+
.values({
|
|
125
126
|
pageId,
|
|
126
127
|
title: input.title,
|
|
127
128
|
url: input.url,
|
|
128
|
-
position:
|
|
129
|
+
position: nextPosition,
|
|
129
130
|
isVisible: input.isVisible ?? true,
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return row
|
|
131
|
+
})
|
|
132
|
+
.returning();
|
|
133
|
+
return row!;
|
|
133
134
|
},
|
|
134
135
|
|
|
135
136
|
updateLinkPageItem: async (
|
|
@@ -141,15 +142,12 @@ export function createLinkPageQueries(db: PrismaClient): LinkPageQueries {
|
|
|
141
142
|
if (input.url !== undefined) data.url = input.url;
|
|
142
143
|
if (input.position !== undefined) data.position = input.position;
|
|
143
144
|
if (input.isVisible !== undefined) data.isVisible = input.isVisible;
|
|
144
|
-
const row = await db.
|
|
145
|
-
|
|
146
|
-
data,
|
|
147
|
-
});
|
|
148
|
-
return row as unknown as LinkPageItem;
|
|
145
|
+
const [row] = await db.update(linkPageItems).set(data).where(eq(linkPageItems.id, id)).returning();
|
|
146
|
+
return row!;
|
|
149
147
|
},
|
|
150
148
|
|
|
151
149
|
deleteLinkPageItem: async (id: number): Promise<void> => {
|
|
152
|
-
await db.
|
|
150
|
+
await db.delete(linkPageItems).where(eq(linkPageItems.id, id));
|
|
153
151
|
},
|
|
154
152
|
|
|
155
153
|
/**
|
|
@@ -158,18 +156,15 @@ export function createLinkPageQueries(db: PrismaClient): LinkPageQueries {
|
|
|
158
156
|
* their current position — callers must include the full set to fully
|
|
159
157
|
* resort.
|
|
160
158
|
*/
|
|
161
|
-
reorderLinkPageItems: async (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}),
|
|
171
|
-
),
|
|
172
|
-
);
|
|
159
|
+
reorderLinkPageItems: async (pageId: number, orderedItemIds: number[]): Promise<void> => {
|
|
160
|
+
await db.transaction(async (tx) => {
|
|
161
|
+
for (let position = 0; position < orderedItemIds.length; position++) {
|
|
162
|
+
await tx
|
|
163
|
+
.update(linkPageItems)
|
|
164
|
+
.set({ position })
|
|
165
|
+
.where(and(eq(linkPageItems.id, orderedItemIds[position]!), eq(linkPageItems.pageId, pageId)));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
173
168
|
},
|
|
174
169
|
};
|
|
175
170
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { boolean, index, integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
2
|
+
import { releases } from "@gigamusic/db/schema";
|
|
3
|
+
|
|
4
|
+
export const linkPageColumns = {
|
|
5
|
+
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
|
6
|
+
slug: text("slug").notNull().unique(),
|
|
7
|
+
title: text("title").notNull(),
|
|
8
|
+
description: text("description"),
|
|
9
|
+
coverImageUrl: text("coverImageUrl"),
|
|
10
|
+
releaseId: integer("releaseId").references(() => releases.id, { onDelete: "set null" }),
|
|
11
|
+
isPublished: boolean("isPublished").notNull().default(true),
|
|
12
|
+
createdAt: timestamp("createdAt", { mode: "date", precision: 3 }).notNull().defaultNow(),
|
|
13
|
+
updatedAt: timestamp("updatedAt", { mode: "date", precision: 3 })
|
|
14
|
+
.notNull()
|
|
15
|
+
.$defaultFn(() => new Date())
|
|
16
|
+
.$onUpdateFn(() => new Date()),
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export const linkPages = pgTable("link_pages", linkPageColumns, (t) => [
|
|
20
|
+
index("link_pages_releaseId_idx").on(t.releaseId),
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const linkPageItemColumns = {
|
|
24
|
+
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
|
25
|
+
pageId: integer("pageId")
|
|
26
|
+
.notNull()
|
|
27
|
+
.references(() => linkPages.id, { onDelete: "cascade" }),
|
|
28
|
+
title: text("title").notNull(),
|
|
29
|
+
url: text("url").notNull(),
|
|
30
|
+
position: integer("position").notNull().default(0),
|
|
31
|
+
isVisible: boolean("isVisible").notNull().default(true),
|
|
32
|
+
createdAt: timestamp("createdAt", { mode: "date", precision: 3 }).notNull().defaultNow(),
|
|
33
|
+
updatedAt: timestamp("updatedAt", { mode: "date", precision: 3 })
|
|
34
|
+
.notNull()
|
|
35
|
+
.$defaultFn(() => new Date())
|
|
36
|
+
.$onUpdateFn(() => new Date()),
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
export const linkPageItems = pgTable("link_page_items", linkPageItemColumns, (t) => [
|
|
40
|
+
index("link_page_items_pageId_idx").on(t.pageId),
|
|
41
|
+
]);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { relations } from "drizzle-orm";
|
|
2
|
+
import { releases } from "@gigamusic/db/schema";
|
|
3
|
+
import { linkPages, linkPageItems } from "./link-pages.js";
|
|
4
|
+
|
|
5
|
+
export const linkPagesRelations = relations(linkPages, ({ one, many }) => ({
|
|
6
|
+
release: one(releases, { fields: [linkPages.releaseId], references: [releases.id] }),
|
|
7
|
+
items: many(linkPageItems),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
export const linkPageItemsRelations = relations(linkPageItems, ({ one }) => ({
|
|
11
|
+
page: one(linkPages, { fields: [linkPageItems.pageId], references: [linkPages.id] }),
|
|
12
|
+
}));
|
package/src/server.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import type { LinkPageInput, LinkPageItemInput } from "@gigamusic/core";
|
|
2
|
+
import type { linkPageItems, linkPages } from "./schema/index.js";
|
|
2
3
|
|
|
3
4
|
export type { LinkPageInput, LinkPageItemInput };
|
|
4
5
|
|
|
5
6
|
/** A single link button rendered inside a `LinkPage`. */
|
|
6
|
-
export
|
|
7
|
-
id: number;
|
|
8
|
-
pageId: number;
|
|
9
|
-
title: string;
|
|
10
|
-
url: string;
|
|
11
|
-
position: number;
|
|
12
|
-
isVisible: boolean;
|
|
13
|
-
createdAt: Date;
|
|
14
|
-
updatedAt: Date;
|
|
15
|
-
}
|
|
7
|
+
export type LinkPageItem = typeof linkPageItems.$inferSelect;
|
|
16
8
|
|
|
17
9
|
/** Optional `Release` reference exposed to the public view for cover fallback. */
|
|
18
10
|
export interface LinkPageReleaseRef {
|
|
@@ -23,18 +15,8 @@ export interface LinkPageReleaseRef {
|
|
|
23
15
|
isPublished: boolean;
|
|
24
16
|
}
|
|
25
17
|
|
|
26
|
-
/** Top-level "Linktree-style" page row.
|
|
27
|
-
export
|
|
28
|
-
id: number;
|
|
29
|
-
slug: string;
|
|
30
|
-
title: string;
|
|
31
|
-
description: string | null;
|
|
32
|
-
coverImageUrl: string | null;
|
|
33
|
-
releaseId: number | null;
|
|
34
|
-
isPublished: boolean;
|
|
35
|
-
createdAt: Date;
|
|
36
|
-
updatedAt: Date;
|
|
37
|
-
}
|
|
18
|
+
/** Top-level "Linktree-style" page row. */
|
|
19
|
+
export type LinkPage = typeof linkPages.$inferSelect;
|
|
38
20
|
|
|
39
21
|
/** A `LinkPage` joined with its visible (or all) items and an optional release ref. */
|
|
40
22
|
export interface LinkPageWithItems extends LinkPage {
|
|
@@ -43,7 +25,7 @@ export interface LinkPageWithItems extends LinkPage {
|
|
|
43
25
|
}
|
|
44
26
|
|
|
45
27
|
/**
|
|
46
|
-
* Query helpers that close over a
|
|
28
|
+
* Query helpers that close over a Drizzle DB. Consumers merge this with
|
|
47
29
|
* `@gigamusic/db`'s `Queries` factory result (or pass it through wherever a
|
|
48
30
|
* `LinkPageQueries` is needed).
|
|
49
31
|
*/
|
package/prisma/_typegen.prisma
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Internal — exists ONLY so `prisma generate --schema=./prisma` in THIS
|
|
2
|
-
// package can produce a PrismaClient with LinkPage + LinkPageItem on the
|
|
3
|
-
// delegate. The `gigamusic-links sync` CLI skips files starting with `_`,
|
|
4
|
-
// so this is never copied into a consumer app. The consumer's merged
|
|
5
|
-
// schema gets `Release` from `@gigamusic/db`; here we declare a minimal
|
|
6
|
-
// stub so the link-page.prisma relation compiles in isolation.
|
|
7
|
-
|
|
8
|
-
datasource db {
|
|
9
|
-
provider = "postgresql"
|
|
10
|
-
url = env("DATABASE_URL")
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
generator client {
|
|
14
|
-
provider = "prisma-client-js"
|
|
15
|
-
binaryTargets = ["native"]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/// Minimal stub of @gigamusic/db's `Release` model — only the fields this
|
|
19
|
-
/// package's queries select. The consumer's merged schema redefines `Release`
|
|
20
|
-
/// with all its fields; this stub only needs to satisfy the link-page relation
|
|
21
|
-
/// and the columns we read.
|
|
22
|
-
model Release {
|
|
23
|
-
id Int @id @default(autoincrement())
|
|
24
|
-
name String
|
|
25
|
-
slug String @unique
|
|
26
|
-
coverImageUrl String?
|
|
27
|
-
isPublished Boolean @default(false)
|
|
28
|
-
linkPages LinkPage[]
|
|
29
|
-
}
|
package/prisma/link-page.prisma
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// gigamusic link-page schema fragment
|
|
2
|
-
//
|
|
3
|
-
// Shipped by `@gigamusic/links` and copied into a consumer app's
|
|
4
|
-
// `prisma/schema/` directory by `gigamusic-links sync` with a `20-` prefix
|
|
5
|
-
// (after `10-` fragments from @gigamusic/db, before consumer-owned `99-`).
|
|
6
|
-
//
|
|
7
|
-
// `LinkPage` references the `Release` model from @gigamusic/db via the
|
|
8
|
-
// `releaseId` scalar foreign key. Prisma requires *both* sides of every
|
|
9
|
-
// relation to be declared, so for the `release Release?` relation to compile
|
|
10
|
-
// the consumer must extend the upstream `Release` model with:
|
|
11
|
-
//
|
|
12
|
-
// model Release {
|
|
13
|
-
// // ...existing fields...
|
|
14
|
-
// linkPages LinkPage[]
|
|
15
|
-
// }
|
|
16
|
-
//
|
|
17
|
-
// The demo + create-gigamusic-app scaffolding wire this up automatically.
|
|
18
|
-
//
|
|
19
|
-
// Reserved model names: LinkPage, LinkPageItem
|
|
20
|
-
// Reserved table names: link_pages, link_page_items
|
|
21
|
-
|
|
22
|
-
model LinkPage {
|
|
23
|
-
id Int @id @default(autoincrement())
|
|
24
|
-
slug String @unique
|
|
25
|
-
title String
|
|
26
|
-
description String?
|
|
27
|
-
coverImageUrl String?
|
|
28
|
-
releaseId Int?
|
|
29
|
-
isPublished Boolean @default(true)
|
|
30
|
-
createdAt DateTime @default(now())
|
|
31
|
-
updatedAt DateTime @updatedAt
|
|
32
|
-
|
|
33
|
-
release Release? @relation(fields: [releaseId], references: [id], onDelete: SetNull)
|
|
34
|
-
items LinkPageItem[]
|
|
35
|
-
|
|
36
|
-
@@index([releaseId])
|
|
37
|
-
@@map("link_pages")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
model LinkPageItem {
|
|
41
|
-
id Int @id @default(autoincrement())
|
|
42
|
-
pageId Int
|
|
43
|
-
title String
|
|
44
|
-
url String
|
|
45
|
-
position Int @default(0)
|
|
46
|
-
isVisible Boolean @default(true)
|
|
47
|
-
createdAt DateTime @default(now())
|
|
48
|
-
updatedAt DateTime @updatedAt
|
|
49
|
-
|
|
50
|
-
page LinkPage @relation(fields: [pageId], references: [id], onDelete: Cascade)
|
|
51
|
-
|
|
52
|
-
@@index([pageId])
|
|
53
|
-
@@map("link_page_items")
|
|
54
|
-
}
|