![]()
{
return (
{hour}
diff --git a/workspaces/client/src/setups/unocss.ts b/workspaces/client/src/setups/unocss.ts
deleted file mode 100644
index d8e91da31..000000000
--- a/workspaces/client/src/setups/unocss.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { IconifyJSON } from '@iconify/types';
-import presetIcons from '@unocss/preset-icons/browser';
-import presetWind3 from '@unocss/preset-wind3';
-import initUnocssRuntime, { defineConfig } from '@unocss/runtime';
-
-async function init() {
- await initUnocssRuntime({
- defaults: defineConfig({
- layers: {
- default: 1,
- icons: 0,
- preflights: 0,
- reset: -1,
- },
- preflights: [
- {
- getCSS: () => import('@unocss/reset/tailwind-compat.css?raw').then(({ default: css }) => css),
- layer: 'reset',
- },
- {
- getCSS: () => /* css */ `
- @view-transition {
- navigation: auto;
- }
- html,
- :host {
- font-family: 'Noto Sans JP', sans-serif !important;
- }
- video {
- max-height: 100%;
- max-width: 100%;
- }
- `,
- },
- {
- getCSS: () => /* css */ `
- @keyframes fade-in {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
- }
- `,
- },
- ],
- presets: [
- presetWind3(),
- presetIcons({
- collections: {
- bi: () => import('@iconify/json/json/bi.json').then((m): IconifyJSON => m.default as IconifyJSON),
- bx: () => import('@iconify/json/json/bx.json').then((m): IconifyJSON => m.default as IconifyJSON),
- 'fa-regular': () =>
- import('@iconify/json/json/fa-regular.json').then((m): IconifyJSON => m.default as IconifyJSON),
- 'fa-solid': () =>
- import('@iconify/json/json/fa-solid.json').then((m): IconifyJSON => m.default as IconifyJSON),
- fluent: () => import('@iconify/json/json/fluent.json').then((m): IconifyJSON => m.default as IconifyJSON),
- 'line-md': () =>
- import('@iconify/json/json/line-md.json').then((m): IconifyJSON => m.default as IconifyJSON),
- 'material-symbols': () =>
- import('@iconify/json/json/material-symbols.json').then((m): IconifyJSON => m.default as IconifyJSON),
- },
- }),
- ],
- }),
- });
-}
-
-init().catch((err: unknown) => {
- throw err;
-});
diff --git a/workspaces/client/uno.config.ts b/workspaces/client/uno.config.ts
new file mode 100644
index 000000000..f2d373dc7
--- /dev/null
+++ b/workspaces/client/uno.config.ts
@@ -0,0 +1,127 @@
+import type { IconifyJSON } from '@iconify/types'
+import {
+ defineConfig,
+ presetIcons,
+ presetWind3,
+} from 'unocss'
+
+export default defineConfig({
+ layers: {
+ default: 1,
+ icons: 0,
+ preflights: 0,
+ reset: -1,
+ },
+ preflights: [
+ {
+ getCSS: () => /* css */ `
+ @view-transition {
+ navigation: auto;
+ }
+ html,
+ :host {
+ font-family: 'Noto Sans JP', sans-serif !important;
+ }
+ video {
+ max-height: 100%;
+ max-width: 100%;
+ }
+ `,
+ },
+ {
+ getCSS: () => /* css */ `
+ @keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+ `,
+ },
+ ],
+ presets: [
+ presetWind3(),
+ presetIcons({
+ collections: {
+ bi: () => import('@iconify/json/json/bi.json').then((m): IconifyJSON => m.default as IconifyJSON),
+ bx: () => import('@iconify/json/json/bx.json').then((m): IconifyJSON => m.default as IconifyJSON),
+ 'fa-regular': () =>
+ import('@iconify/json/json/fa-regular.json').then((m): IconifyJSON => m.default as IconifyJSON),
+ 'fa-solid': () =>
+ import('@iconify/json/json/fa-solid.json').then((m): IconifyJSON => m.default as IconifyJSON),
+ fluent: () => import('@iconify/json/json/fluent.json').then((m): IconifyJSON => m.default as IconifyJSON),
+ 'line-md': () =>
+ import('@iconify/json/json/line-md.json').then((m): IconifyJSON => m.default as IconifyJSON),
+ 'material-symbols': () =>
+ import('@iconify/json/json/material-symbols.json').then((m): IconifyJSON => m.default as IconifyJSON),
+ },
+ }),
+ ],
+ safelist: [
+ // 動的生成されるクラス
+ 'opacity-75',
+ 'cursor-pointer',
+ 'bg-gradient-to-b',
+ 'from-[#171717]',
+ 'to-transparent',
+ 'to-[#171717]',
+ // 共通の動的クラス
+ 'size-[48px]',
+ 'text-[#ffffff]',
+ 'bg-[#00000077]',
+ 'bg-[#000000CC]',
+ 'animate-[fade-in_0.5s_ease-in_0.5s_both]',
+ // ボーダーと構造的なクラス
+ 'border-x-[1px]',
+ 'border-x-[#212121]',
+ 'border-[2px]',
+ 'border-solid',
+ 'border-[#FFFFFF1F]',
+ // ポジショニングとグリッド関連
+ 'place-self-center',
+ 'place-self-stretch',
+ // サイズ関連の動的クラス
+ 'w-[24px]',
+ 'w-[8px]',
+ 'w-[188px]',
+ 'w-[480px]',
+ 'w-full',
+ 'w-auto',
+ 'h-[20px]',
+ 'h-[72px]',
+ 'h-[80px]',
+ 'h-[100vh]',
+ 'h-[120px]',
+ 'h-auto',
+ 'h-full',
+ 'min-h-[100vh]',
+ // パディングとマージン
+ 'px-[8px]',
+ 'px-[12px]',
+ 'px-[16px]',
+ 'px-[24px]',
+ 'py-[32px]',
+ 'py-[48px]',
+ 'p-[8px]',
+ 'p-[14px]',
+ 'mb-[16px]',
+ 'mb-[24px]',
+ 'mx-[-24px]',
+ 'pl-[24px]',
+ 'pr-[56px]',
+ 'pt-[80px]',
+ // テキストサイズ
+ 'text-[14px]',
+ 'text-[16px]',
+ 'text-[22px]',
+ // 変数を含む動的クラス(固定値バージョン)
+ 'bg-[#FCF6E5]',
+ 'bg-[#212121]',
+ 'text-[#212121]',
+ 'text-[#ffffff]',
+ 'text-[#767676]',
+ 'text-[#999999]',
+ ],
+})
\ No newline at end of file
diff --git a/workspaces/client/webpack.config.mjs b/workspaces/client/webpack.config.mjs
index 9164a996e..122291ab9 100644
--- a/workspaces/client/webpack.config.mjs
+++ b/workspaces/client/webpack.config.mjs
@@ -1,12 +1,17 @@
import path from 'node:path';
import webpack from 'webpack';
+import TerserPlugin from 'terser-webpack-plugin';
+import UnoCSS from '@unocss/webpack';
/** @type {import('webpack').Configuration} */
const config = {
- devtool: 'inline-source-map',
- entry: './src/main.tsx',
- mode: 'none',
+ //devtool: process.env['NODE_ENV'] === 'production' ? 'source-map' : 'eval',
+ devtool: false,
+ entry: {
+ main: './src/main.tsx'
+ },
+ mode: process.env['NODE_ENV'] === 'production' ? 'production' : 'development',
module: {
rules: [
{
@@ -34,6 +39,10 @@ const config = {
},
},
},
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader'],
+ },
{
test: /\.png$/,
type: 'asset/inline',
@@ -51,16 +60,113 @@ const config = {
},
],
},
+ optimization: {
+ minimize: process.env['NODE_ENV'] === 'production',
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ compress: {
+ drop_console: process.env['NODE_ENV'] === 'production',
+ },
+ output: {
+ comments: false,
+ },
+ },
+ extractComments: false,
+ }),
+ ],
+ usedExports: true,
+ sideEffects: true,
+ splitChunks: {
+ chunks: 'all',
+ cacheGroups: {
+ // 最後のフォールバックとしてのvendors
+ vendors: {
+ test: /[\\/]node_modules[\\/]/,
+ priority: -10,
+ name: 'vendors',
+ },
+ // Reactとその関連ライブラリ
+ react: {
+ test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom|react-use|react-final-form|react-flip-toolkit)[\\/]/,
+ priority: 20,
+ name: 'react-libs',
+ enforce: true,
+ },
+ // core-jsとBabel関連
+ corejs: {
+ test: /[\\/]node_modules[\\/](core-js|@babel\/runtime)[\\/]/,
+ priority: 20,
+ name: 'core-js',
+ enforce: true,
+ },
+ // データ検証・スキーマライブラリ
+ validation: {
+ test: /[\\/]node_modules[\\/](zod|valibot|@standard-schema)[\\/]/,
+ priority: 15,
+ name: 'validation-libs',
+ enforce: true,
+ },
+ // 汎用ユーティリティライブラリ
+ utils: {
+ test: /[\\/]node_modules[\\/](lodash|luxon|immer|invariant|final-form|zustand|classnames)[\\/]/,
+ priority: 15,
+ name: 'utils',
+ enforce: true,
+ },
+ // Iconifyのファイルを個別のチャンクに分割
+ iconify: {
+ test: /[\\/]node_modules[\\/]@iconify[\\/]json[\\/]json[\\/]/,
+ priority: 10,
+ name(/** @type {{ context: string }} */ module) {
+ // iconify/json/json/xxxx.jsonからxxxxを抽出
+ const match = module.context.match(/@iconify\/json\/json\/([^.]+)\.json/);
+ return match ? `iconify-${match[1]}` : 'iconify';
+ },
+ enforce: true,
+ },
+ // FFMPEGのファイルを個別のチャンクに分割
+ ffmpeg: {
+ test: /[\\/]node_modules[\\/]@ffmpeg[\\/]/,
+ priority: 10,
+ name(/** @type {{ context: string }} */ module) {
+ // @ffmpeg/xxx からxxxを抽出
+ const match = module.context.match(/@ffmpeg\/([-\w]+)/);
+ return match ? `ffmpeg-${match[1]}` : 'ffmpeg';
+ },
+ enforce: true,
+ },
+ // すべてのビデオ関連ライブラリとcreate_playerを1つのチャンクにまとめる
+ videoPlayers: {
+ test: /** @type {any} */ (module) => {
+ // create_player.tsファイルを含める
+ if (module.resource && typeof module.resource === 'string' && /create_player\.ts$/.test(module.resource)) {
+ return true;
+ }
+ // video.js、@videojs、hls.js、shaka-playerを含める
+ return /[\\/]node_modules[\\/](video\.js|@videojs|hls\.js|shaka-player)[\\/]/.test(
+ (typeof module.resource === 'string' ? module.resource : '')
+ );
+ },
+ priority: 10,
+ name: 'video-players',
+ enforce: true,
+ },
+ },
+ },
+ runtimeChunk: {
+ name: 'runtime'
+ },
+ },
output: {
chunkFilename: 'chunk-[contenthash].js',
- chunkFormat: false,
- filename: 'main.js',
+ filename: '[name].js',
path: path.resolve(import.meta.dirname, './dist'),
publicPath: 'auto',
},
plugins: [
- new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
- new webpack.EnvironmentPlugin({ API_BASE_URL: '/api', NODE_ENV: '' }),
+ new webpack.EnvironmentPlugin({ API_BASE_URL: '/api' }),
+ UnoCSS(),
],
resolve: {
alias: {
diff --git a/workspaces/schema/src/database/schema.ts b/workspaces/schema/src/database/schema.ts
index 7a08aec3e..2982a3cd6 100644
--- a/workspaces/schema/src/database/schema.ts
+++ b/workspaces/schema/src/database/schema.ts
@@ -2,7 +2,7 @@
import '@wsh-2025/schema/src/setups/luxon';
import { relations } from 'drizzle-orm';
-import { sqliteTable as table } from 'drizzle-orm/sqlite-core';
+import { index, sqliteTable as table } from 'drizzle-orm/sqlite-core';
import * as t from 'drizzle-orm/sqlite-core';
import { DateTime } from 'luxon';
@@ -190,7 +190,9 @@ export const recommendedModule = table(
referenceId: t.text().notNull(),
type: t.text().notNull(),
},
- () => [],
+ (table) => [
+ index('reference_id_idx').on(table.referenceId),
+ ],
);
export const recommendedModuleRelation = relations(recommendedModule, ({ many }) => ({
items: many(recommendedItem),
diff --git a/workspaces/server/database.sqlite b/workspaces/server/database.sqlite
index 5afaf4e28..3b5239290 100644
Binary files a/workspaces/server/database.sqlite and b/workspaces/server/database.sqlite differ
diff --git a/workspaces/server/migrations/0005_tiresome_colossus.sql b/workspaces/server/migrations/0005_tiresome_colossus.sql
new file mode 100644
index 000000000..b1e11e827
--- /dev/null
+++ b/workspaces/server/migrations/0005_tiresome_colossus.sql
@@ -0,0 +1 @@
+CREATE INDEX `reference_id_idx` ON `recommendedModule` (`referenceId`);
\ No newline at end of file
diff --git a/workspaces/server/migrations/meta/0005_snapshot.json b/workspaces/server/migrations/meta/0005_snapshot.json
new file mode 100644
index 000000000..305ddea3a
--- /dev/null
+++ b/workspaces/server/migrations/meta/0005_snapshot.json
@@ -0,0 +1,474 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "0f491de2-c9f2-44d1-9190-49e6dbbb4644",
+ "prevId": "a53ab7e6-a56a-450f-8b94-a490a9a5557d",
+ "tables": {
+ "channel": {
+ "name": "channel",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "logoUrl": {
+ "name": "logoUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "episode": {
+ "name": "episode",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thumbnailUrl": {
+ "name": "thumbnailUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "seriesId": {
+ "name": "seriesId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "streamId": {
+ "name": "streamId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "premium": {
+ "name": "premium",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "episode_seriesId_series_id_fk": {
+ "name": "episode_seriesId_series_id_fk",
+ "tableFrom": "episode",
+ "tableTo": "series",
+ "columnsFrom": [
+ "seriesId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "episode_streamId_stream_id_fk": {
+ "name": "episode_streamId_stream_id_fk",
+ "tableFrom": "episode",
+ "tableTo": "stream",
+ "columnsFrom": [
+ "streamId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "program": {
+ "name": "program",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "startAt": {
+ "name": "startAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "endAt": {
+ "name": "endAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thumbnailUrl": {
+ "name": "thumbnailUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "channelId": {
+ "name": "channelId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "episodeId": {
+ "name": "episodeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "program_channelId_channel_id_fk": {
+ "name": "program_channelId_channel_id_fk",
+ "tableFrom": "program",
+ "tableTo": "channel",
+ "columnsFrom": [
+ "channelId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "program_episodeId_episode_id_fk": {
+ "name": "program_episodeId_episode_id_fk",
+ "tableFrom": "program",
+ "tableTo": "episode",
+ "columnsFrom": [
+ "episodeId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "recommendedItem": {
+ "name": "recommendedItem",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "moduleId": {
+ "name": "moduleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "seriesId": {
+ "name": "seriesId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "episodeId": {
+ "name": "episodeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "recommendedItem_moduleId_recommendedModule_id_fk": {
+ "name": "recommendedItem_moduleId_recommendedModule_id_fk",
+ "tableFrom": "recommendedItem",
+ "tableTo": "recommendedModule",
+ "columnsFrom": [
+ "moduleId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "recommendedItem_seriesId_series_id_fk": {
+ "name": "recommendedItem_seriesId_series_id_fk",
+ "tableFrom": "recommendedItem",
+ "tableTo": "series",
+ "columnsFrom": [
+ "seriesId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "recommendedItem_episodeId_episode_id_fk": {
+ "name": "recommendedItem_episodeId_episode_id_fk",
+ "tableFrom": "recommendedItem",
+ "tableTo": "episode",
+ "columnsFrom": [
+ "episodeId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "recommendedModule": {
+ "name": "recommendedModule",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "referenceId": {
+ "name": "referenceId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "reference_id_idx": {
+ "name": "reference_id_idx",
+ "columns": [
+ "referenceId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "series": {
+ "name": "series",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thumbnailUrl": {
+ "name": "thumbnailUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stream": {
+ "name": "stream",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "numberOfChunks": {
+ "name": "numberOfChunks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/workspaces/server/migrations/meta/_journal.json b/workspaces/server/migrations/meta/_journal.json
index fb4b52537..31aa51523 100644
--- a/workspaces/server/migrations/meta/_journal.json
+++ b/workspaces/server/migrations/meta/_journal.json
@@ -36,6 +36,13 @@
"when": 1742275045469,
"tag": "0004_abnormal_network",
"breakpoints": true
+ },
+ {
+ "idx": 5,
+ "version": "6",
+ "when": 1742662424550,
+ "tag": "0005_tiresome_colossus",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/workspaces/server/src/ssr.tsx b/workspaces/server/src/ssr.tsx
index 7603aafdd..820b4ac49 100644
--- a/workspaces/server/src/ssr.tsx
+++ b/workspaces/server/src/ssr.tsx
@@ -1,4 +1,3 @@
-import { readdirSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -13,18 +12,6 @@ import { StrictMode } from 'react';
import { renderToString } from 'react-dom/server';
import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router';
-function getFiles(parent: string): string[] {
- const dirents = readdirSync(parent, { withFileTypes: true });
- return dirents
- .filter((dirent) => dirent.isFile() && !dirent.name.startsWith('.'))
- .map((dirent) => path.join(parent, dirent.name));
-}
-
-function getFilePaths(relativePath: string, rootDir: string): string[] {
- const files = getFiles(path.resolve(rootDir, relativePath));
- return files.map((file) => path.join('/', path.relative(rootDir, file)));
-}
-
export function registerSsr(app: FastifyInstance): void {
app.register(fastifyStatic, {
prefix: '/public/',
@@ -59,30 +46,31 @@ export function registerSsr(app: FastifyInstance): void {
,
);
- const rootDir = path.resolve(__dirname, '../../../');
- const imagePaths = [
- getFilePaths('public/images', rootDir),
- getFilePaths('public/animations', rootDir),
- getFilePaths('public/logos', rootDir),
- ].flat();
-
reply.type('text/html').send(/* html */ `
-
- ${imagePaths.map((imagePath) => `
`).join('\n')}
+
+
+
+
+
+
+
+
+
-
+
+
-
`);
});
}
diff --git a/workspaces/server/tools/seed.ts b/workspaces/server/tools/seed.ts
index 611eba167..c9d212a9e 100644
--- a/workspaces/server/tools/seed.ts
+++ b/workspaces/server/tools/seed.ts
@@ -14,7 +14,11 @@ import { readdirSync } from 'node:fs';
function getFiles(parent: string): string[] {
const dirents = readdirSync(parent, { withFileTypes: true });
return dirents
- .filter((dirent) => dirent.isFile() && !dirent.name.startsWith('.'))
+ .filter((dirent) =>
+ dirent.isFile() &&
+ !dirent.name.startsWith('.') &&
+ dirent.name.toLowerCase().endsWith('.avif')
+ )
.map((dirent) => path.join(parent, dirent.name));
}
@@ -131,7 +135,7 @@ async function main() {
const data: (typeof schema.series.$inferInsert)[] = Array.from({ length: 30 }, () => ({
description: faker.lorem.paragraph({ max: 200, min: 100 }).replace(/\s/g, '').replace(/\./g, '。'),
id: faker.string.uuid(),
- thumbnailUrl: `${faker.helpers.arrayElement(imagePaths)}?version=${faker.string.nanoid()}`,
+ thumbnailUrl: `${faker.helpers.arrayElement(imagePaths)}`,
title: faker.helpers.arrayElement(seriesTitleList),
}));
const result = await database.insert(schema.series).values(data).returning();
@@ -150,7 +154,7 @@ async function main() {
order: idx + 1,
seriesId: series.id,
streamId: faker.helpers.arrayElement(streamList).id,
- thumbnailUrl: `${faker.helpers.arrayElement(imagePaths)}?version=${faker.string.nanoid()}`,
+ thumbnailUrl: `${faker.helpers.arrayElement(imagePaths)}`,
title: `第${String(idx + 1)}話 ${faker.helpers.arrayElement(episodeTitleList)}`,
premium: idx % 5 === 0,
}),
@@ -183,7 +187,7 @@ async function main() {
episodeId: episode.id,
id: faker.string.uuid(),
startAt: new Date(startAt).toISOString(),
- thumbnailUrl: `${faker.helpers.arrayElement(imagePaths)}?version=${faker.string.nanoid()}`,
+ thumbnailUrl: `${faker.helpers.arrayElement(imagePaths)}`,
title: `${series?.title ?? ''} ${episode.title}`,
};
programList.push(program);