Adding the first stubs of the magazine browser
This commit is contained in:
225
.output.txt
225
.output.txt
@@ -1,225 +0,0 @@
|
|||||||
▲ Next.js 15.5.9 (Turbopack)
|
|
||||||
- Environments: .env
|
|
||||||
Creating an optimized production build ...
|
|
||||||
✓ Finished writing to disk in 48ms
|
|
||||||
Turbopack build encountered 21 warnings:
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: 311 repetitive deprecation warnings omitted.
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 0, column 8 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/bootstrap.scss:0:8:
|
|
||||||
Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
0 | @import "mixins/banner";
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 1:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 10, column 29 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:10:29:
|
|
||||||
Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
Use math.unit instead.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
10 | @if $prev-num == null or unit($num) == "%" or unit($prev-num) == "%" {
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 11:30 -assert-ascending()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 494:1 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 10, column 50 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:10:50:
|
|
||||||
Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
Use math.unit instead.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
10 | @if $prev-num == null or unit($num) == "%" or unit($prev-num) == "%" {
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 11:51 -assert-ascending()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 494:1 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 10, column 8 of file:///Volumes/McFiver/u/GIT/next-explorer/src/scss/nbn.scss:10:8:
|
|
||||||
Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
10 | @import "bootswatch";
|
|
||||||
src/scss/nbn.scss 11:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 12, column 8 of file:///Volumes/McFiver/u/GIT/next-explorer/src/scss/nbn.scss:12:8:
|
|
||||||
Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
12 | @import "explorer";
|
|
||||||
src/scss/nbn.scss 13:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 176, column 10 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:176:10:
|
|
||||||
The Sass if() syntax is deprecated in favor of the modern CSS syntax.
|
|
||||||
Suggestion: if(sass($l1 > $l2): divide($l1 + 0.05, $l2 + 0.05); else: divide($l2 + 0.05, $l1 + 0.05))
|
|
||||||
More info: https://sass-lang.com/d/if-function
|
|
||||||
176 | @return if($l1 > $l2, divide($l1 + .05, $l2 + .05), divide($l2 + .05, $l1 + .05));
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 177:11 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 7:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 184, column 9 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:184:9:
|
|
||||||
red() is deprecated. Suggestion:
|
|
||||||
color.channel($color, "red", $space: rgb)
|
|
||||||
More info: https://sass-lang.com/d/color-functions
|
|
||||||
184 | "r": red($color),
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 185:10 luminance()
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 174:8 contrast-ratio()
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 159:22 color-contrast()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 846:42 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 185, column 9 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:185:9:
|
|
||||||
green() is deprecated. Suggestion:
|
|
||||||
color.channel($color, "green", $space: rgb)
|
|
||||||
More info: https://sass-lang.com/d/color-functions
|
|
||||||
185 | "g": green($color),
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 186:10 luminance()
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 174:8 contrast-ratio()
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 159:22 color-contrast()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 846:42 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 186, column 9 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:186:9:
|
|
||||||
blue() is deprecated. Suggestion:
|
|
||||||
color.channel($color, "blue", $space: rgb)
|
|
||||||
More info: https://sass-lang.com/d/color-functions
|
|
||||||
186 | "b": blue($color)
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 187:10 luminance()
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 174:8 contrast-ratio()
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 159:22 color-contrast()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 846:42 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 190, column 12 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:190:12:
|
|
||||||
The Sass if() syntax is deprecated in favor of the modern CSS syntax.
|
|
||||||
Suggestion: if(sass(divide($value, 255) < 0.04045): divide(divide($value, 255), 12.92); else: nth($_luminance-list, $value + 1))
|
|
||||||
More info: https://sass-lang.com/d/if-function
|
|
||||||
190 | $value: if(divide($value, 255) < .04045, divide(divide($value, 255), 12.92), nth($_luminance-list, $value + 1));
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 191:13 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 7:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 206, column 10 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:206:10:
|
|
||||||
Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
Use color.mix instead.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
206 | @return mix(white, $color, $weight);
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 207:11 tint-color()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 79:12 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 211, column 10 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:211:10:
|
|
||||||
Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
Use color.mix instead.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
211 | @return mix(black, $color, $weight);
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 212:11 shade-color()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 84:12 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 216, column 10 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:216:10:
|
|
||||||
The Sass if() syntax is deprecated in favor of the modern CSS syntax.
|
|
||||||
Suggestion: if(sass($weight > 0): shade-color($color, $weight); else: tint-color($color, -$weight))
|
|
||||||
More info: https://sass-lang.com/d/if-function
|
|
||||||
216 | @return if($weight > 0, shade-color($color, $weight), tint-color($color, -$weight));
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 217:11 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 7:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 341, column 26 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_variables.scss:341:26:
|
|
||||||
Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
Use color.mix instead.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
341 | $light-bg-subtle: mix($gray-100, $white) !default;
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 342:27 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 36, column 10 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:36:10:
|
|
||||||
red() is deprecated. Suggestion:
|
|
||||||
color.channel($color, "red", $space: rgb)
|
|
||||||
More info: https://sass-lang.com/d/color-functions
|
|
||||||
36 | @return red($value), green($value), blue($value);
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 37:11 to-rgb()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 846:31 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 36, column 23 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:36:23:
|
|
||||||
green() is deprecated. Suggestion:
|
|
||||||
color.channel($color, "green", $space: rgb)
|
|
||||||
More info: https://sass-lang.com/d/color-functions
|
|
||||||
36 | @return red($value), green($value), blue($value);
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 37:24 to-rgb()
|
|
||||||
node_modules/bootstrap/scss/_variables.scss 846:31 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 8:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 57, column 29 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:57:29:
|
|
||||||
The Sass if() syntax is deprecated in favor of the modern CSS syntax.
|
|
||||||
Suggestion: if(sass($arg == "$key"): $key; else: if($arg == "$value", $value, $arg))
|
|
||||||
More info: https://sass-lang.com/d/if-function
|
|
||||||
57 | $_args: append($_args, if($arg == "$key", $key, if($arg == "$value", $value, $arg)));
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 58:30 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 7:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 57, column 54 of file:///Volumes/McFiver/u/GIT/next-explorer/node_modules/bootstrap/scss/_functions.scss:57:54:
|
|
||||||
The Sass if() syntax is deprecated in favor of the modern CSS syntax.
|
|
||||||
Suggestion: if(sass($arg == "$value"): $value; else: $arg)
|
|
||||||
More info: https://sass-lang.com/d/if-function
|
|
||||||
57 | $_args: append($_args, if($arg == "$key", $key, if($arg == "$value", $value, $arg)));
|
|
||||||
node_modules/bootstrap/scss/_functions.scss 58:55 @import
|
|
||||||
node_modules/bootstrap/scss/bootstrap.scss 7:9 @import
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 6, column 8 of file:///Volumes/McFiver/u/GIT/next-explorer/src/scss/nbn.scss:6:8:
|
|
||||||
Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
6 | @import "variables";
|
|
||||||
src/scss/nbn.scss 7:9 root stylesheet
|
|
||||||
./src/scss/nbn.scss
|
|
||||||
Issue while running loader
|
|
||||||
SassWarning: Deprecation Warning on line 8, column 8 of file:///Volumes/McFiver/u/GIT/next-explorer/src/scss/nbn.scss:8:8:
|
|
||||||
Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
|
|
||||||
More info and automated migrator: https://sass-lang.com/d/import
|
|
||||||
8 | @import "../../node_modules/bootstrap/scss/bootstrap";
|
|
||||||
src/scss/nbn.scss 9:9 root stylesheet
|
|
||||||
✓ Compiled successfully in 3.7s
|
|
||||||
./src/app/zxdb/releases/ReleasesExplorer.tsx
|
|
||||||
142:6 Warning: React Hook useEffect has missing dependencies: 'fetchData', 'initial', 'initialUrlState?.casetypeId', 'initialUrlState?.dLanguageId', 'initialUrlState?.dMachinetypeId', 'initialUrlState?.filetypeId', 'initialUrlState?.isDemo', 'initialUrlState?.q', 'initialUrlState?.schemetypeId', 'initialUrlState?.sort', 'initialUrlState?.sourcetypeId', 'initialUrlState?.year', 'q', and 'updateUrl'. Either include them or remove the dependency array. react-hooks/exhaustive-deps
|
|
||||||
info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/app/api-reference/config/eslint#disabling-rules
|
|
||||||
Failed to compile.
|
|
||||||
./src/server/repo/zxdb.ts:491:17
|
|
||||||
Type error: Argument of type 'Name' is not assignable to parameter of type 'SQL<unknown> | Column<ColumnBaseConfig<ColumnDataType, string>, object, object> | Aliased<unknown>'.
|
|
||||||
Type 'Name' is missing the following properties from type 'Aliased<unknown>': sql, fieldAlias, _
|
|
||||||
489 | .select({ total: sql<number>`count(distinct ${sql.identifier("label_id")})` })
|
|
||||||
490 | .from(sql`search_by_names`)
|
|
||||||
> 491 | .where(like(sql.identifier("label_name"), pattern));
|
|
||||||
| ^
|
|
||||||
492 | const total = Number(countRows[0]?.total ?? 0);
|
|
||||||
493 |
|
|
||||||
494 | const items = await db
|
|
||||||
Next.js build worker exited with code: 1 and signal: null
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Link entry_id across UI; surface aliases/webrefs on Entry\n\n- Add EntryLink component for /zxdb/entries/[id]\n- Use EntryLink in Entries, Releases, and Label detail tables\n- Extend Entry detail with Aliases and Web links sections\n- Add Drizzle schema for aliases, webrefs, websites; fetch in repo\n\nSigned-off-by: Junie@lucy\n
|
|
||||||
11
example.env
11
example.env
@@ -5,3 +5,14 @@
|
|||||||
# See docs/ZXDB.md for full setup instructions (DB import, helper tables,
|
# See docs/ZXDB.md for full setup instructions (DB import, helper tables,
|
||||||
# readonly role, and environment validation notes).
|
# readonly role, and environment validation notes).
|
||||||
ZXDB_URL=mysql://zxdb_readonly:password@hostname:3306/zxdb
|
ZXDB_URL=mysql://zxdb_readonly:password@hostname:3306/zxdb
|
||||||
|
|
||||||
|
# Base HTTP locations for CDN sources used by downloads.file_link
|
||||||
|
# When file_link starts with /zxdb, it will be fetched from ZXDB_FILEPATH
|
||||||
|
ZXDB_FILEPATH=https://zxdbfiles.com/
|
||||||
|
|
||||||
|
# When file_link starts with /public, it will be fetched from WOS_FILEPATH
|
||||||
|
# Note: Example uses the Internet Archive WoS mirror; keep the trailing slash
|
||||||
|
WOS_FILEPATH=https://archive.org/download/World_of_Spectrum_June_2017_Mirror/World%20of%20Spectrum%20June%202017%20Mirror.zip/World%20of%20Spectrum%20June%202017%20Mirror/
|
||||||
|
|
||||||
|
# Local cache root where files will be mirrored (without the leading slash)
|
||||||
|
CDN_CACHE=/mnt/files/zxfiles
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"dotenv-expand": "^11.0.7",
|
||||||
"drizzle-orm": "^0.36.4",
|
"drizzle-orm": "^0.36.4",
|
||||||
"mysql2": "^3.16.0",
|
"mysql2": "^3.16.0",
|
||||||
"next": "~15.5.9",
|
"next": "~15.5.9",
|
||||||
@@ -27,9 +29,9 @@
|
|||||||
"@types/node": "^20.19.27",
|
"@types/node": "^20.19.27",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"drizzle-kit": "^0.30.6",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-config-next": "15.5.4",
|
"eslint-config-next": "15.5.4",
|
||||||
"drizzle-kit": "^0.30.6",
|
|
||||||
"sass": "^1.97.0"
|
"sass": "^1.97.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -11,6 +11,12 @@ importers:
|
|||||||
bootstrap:
|
bootstrap:
|
||||||
specifier: ^5.3.8
|
specifier: ^5.3.8
|
||||||
version: 5.3.8(@popperjs/core@2.11.8)
|
version: 5.3.8(@popperjs/core@2.11.8)
|
||||||
|
dotenv:
|
||||||
|
specifier: ^17.2.3
|
||||||
|
version: 17.2.3
|
||||||
|
dotenv-expand:
|
||||||
|
specifier: ^11.0.7
|
||||||
|
version: 11.0.7
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.36.4
|
specifier: ^0.36.4
|
||||||
version: 0.36.4(@types/react@19.2.7)(mysql2@3.16.0)(react@19.1.0)
|
version: 0.36.4(@types/react@19.2.7)(mysql2@3.16.0)(react@19.1.0)
|
||||||
@@ -1151,6 +1157,18 @@ packages:
|
|||||||
dom-helpers@5.2.1:
|
dom-helpers@5.2.1:
|
||||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||||
|
|
||||||
|
dotenv-expand@11.0.7:
|
||||||
|
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
dotenv@16.6.1:
|
||||||
|
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
dotenv@17.2.3:
|
||||||
|
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
drizzle-kit@0.30.6:
|
drizzle-kit@0.30.6:
|
||||||
resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==}
|
resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -3159,6 +3177,14 @@ snapshots:
|
|||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
dotenv-expand@11.0.7:
|
||||||
|
dependencies:
|
||||||
|
dotenv: 16.6.1
|
||||||
|
|
||||||
|
dotenv@16.6.1: {}
|
||||||
|
|
||||||
|
dotenv@17.2.3: {}
|
||||||
|
|
||||||
drizzle-kit@0.30.6:
|
drizzle-kit@0.30.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@drizzle-team/brocli': 0.10.2
|
'@drizzle-team/brocli': 0.10.2
|
||||||
|
|||||||
79
src/app/zxdb/issues/[id]/page.tsx
Normal file
79
src/app/zxdb/issues/[id]/page.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { getIssue } from "@/server/repo/zxdb";
|
||||||
|
import EntryLink from "@/app/zxdb/components/EntryLink";
|
||||||
|
|
||||||
|
export const metadata = { title: "ZXDB Issue" };
|
||||||
|
export const revalidate = 3600;
|
||||||
|
|
||||||
|
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||||
|
const { id } = await params;
|
||||||
|
const issueId = Number(id);
|
||||||
|
if (!Number.isFinite(issueId) || issueId <= 0) return notFound();
|
||||||
|
|
||||||
|
const issue = await getIssue(issueId);
|
||||||
|
if (!issue) return notFound();
|
||||||
|
|
||||||
|
const ym = [issue.dateYear ?? "", issue.dateMonth ? String(issue.dateMonth).padStart(2, "0") : ""].filter(Boolean).join("/");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-3 d-flex gap-2 flex-wrap">
|
||||||
|
<Link className="btn btn-outline-secondary btn-sm" href={`/zxdb/magazines/${issue.magazine.id}`}>← Back to magazine</Link>
|
||||||
|
<Link className="btn btn-outline-secondary btn-sm" href="/zxdb/magazines">All magazines</Link>
|
||||||
|
{issue.linkMask && (
|
||||||
|
<a className="btn btn-outline-secondary btn-sm" href={issue.linkMask} target="_blank" rel="noreferrer">Issue link</a>
|
||||||
|
)}
|
||||||
|
{issue.archiveMask && (
|
||||||
|
<a className="btn btn-outline-secondary btn-sm" href={issue.archiveMask} target="_blank" rel="noreferrer">Archive</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="mb-1">{issue.magazine.title}</h1>
|
||||||
|
<div className="text-secondary mb-3">
|
||||||
|
Issue: {ym || issue.id}{issue.volume != null ? ` · Vol ${issue.volume}` : ""}{issue.number != null ? ` · No ${issue.number}` : ""}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(issue.special || issue.supplement) && (
|
||||||
|
<div className="mb-3">
|
||||||
|
{issue.special && <div><strong>Special:</strong> {issue.special}</div>}
|
||||||
|
{issue.supplement && <div><strong>Supplement:</strong> {issue.supplement}</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h2 className="h5 mt-4">References</h2>
|
||||||
|
{issue.refs.length === 0 ? (
|
||||||
|
<div className="text-secondary">No references recorded.</div>
|
||||||
|
) : (
|
||||||
|
<div className="table-responsive">
|
||||||
|
<table className="table table-sm align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width: 80 }}>Page</th>
|
||||||
|
<th style={{ width: 140 }}>Type</th>
|
||||||
|
<th>Reference</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{issue.refs.map((r) => (
|
||||||
|
<tr key={r.id}>
|
||||||
|
<td>{r.page}</td>
|
||||||
|
<td>{r.typeName}</td>
|
||||||
|
<td>
|
||||||
|
{r.entryId ? (
|
||||||
|
<EntryLink id={r.entryId} title={r.entryTitle ?? undefined} />
|
||||||
|
) : r.labelId ? (
|
||||||
|
<Link href={`/zxdb/labels/${r.labelId}`}>{r.labelName ?? r.labelId}</Link>
|
||||||
|
) : (
|
||||||
|
<span className="text-secondary">—</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
85
src/app/zxdb/magazines/[id]/page.tsx
Normal file
85
src/app/zxdb/magazines/[id]/page.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { getMagazine } from "@/server/repo/zxdb";
|
||||||
|
|
||||||
|
export const metadata = { title: "ZXDB Magazine" };
|
||||||
|
export const revalidate = 3600;
|
||||||
|
|
||||||
|
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||||
|
const { id } = await params;
|
||||||
|
const magazineId = Number(id);
|
||||||
|
if (!Number.isFinite(magazineId) || magazineId <= 0) return notFound();
|
||||||
|
|
||||||
|
const mag = await getMagazine(magazineId);
|
||||||
|
if (!mag) return notFound();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="mb-1">{mag.title}</h1>
|
||||||
|
<div className="text-secondary mb-3">Language: {mag.languageId}</div>
|
||||||
|
|
||||||
|
<div className="mb-3 d-flex gap-2 flex-wrap">
|
||||||
|
<Link className="btn btn-outline-secondary btn-sm" href="/zxdb/magazines">← Back to list</Link>
|
||||||
|
{mag.linkSite && (
|
||||||
|
<a className="btn btn-outline-secondary btn-sm" href={mag.linkSite} target="_blank" rel="noreferrer">
|
||||||
|
Official site
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="h5 mt-4">Issues</h2>
|
||||||
|
{mag.issues.length === 0 ? (
|
||||||
|
<div className="text-secondary">No issues found.</div>
|
||||||
|
) : (
|
||||||
|
<div className="table-responsive">
|
||||||
|
<table className="table table-sm align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width: 200 }}>Issue</th>
|
||||||
|
<th style={{ width: 100 }}>Volume</th>
|
||||||
|
<th style={{ width: 100 }}>Number</th>
|
||||||
|
<th>Special</th>
|
||||||
|
<th>Supplement</th>
|
||||||
|
<th style={{ width: 100 }}>Links</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{mag.issues.map((i) => (
|
||||||
|
<tr key={i.id}>
|
||||||
|
<td>
|
||||||
|
<Link href={`/zxdb/issues/${i.id}`} className="link-underline link-underline-opacity-0">
|
||||||
|
{i.dateYear ?? ""}
|
||||||
|
{i.dateMonth ? `/${String(i.dateMonth).padStart(2, "0")}` : ""}
|
||||||
|
{" "}
|
||||||
|
<span className="text-secondary">(open issue)</span>
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td>{i.volume ?? ""}</td>
|
||||||
|
<td>{i.number ?? ""}</td>
|
||||||
|
<td>{i.special ?? ""}</td>
|
||||||
|
<td>{i.supplement ?? ""}</td>
|
||||||
|
<td>
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
{i.linkMask && (
|
||||||
|
<a className="btn btn-outline-secondary btn-sm" href={i.linkMask} target="_blank" rel="noreferrer" title="Link">
|
||||||
|
<span className="bi bi-link-45deg" aria-hidden />
|
||||||
|
<span className="visually-hidden">Link</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{i.archiveMask && (
|
||||||
|
<a className="btn btn-outline-secondary btn-sm" href={i.archiveMask} target="_blank" rel="noreferrer" title="Archive">
|
||||||
|
<span className="bi bi-archive" aria-hidden />
|
||||||
|
<span className="visually-hidden">Archive</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
81
src/app/zxdb/magazines/page.tsx
Normal file
81
src/app/zxdb/magazines/page.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { listMagazines } from "@/server/repo/zxdb";
|
||||||
|
|
||||||
|
export const metadata = { title: "ZXDB Magazines" };
|
||||||
|
|
||||||
|
// Depends on searchParams (?q=, ?page=). Force dynamic so each request renders correctly.
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export default async function Page({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
|
}) {
|
||||||
|
const sp = await searchParams;
|
||||||
|
const q = (Array.isArray(sp.q) ? sp.q[0] : sp.q) ?? "";
|
||||||
|
const page = Math.max(1, Number(Array.isArray(sp.page) ? sp.page[0] : sp.page) || 1);
|
||||||
|
|
||||||
|
const data = await listMagazines({ q, page, pageSize: 20 });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="mb-3">Magazines</h1>
|
||||||
|
|
||||||
|
<form className="mb-3" action="/zxdb/magazines" method="get">
|
||||||
|
<div className="input-group">
|
||||||
|
<input type="text" className="form-control" name="q" placeholder="Search magazines..." defaultValue={q} />
|
||||||
|
<button className="btn btn-outline-secondary" type="submit">
|
||||||
|
<span className="bi bi-search" aria-hidden />
|
||||||
|
<span className="visually-hidden">Search</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="list-group">
|
||||||
|
{data.items.map((m) => (
|
||||||
|
<Link key={m.id} className="list-group-item list-group-item-action d-flex justify-content-between align-items-center" href={`/zxdb/magazines/${m.id}`}>
|
||||||
|
<span>
|
||||||
|
{m.title}
|
||||||
|
<span className="text-secondary ms-2">({m.languageId})</span>
|
||||||
|
</span>
|
||||||
|
<span className="badge bg-secondary rounded-pill" title="Issues">
|
||||||
|
{m.issueCount}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Pagination page={data.page} pageSize={data.pageSize} total={data.total} q={q} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Pagination({ page, pageSize, total, q }: { page: number; pageSize: number; total: number; q: string }) {
|
||||||
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
const makeHref = (p: number) => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (q) params.set("q", q);
|
||||||
|
params.set("page", String(p));
|
||||||
|
return `/zxdb/magazines?${params.toString()}`;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<nav className="mt-3" aria-label="Pagination">
|
||||||
|
<ul className="pagination">
|
||||||
|
<li className={`page-item ${page <= 1 ? "disabled" : ""}`}>
|
||||||
|
<Link className="page-link" href={makeHref(Math.max(1, page - 1))}>
|
||||||
|
Previous
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="page-item disabled">
|
||||||
|
<span className="page-link">Page {page} of {totalPages}</span>
|
||||||
|
</li>
|
||||||
|
<li className={`page-item ${page >= totalPages ? "disabled" : ""}`}>
|
||||||
|
<Link className="page-link" href={makeHref(Math.min(totalPages, page + 1))}>
|
||||||
|
Next
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -44,6 +44,22 @@ export default async function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="col-sm-6 col-lg-4">
|
||||||
|
<Link href="/zxdb/magazines" className="text-decoration-none">
|
||||||
|
<div className="card h-100 shadow-sm">
|
||||||
|
<div className="card-body d-flex align-items-center">
|
||||||
|
<div className="me-3" aria-hidden>
|
||||||
|
<span className="bi bi-journal-text" style={{ fontSize: 28 }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 className="card-title mb-1">Magazines</h5>
|
||||||
|
<div className="card-text text-secondary">Browse magazines and their issues</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import {
|
|||||||
aliases,
|
aliases,
|
||||||
webrefs,
|
webrefs,
|
||||||
websites,
|
websites,
|
||||||
|
magazines,
|
||||||
|
issues,
|
||||||
|
magrefs,
|
||||||
|
referencetypes,
|
||||||
} from "@/server/schema/zxdb";
|
} from "@/server/schema/zxdb";
|
||||||
|
|
||||||
export interface SearchParams {
|
export interface SearchParams {
|
||||||
@@ -66,6 +70,58 @@ export interface EntryFacets {
|
|||||||
machinetypes: FacetItem<number>[];
|
machinetypes: FacetItem<number>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MagazineListItem {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
languageId: string;
|
||||||
|
issueCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MagazineDetail {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
languageId: string;
|
||||||
|
linkSite?: string | null;
|
||||||
|
linkMask?: string | null;
|
||||||
|
archiveMask?: string | null;
|
||||||
|
issues: Array<{
|
||||||
|
id: number;
|
||||||
|
dateYear: number | null;
|
||||||
|
dateMonth: number | null;
|
||||||
|
number: number | null;
|
||||||
|
volume: number | null;
|
||||||
|
special: string | null;
|
||||||
|
supplement: string | null;
|
||||||
|
linkMask?: string | null;
|
||||||
|
archiveMask?: string | null;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IssueDetail {
|
||||||
|
id: number;
|
||||||
|
magazine: { id: number; title: string };
|
||||||
|
dateYear: number | null;
|
||||||
|
dateMonth: number | null;
|
||||||
|
number: number | null;
|
||||||
|
volume: number | null;
|
||||||
|
special: string | null;
|
||||||
|
supplement: string | null;
|
||||||
|
linkMask?: string | null;
|
||||||
|
archiveMask?: string | null;
|
||||||
|
refs: Array<{
|
||||||
|
id: number;
|
||||||
|
page: number;
|
||||||
|
typeId: number;
|
||||||
|
typeName: string;
|
||||||
|
entryId: number | null;
|
||||||
|
entryTitle: string | null;
|
||||||
|
labelId: number | null;
|
||||||
|
labelName: string | null;
|
||||||
|
isOriginal: number;
|
||||||
|
scoreGroup: string;
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
export async function searchEntries(params: SearchParams): Promise<PagedResult<SearchResultItem>> {
|
export async function searchEntries(params: SearchParams): Promise<PagedResult<SearchResultItem>> {
|
||||||
const q = (params.q ?? "").trim();
|
const q = (params.q ?? "").trim();
|
||||||
const pageSize = Math.max(1, Math.min(params.pageSize ?? 20, 100));
|
const pageSize = Math.max(1, Math.min(params.pageSize ?? 20, 100));
|
||||||
@@ -1180,3 +1236,149 @@ export async function listCurrencies() {
|
|||||||
export async function listRoletypes() {
|
export async function listRoletypes() {
|
||||||
return db.select().from(roletypes).orderBy(roletypes.name);
|
return db.select().from(roletypes).orderBy(roletypes.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listMagazines(params: { q?: string; page?: number; pageSize?: number }): Promise<PagedResult<MagazineListItem>> {
|
||||||
|
const q = (params.q ?? "").trim();
|
||||||
|
const pageSize = Math.max(1, Math.min(params.pageSize ?? 20, 100));
|
||||||
|
const page = Math.max(1, params.page ?? 1);
|
||||||
|
const offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
const whereExpr = q ? like(magazines.name, `%${q}%`) : sql`true`;
|
||||||
|
|
||||||
|
const [items, totalRows] = await Promise.all([
|
||||||
|
db
|
||||||
|
.select({
|
||||||
|
id: magazines.id,
|
||||||
|
// Expose as `title` to UI while DB column is `name`
|
||||||
|
title: magazines.name,
|
||||||
|
languageId: magazines.languageId,
|
||||||
|
issueCount: sql<number>`count(${issues.id})`,
|
||||||
|
})
|
||||||
|
.from(magazines)
|
||||||
|
.leftJoin(issues, eq(issues.magazineId, magazines.id))
|
||||||
|
.where(whereExpr)
|
||||||
|
.groupBy(magazines.id)
|
||||||
|
.orderBy(asc(magazines.name))
|
||||||
|
.limit(pageSize)
|
||||||
|
.offset(offset),
|
||||||
|
db
|
||||||
|
.select({ cnt: sql<number>`count(*)` })
|
||||||
|
.from(magazines)
|
||||||
|
.where(whereExpr),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total: totalRows[0]?.cnt ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMagazine(id: number): Promise<MagazineDetail | null> {
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
id: magazines.id,
|
||||||
|
// Alias DB `name` as `title` for UI shape
|
||||||
|
title: magazines.name,
|
||||||
|
languageId: magazines.languageId,
|
||||||
|
linkSite: magazines.linkSite,
|
||||||
|
linkMask: magazines.linkMask,
|
||||||
|
archiveMask: magazines.archiveMask,
|
||||||
|
})
|
||||||
|
.from(magazines)
|
||||||
|
.where(eq(magazines.id, id));
|
||||||
|
if (rows.length === 0) return null;
|
||||||
|
const mag = rows[0];
|
||||||
|
|
||||||
|
const iss = await db
|
||||||
|
.select({
|
||||||
|
id: issues.id,
|
||||||
|
dateYear: issues.dateYear,
|
||||||
|
dateMonth: issues.dateMonth,
|
||||||
|
number: issues.number,
|
||||||
|
volume: issues.volume,
|
||||||
|
special: issues.special,
|
||||||
|
supplement: issues.supplement,
|
||||||
|
linkMask: issues.linkMask,
|
||||||
|
archiveMask: issues.archiveMask,
|
||||||
|
})
|
||||||
|
.from(issues)
|
||||||
|
.where(eq(issues.magazineId, id))
|
||||||
|
.orderBy(
|
||||||
|
asc(issues.dateYear),
|
||||||
|
asc(issues.dateMonth),
|
||||||
|
asc(issues.volume),
|
||||||
|
asc(issues.number),
|
||||||
|
asc(issues.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...mag, issues: iss };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getIssue(id: number): Promise<IssueDetail | null> {
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
id: issues.id,
|
||||||
|
magazineId: issues.magazineId,
|
||||||
|
magazineTitle: magazines.name,
|
||||||
|
dateYear: issues.dateYear,
|
||||||
|
dateMonth: issues.dateMonth,
|
||||||
|
number: issues.number,
|
||||||
|
volume: issues.volume,
|
||||||
|
special: issues.special,
|
||||||
|
supplement: issues.supplement,
|
||||||
|
linkMask: issues.linkMask,
|
||||||
|
archiveMask: issues.archiveMask,
|
||||||
|
})
|
||||||
|
.from(issues)
|
||||||
|
.leftJoin(magazines, eq(magazines.id, issues.magazineId))
|
||||||
|
.where(eq(issues.id, id));
|
||||||
|
const base = rows[0];
|
||||||
|
if (!base) return null;
|
||||||
|
|
||||||
|
const refs = await db
|
||||||
|
.select({
|
||||||
|
id: magrefs.id,
|
||||||
|
page: magrefs.page,
|
||||||
|
typeId: magrefs.referencetypeId,
|
||||||
|
typeName: referencetypes.name,
|
||||||
|
entryId: magrefs.entryId,
|
||||||
|
entryTitle: entries.title,
|
||||||
|
labelId: magrefs.labelId,
|
||||||
|
labelName: labels.name,
|
||||||
|
isOriginal: magrefs.isOriginal,
|
||||||
|
scoreGroup: magrefs.scoreGroup,
|
||||||
|
})
|
||||||
|
.from(magrefs)
|
||||||
|
.leftJoin(referencetypes, eq(referencetypes.id, magrefs.referencetypeId))
|
||||||
|
.leftJoin(entries, eq(entries.id, magrefs.entryId))
|
||||||
|
.leftJoin(labels, eq(labels.id, magrefs.labelId))
|
||||||
|
.where(eq(magrefs.issueId, id))
|
||||||
|
.orderBy(asc(magrefs.page), asc(magrefs.id));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: base.id,
|
||||||
|
magazine: { id: Number(base.magazineId), title: base.magazineTitle ?? "" },
|
||||||
|
dateYear: base.dateYear,
|
||||||
|
dateMonth: base.dateMonth,
|
||||||
|
number: base.number,
|
||||||
|
volume: base.volume,
|
||||||
|
special: base.special,
|
||||||
|
supplement: base.supplement,
|
||||||
|
linkMask: base.linkMask,
|
||||||
|
archiveMask: base.archiveMask,
|
||||||
|
refs: refs.map((r) => ({
|
||||||
|
id: r.id,
|
||||||
|
page: r.page,
|
||||||
|
typeId: Number(r.typeId),
|
||||||
|
typeName: r.typeName ?? "",
|
||||||
|
entryId: r.entryId ?? null,
|
||||||
|
entryTitle: r.entryTitle ?? null,
|
||||||
|
labelId: r.labelId ?? null,
|
||||||
|
labelName: r.labelName ?? null,
|
||||||
|
isOriginal: Number(r.isOriginal),
|
||||||
|
scoreGroup: r.scoreGroup,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ export const entries = mysqlTable("entries", {
|
|||||||
isXrated: tinyint("is_xrated").notNull(),
|
isXrated: tinyint("is_xrated").notNull(),
|
||||||
machinetypeId: tinyint("machinetype_id"),
|
machinetypeId: tinyint("machinetype_id"),
|
||||||
maxPlayers: tinyint("max_players").notNull().default(1),
|
maxPlayers: tinyint("max_players").notNull().default(1),
|
||||||
|
// DB allows NULLs on many of these
|
||||||
languageId: char("language_id", { length: 2 }),
|
languageId: char("language_id", { length: 2 }),
|
||||||
genretypeSpotId: tinyint("spot_genretype_id"),
|
|
||||||
genretypeId: tinyint("genretype_id"),
|
genretypeId: tinyint("genretype_id"),
|
||||||
|
genretypeSpotId: tinyint("spot_genretype_id"),
|
||||||
availabletypeId: char("availabletype_id", { length: 1 }),
|
availabletypeId: char("availabletype_id", { length: 1 }),
|
||||||
withoutLoadScreen: tinyint("without_load_screen").notNull(),
|
withoutLoadScreen: tinyint("without_load_screen").notNull(),
|
||||||
withoutInlay: tinyint("without_inlay").notNull(),
|
withoutInlay: tinyint("without_inlay").notNull(),
|
||||||
@@ -28,6 +29,14 @@ export type Entry = typeof entries.$inferSelect;
|
|||||||
export const labels = mysqlTable("labels", {
|
export const labels = mysqlTable("labels", {
|
||||||
id: int("id").notNull().primaryKey(),
|
id: int("id").notNull().primaryKey(),
|
||||||
name: varchar("name", { length: 100 }).notNull(),
|
name: varchar("name", { length: 100 }).notNull(),
|
||||||
|
countryId: char("country_id", { length: 2 }),
|
||||||
|
country2Id: char("country2_id", { length: 2 }),
|
||||||
|
fromId: int("from_id"),
|
||||||
|
ownerId: int("owner_id"),
|
||||||
|
wasRenamed: tinyint("was_renamed").notNull().default(0),
|
||||||
|
deceased: varchar("deceased", { length: 200 }),
|
||||||
|
linkWikipedia: varchar("link_wikipedia", { length: 200 }),
|
||||||
|
linkSite: varchar("link_site", { length: 200 }),
|
||||||
labeltypeId: char("labeltype_id", { length: 1 }),
|
labeltypeId: char("labeltype_id", { length: 1 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,6 +63,8 @@ export const authors = mysqlTable("authors", {
|
|||||||
entryId: int("entry_id").notNull(),
|
entryId: int("entry_id").notNull(),
|
||||||
labelId: int("label_id").notNull(),
|
labelId: int("label_id").notNull(),
|
||||||
teamId: int("team_id"),
|
teamId: int("team_id"),
|
||||||
|
// Present in schema; sequence of the author for a given entry
|
||||||
|
authorSeq: smallint("author_seq").notNull().default(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const publishers = mysqlTable("publishers", {
|
export const publishers = mysqlTable("publishers", {
|
||||||
@@ -143,6 +154,35 @@ export const hosts = mysqlTable("hosts", {
|
|||||||
magazineId: smallint("magazine_id"),
|
magazineId: smallint("magazine_id"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- Magazines and Issues (subset used by the app) ----
|
||||||
|
export const magazines = mysqlTable("magazines", {
|
||||||
|
id: smallint("id").notNull().primaryKey(),
|
||||||
|
// ZXDB column is `name`
|
||||||
|
name: varchar("name", { length: 100 }).notNull(),
|
||||||
|
countryId: char("country_id", { length: 2 }).notNull(),
|
||||||
|
languageId: char("language_id", { length: 2 }).notNull(),
|
||||||
|
linkSite: varchar("link_site", { length: 200 }),
|
||||||
|
magtypeId: char("magtype_id", { length: 1 }).notNull(),
|
||||||
|
topicId: int("topic_id"),
|
||||||
|
linkMask: varchar("link_mask", { length: 250 }),
|
||||||
|
archiveMask: varchar("archive_mask", { length: 250 }),
|
||||||
|
translationMask: varchar("translation_mask", { length: 250 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const issues = mysqlTable("issues", {
|
||||||
|
id: int("id").notNull().primaryKey(),
|
||||||
|
magazineId: smallint("magazine_id").notNull(),
|
||||||
|
dateYear: smallint("date_year"),
|
||||||
|
dateMonth: smallint("date_month"),
|
||||||
|
dateDay: smallint("date_day"),
|
||||||
|
volume: smallint("volume"),
|
||||||
|
number: smallint("number"),
|
||||||
|
special: varchar("special", { length: 100 }),
|
||||||
|
supplement: varchar("supplement", { length: 100 }),
|
||||||
|
linkMask: varchar("link_mask", { length: 250 }),
|
||||||
|
archiveMask: varchar("archive_mask", { length: 250 }),
|
||||||
|
});
|
||||||
|
|
||||||
// ---- Aliases (alternative titles per entry/release/language)
|
// ---- Aliases (alternative titles per entry/release/language)
|
||||||
export const aliases = mysqlTable("aliases", {
|
export const aliases = mysqlTable("aliases", {
|
||||||
entryId: int("entry_id").notNull(),
|
entryId: int("entry_id").notNull(),
|
||||||
@@ -214,3 +254,91 @@ export const roles = mysqlTable("roles", {
|
|||||||
labelId: int("label_id").notNull(),
|
labelId: int("label_id").notNull(),
|
||||||
roletypeId: char("roletype_id", { length: 1 }).notNull(),
|
roletypeId: char("roletype_id", { length: 1 }).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- Additional ZXDB schema coverage (lookups and content) ----
|
||||||
|
|
||||||
|
export const articletypes = mysqlTable("articletypes", {
|
||||||
|
id: char("id", { length: 1 }).notNull().primaryKey(),
|
||||||
|
name: varchar("text", { length: 50 }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const articles = mysqlTable("articles", {
|
||||||
|
labelId: int("label_id").notNull(),
|
||||||
|
link: varchar("link", { length: 200 }).notNull(),
|
||||||
|
articletypeId: char("articletype_id", { length: 1 }).notNull(),
|
||||||
|
title: varchar("title", { length: 200 }),
|
||||||
|
languageId: char("language_id", { length: 2 }).notNull(),
|
||||||
|
writer: varchar("writer", { length: 200 }),
|
||||||
|
dateYear: smallint("date_year"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const categories = mysqlTable("categories", {
|
||||||
|
id: smallint("id").notNull().primaryKey(),
|
||||||
|
// DB column `text`
|
||||||
|
name: varchar("text", { length: 50 }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const contenttypes = mysqlTable("contenttypes", {
|
||||||
|
id: char("id", { length: 1 }).notNull().primaryKey(),
|
||||||
|
name: varchar("text", { length: 50 }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const contents = mysqlTable("contents", {
|
||||||
|
// ZXDB contents table does not have its own `id`; natural key is (issue_id, page_from, page_to, label_id, entry_id)
|
||||||
|
entryId: int("entry_id").notNull(),
|
||||||
|
labelId: int("label_id"),
|
||||||
|
issueId: int("issue_id").notNull(),
|
||||||
|
contenttypeId: char("contenttype_id", { length: 1 }).notNull(),
|
||||||
|
pageFrom: smallint("page_from"),
|
||||||
|
pageTo: smallint("page_to"),
|
||||||
|
title: varchar("title", { length: 200 }),
|
||||||
|
dateYear: smallint("date_year"),
|
||||||
|
rating: tinyint("rating"),
|
||||||
|
comments: varchar("comments", { length: 250 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const extensions = mysqlTable("extensions", {
|
||||||
|
ext: varchar("ext", { length: 15 }).notNull().primaryKey(),
|
||||||
|
name: varchar("text", { length: 50 }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const features = mysqlTable("features", {
|
||||||
|
id: int("id").notNull().primaryKey(),
|
||||||
|
name: varchar("name", { length: 150 }).notNull(),
|
||||||
|
version: tinyint("version").notNull().default(0),
|
||||||
|
labelId: int("label_id"),
|
||||||
|
label2Id: int("label2_id"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tooltypes = mysqlTable("tooltypes", {
|
||||||
|
id: char("id", { length: 1 }).notNull().primaryKey(),
|
||||||
|
name: varchar("text", { length: 50 }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tools = mysqlTable("tools", {
|
||||||
|
id: int("id").notNull().primaryKey(),
|
||||||
|
title: varchar("title", { length: 200 }).notNull(),
|
||||||
|
languageId: char("language_id", { length: 2 }),
|
||||||
|
tooltypeId: char("tooltype_id", { length: 1 }),
|
||||||
|
link: varchar("link", { length: 200 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---- Magazine references (per-issue references to entries/labels/topics) ----
|
||||||
|
export const referencetypes = mysqlTable("referencetypes", {
|
||||||
|
id: tinyint("id").notNull().primaryKey(),
|
||||||
|
name: varchar("text", { length: 50 }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const magrefs = mysqlTable("magrefs", {
|
||||||
|
id: int("id").notNull().primaryKey(),
|
||||||
|
referencetypeId: tinyint("referencetype_id").notNull(),
|
||||||
|
entryId: int("entry_id"),
|
||||||
|
labelId: int("label_id"),
|
||||||
|
topicId: int("topic_id"),
|
||||||
|
issueId: int("issue_id").notNull(),
|
||||||
|
page: smallint("page").notNull().default(0),
|
||||||
|
isOriginal: tinyint("is_original").notNull().default(0),
|
||||||
|
scoreGroup: varchar("score_group", { length: 100 }).notNull().default(""),
|
||||||
|
reviewId: int("review_id"),
|
||||||
|
awardId: tinyint("award_id"),
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user