Compare commits

...

5 Commits

Author SHA1 Message Date
a660514ade
update: header 2025-07-06 19:06:32 +08:00
b2488bddfe
add: header 2025-06-29 18:00:52 +08:00
dab8a94217
add: Drizzle ORM 2025-06-22 03:56:51 +08:00
87ee7ab9b4
remove: .source folder in next package 2025-06-21 00:35:18 +08:00
edbae956ca
add: solid.js powered frontend 2025-06-20 01:00:48 +08:00
101 changed files with 6855 additions and 52 deletions

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="TypeScriptCompiler">
<option name="useTypesFromServer" value="true" />
</component>
</project>

View File

@ -30,6 +30,8 @@
<excludeFolder url="file://$MODULE_DIR$/redis" /> <excludeFolder url="file://$MODULE_DIR$/redis" />
<excludeFolder url="file://$MODULE_DIR$/ml" /> <excludeFolder url="file://$MODULE_DIR$/ml" />
<excludeFolder url="file://$MODULE_DIR$/src" /> <excludeFolder url="file://$MODULE_DIR$/src" />
<excludeFolder url="file://$MODULE_DIR$/packages/crawler/.cache" />
<excludeFolder url="file://$MODULE_DIR$/packages/solid/.vinxi" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

View File

@ -9,3 +9,5 @@ MiSans.css
*.yaml *.yaml
*.yml *.yml
*.mdx *.mdx
packages/solid/src/drizzle/cred
packages/solid/src/drizzle/main

1173
bun.lock

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,8 @@
"packages/frontend", "packages/frontend",
"packages/core", "packages/core",
"packages/backend", "packages/backend",
"packages/crawler" "packages/crawler",
"packages/solid"
], ],
"dependencies": { "dependencies": {
"arg": "^5.0.2", "arg": "^5.0.2",

View File

@ -1,3 +0,0 @@
// @ts-nocheck -- skip type checking
import { _runtime } from "fumadocs-mdx"
import * as _source from "../source.config"

View File

@ -1,8 +0,0 @@
// source.config.ts
import { defineConfig } from "fumadocs-mdx/config";
var source_config_default = defineConfig({
mdxOptions: {}
});
export {
source_config_default as default
};

28
packages/solid/.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
dist
.wrangler
.output
.vercel
.netlify
.vinxi
app.config.timestamp_*.js
# Environment
.env
.env*.local
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# Temp
gitignore
# System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"tabWidth": 4,
"trailingComma": "none",
"singleQuote": false,
"printWidth": 120,
"endOfLine": "lf"
}

32
packages/solid/README.md Normal file
View File

@ -0,0 +1,32 @@
# SolidStart
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
## Creating a project
```bash
# create a new project in the current directory
npm init solid@latest
# create a new project in my-app
npm init solid@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Solid apps are built with _presets_, which optimise your project for deployment to different environments.
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli)

View File

@ -0,0 +1,19 @@
"use server";
import { defineConfig } from "@solidjs/start/config";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
vite: {
plugins: [tailwindcss(), tsconfigPaths()],
optimizeDeps: {
include: ["@m3-components/solid"],
esbuildOptions: {
jsx: "automatic",
jsxDev: true,
jsxImportSource: "solid-js/h"
}
},
},
});

1777
packages/solid/bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './src/drizzle/cred',
schema: './src/db/schema.ts',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL_CRED!,
},
});

View File

@ -0,0 +1,10 @@
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './src/drizzle/main',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL_MAIN!,
},
});

View File

@ -0,0 +1,38 @@
{
"name": "example-basic",
"type": "module",
"scripts": {
"dev": "vinxi dev --port 7400 --host",
"build": "vinxi build",
"start": "bun ../../node_modules/.bin/vinxi start --port 7400",
"version": "vinxi version"
},
"dependencies": {
"@m3-components/solid": "0.0.7",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.1.0",
"@tailwindcss/vite": "^4.1.10",
"@types/luxon": "^3.6.2",
"axios": "^1.10.0",
"dotenv": "^16.5.0",
"drizzle-orm": "^0.44.2",
"luxon": "^3.6.1",
"postgres": "^3.4.7",
"solid-js": "^1.9.5",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.10",
"vinxi": "^0.5.7"
},
"engines": {
"node": ">=22"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"drizzle-kit": "^0.31.1",
"postcss": "^8.5.6",
"tsx": "^4.20.3",
"vite-plugin-solid": "^2.11.7",
"vite-tsconfig-paths": "^5.1.4"
}
}

View File

@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {}
}
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

152
packages/solid/src/app.css Normal file
View File

@ -0,0 +1,152 @@
@import url("./fonts/InterFont/Inter.css");
@import url("./fonts/MiSans/MiSans.css");
@import "tailwindcss";
@media (prefers-color-scheme: light) {
:root {
--md-sys-color-background: #fff8f6;
--md-sys-color-on-background: #2a1613;
--md-sys-color-surface: #fff8f6;
--md-sys-color-surface-dim: #f7d2cc;
--md-sys-color-surface-bright: #fff8f6;
--md-sys-color-surface-container-lowest: #ffffff;
--md-sys-color-surface-container-low: #fff0ee;
--md-sys-color-surface-container: #ffe9e6;
--md-sys-color-surface-container-high: #ffe2dd;
--md-sys-color-surface-container-highest: #ffdad4;
--md-sys-color-on-surface: #2a1613;
--md-sys-color-surface-variant: #ffdad4;
--md-sys-color-on-surface-variant: #5f3e39;
--md-sys-color-inverse-surface: #422b27;
--md-sys-color-inverse-on-surface: #ffedea;
--md-sys-color-outline: #946e68;
--md-sys-color-outline-variant: #eabcb4;
--md-sys-color-shadow: #000000;
--md-sys-color-scrim: #000000;
--md-sys-color-surface-tint: #c00100;
--md-sys-color-primary: #a50100;
--md-sys-color-on-primary: #ffffff;
--md-sys-color-primary-container: #eb0000;
--md-sys-color-on-primary-container: #ffffff;
--md-sys-color-inverse-primary: #ffb4a8;
--md-sys-color-secondary: #b4271a;
--md-sys-color-on-secondary: #ffffff;
--md-sys-color-secondary-container: #ff7460;
--md-sys-color-on-secondary-container: #2f0000;
--md-sys-color-tertiary: #6f4800;
--md-sys-color-on-tertiary: #ffffff;
--md-sys-color-tertiary-container: #9f6900;
--md-sys-color-on-tertiary-container: #ffffff;
--md-sys-color-error: #ba1a1a;
--md-sys-color-on-error: #ffffff;
--md-sys-color-error-container: #ffdad6;
--md-sys-color-on-error-container: #410002;
}
}
@media (prefers-color-scheme: dark) {
:root {
--md-sys-color-background: #210e0b;
--md-sys-color-on-background: #ffdad4;
--md-sys-color-surface: #210e0b;
--md-sys-color-surface-dim: #210e0b;
--md-sys-color-surface-bright: #4b332f;
--md-sys-color-surface-container-lowest: #1b0907;
--md-sys-color-surface-container-low: #2a1613;
--md-sys-color-surface-container: #2f1a17;
--md-sys-color-surface-container-high: #3a2421;
--md-sys-color-surface-container-highest: #462f2b;
--md-sys-color-on-surface: #ffdad4;
--md-sys-color-surface-variant: #5f3e39;
--md-sys-color-on-surface-variant: #eabcb4;
--md-sys-color-inverse-surface: #ffdad4;
--md-sys-color-inverse-on-surface: #422b27;
--md-sys-color-outline: #b08780;
--md-sys-color-outline-variant: #5f3e39;
--md-sys-color-shadow: #000000;
--md-sys-color-scrim: #000000;
--md-sys-color-surface-tint: #ffb4a8;
--md-sys-color-primary: #ffb4a8;
--md-sys-color-on-primary: #690000;
--md-sys-color-primary-container: #de0000;
--md-sys-color-on-primary-container: #ffffff;
--md-sys-color-inverse-primary: #c00100;
--md-sys-color-secondary: #ffb4a8;
--md-sys-color-on-secondary: #690000;
--md-sys-color-secondary-container: #870100;
--md-sys-color-on-secondary-container: #ffc9c0;
--md-sys-color-tertiary: #feba54;
--md-sys-color-on-tertiary: #452b00;
--md-sys-color-tertiary-container: #966300;
--md-sys-color-on-tertiary-container: #ffffff;
--md-sys-color-error: #ffb4ab;
--md-sys-color-on-error: #690005;
--md-sys-color-error-container: #93000a;
--md-sys-color-on-error-container: #ffdad6;
}
}
@theme {
--color-background: var(--md-sys-color-background);
--color-on-background: var(--md-sys-color-on-background);
--color-surface: var(--md-sys-color-surface);
--color-surface-dim: var(--md-sys-color-surface-dim);
--color-surface-bright: var(--md-sys-color-surface-bright);
--color-surface-container-lowest: var(--md-sys-color-surface-container-lowest);
--color-surface-container-low: var(--md-sys-color-surface-container-low);
--color-surface-container: var(--md-sys-color-surface-container);
--color-surface-container-high: var(--md-sys-color-surface-container-high);
--color-surface-container-highest: var(--md-sys-color-surface-container-highest);
--color-on-surface: var(--md-sys-color-on-surface);
--color-surface-variant: var(--md-sys-color-surface-variant);
--color-on-surface-variant: var(--md-sys-color-on-surface-variant);
--color-inverse-surface: var(--md-sys-color-inverse-surface);
--color-inverse-on-surface: var(--md-sys-color-inverse-on-surface);
--color-outline: var(--md-sys-color-outline);
--color-outline-variant: var(--md-sys-color-outline-variant);
--color-shadow: var(--md-sys-color-shadow);
--color-scrim: var(--md-sys-color-scrim);
--color-surface-tint: var(--md-sys-color-surface-tint);
--color-primary: var(--md-sys-color-primary);
--color-on-primary: var(--md-sys-color-on-primary);
--color-primary-container: var(--md-sys-color-primary-container);
--color-on-primary-container: var(--md-sys-color-on-primary-container);
--color-inverse-primary: var(--md-sys-color-inverse-primary);
--color-secondary: var(--md-sys-color-secondary);
--color-on-secondary: var(--md-sys-color-on-secondary);
--color-secondary-container: var(--md-sys-color-secondary-container);
--color-on-secondary-container: var(--md-sys-color-on-secondary-container);
--color-tertiary: var(--md-sys-color-tertiary);
--color-on-tertiary: var(--md-sys-color-on-tertiary);
--color-tertiary-container: var(--md-sys-color-tertiary-container);
--color-on-tertiary-container: var(--md-sys-color-on-tertiary-container);
--color-error: var(--md-sys-color-error);
--color-on-error: var(--md-sys-color-on-error);
--color-error-container: var(--md-sys-color-error-container);
--color-on-error-container: var(--md-sys-color-on-error-container);
}
a {
@apply text-primary;
}
:root {
font-family: "Inter", "MiSans", sans-serif;
font-weight: 400;
@apply bg-surface text-on-surface;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
appearance: none;
}
@supports (font-variation-settings: normal) {
:root {
font-family: "Inter Variable", "MiSans VF", sans-serif;
font-optical-sizing: auto;
font-weight: 330;
}
}

View File

@ -0,0 +1,19 @@
import { MetaProvider } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";
export default function App() {
return (
<Router
root={(props) => (
<MetaProvider>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}

View File

@ -0,0 +1,9 @@
import { type SVGIconComponent } from "./types";
export const MenuOpen: SVGIconComponent = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="currentColor" d="M4 18q-.425 0-.712-.288T3 17t.288-.712T4 16h11q.425 0 .713.288T16 17t-.288.713T15 18zm14.9-1.7l-3.6-3.6q-.3-.3-.3-.7t.3-.7l3.6-3.6q.275-.275.7-.275t.7.275t.275.7t-.275.7L17.4 12l2.9 2.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275M4 13q-.425 0-.712-.288T3 12t.288-.712T4 11h8q.425 0 .713.288T13 12t-.288.713T12 13zm0-5q-.425 0-.712-.288T3 7t.288-.712T4 6h11q.425 0 .713.288T16 7t-.288.713T15 8z" />
</svg>
);
}

View File

@ -0,0 +1,3 @@
import { Component, JSX } from "solid-js";
type SVGIconComponent = Component<JSX.SvgSVGAttributes<SVGElement>>;

View File

@ -0,0 +1,48 @@
import { dbCred } from "~db/index";
import { loginSessions, users } from "~db/cred/schema";
import { and, eq, gt, isNull, sql } from "drizzle-orm";
import { SensitiveUserFields, UserType } from "~db/outerSchema";
type ReturnedUser = Omit<UserType, SensitiveUserFields>;
export const getUserLoggedin = async (sessionID?: string): Promise<ReturnedUser | null> => {
if (!sessionID) {
return null;
}
const session = await dbCred
.select({
uid: loginSessions.uid
})
.from(loginSessions)
.where(
and(
eq(loginSessions.id, sessionID),
gt(loginSessions.expireAt, sql`now()`),
isNull(loginSessions.deactivatedAt)
)
)
.limit(1);
if (session.length === 0) {
return null;
}
const uid = session[0].uid;
const user: ReturnedUser[] = await dbCred
.select({
id: users.id,
username: users.username,
nickname: users.nickname,
role: users.role,
createdAt: users.createdAt
})
.from(users)
.where(eq(users.id, uid))
.limit(1);
if (user.length === 0) {
return null;
}
console.log("Query for sessionID:", sessionID);
return user[0];
};

View File

@ -0,0 +1,34 @@
import { Accessor, Component, createSignal } from "solid-js";
import { createContext, useContext } from 'solid-js';
type Hook = {
memoizedValue: any | null;
deps: any[] | null;
promise: Promise<any> | null;
}
export type RequestContextValue = Map<string, Hook>;
export type Context = [Accessor<RequestContextValue>, (v: RequestContextValue) => void];
export const RequestContext = createContext<Context | null>(null);
export const RequestContextProvider: Component<{ children: any }> = (props) => {
const initValue: RequestContextValue = new Map();
const [value, setValue] = createSignal(initValue);
const updateValue = (v: RequestContextValue) => {
setValue(v)
};
const context: Context = [value, updateValue];
return <RequestContext.Provider value={context}>{props.children}</RequestContext.Provider>;
};
export function useRequestContext(): Context {
const ctx = useContext(RequestContext);
if (!ctx) {
throw new Error('useRequestContext must be used within a RequestContextProvider');
}
return ctx;
}

View File

@ -0,0 +1,31 @@
import { Component } from "solid-js";
import {
AppBar,
AppBarSearchBox,
IconButton,
LeadingElement,
TrailingElementGroup,
TrailingElement
} from "@m3-components/solid";
import "@m3-components/solid/index.css";
import { MenuOpen } from "~/components/icons/MenuOpen";
export const Header: Component = () => {
return (
<div class="mt-4 top-0 left-0 w-full">
<AppBar variant="search">
<LeadingElement>
<IconButton>
<MenuOpen/>
</IconButton>
</LeadingElement>
<AppBarSearchBox class="text-center placeholder:text-on-surface-variant text-on-surface" placeholder="搜索" />
<TrailingElementGroup>
<TrailingElement>
<IconButton></IconButton>
</TrailingElement>
</TrailingElementGroup>
</AppBar>
</div>
);
};

View File

@ -0,0 +1,44 @@
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
CREATE SEQUENCE "public"."captcha_difficulty_settings_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1;--> statement-breakpoint
CREATE SEQUENCE "public"."users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1;--> statement-breakpoint
CREATE TABLE "captcha_difficulty_settings" (
"id" integer DEFAULT nextval('captcha_difficulty_settings_id_seq'::regclass) NOT NULL,
"method" text NOT NULL,
"path" text NOT NULL,
"duration" real NOT NULL,
"threshold" integer NOT NULL,
"difficulty" integer NOT NULL,
"global" boolean NOT NULL
);
--> statement-breakpoint
CREATE TABLE "login_sessions" (
"id" text NOT NULL,
"uid" integer NOT NULL,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"expire_at" timestamp with time zone,
"last_used_at" timestamp with time zone,
"ip_address" "inet",
"user_agent" text,
"deactivated_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" integer DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
"nickname" text,
"username" text NOT NULL,
"password" text NOT NULL,
"unq_id" text DEFAULT gen_random_uuid() NOT NULL,
"role" text DEFAULT 'USER' NOT NULL,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "captcha_difficulty_settings_pkey" ON "captcha_difficulty_settings" USING btree ("id" int4_ops);--> statement-breakpoint
CREATE INDEX "inx_login-sessions_uid" ON "login_sessions" USING btree ("uid" int4_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "login_sessions_pkey" ON "login_sessions" USING btree ("id" text_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "users_pkey" ON "users" USING btree ("id" int4_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "users_pkey1" ON "users" USING btree ("id" int4_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "users_unq_id_key" ON "users" USING btree ("unq_id" text_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "users_username_key" ON "users" USING btree ("username" text_ops);
*/

View File

@ -0,0 +1,350 @@
{
"id": "00000000-0000-0000-0000-000000000000",
"prevId": "",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.captcha_difficulty_settings": {
"name": "captcha_difficulty_settings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": "nextval('captcha_difficulty_settings_id_seq'::regclass)"
},
"method": {
"name": "method",
"type": "text",
"primaryKey": false,
"notNull": true
},
"path": {
"name": "path",
"type": "text",
"primaryKey": false,
"notNull": true
},
"duration": {
"name": "duration",
"type": "real",
"primaryKey": false,
"notNull": true
},
"threshold": {
"name": "threshold",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"difficulty": {
"name": "difficulty",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"global": {
"name": "global",
"type": "boolean",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"captcha_difficulty_settings_pkey": {
"name": "captcha_difficulty_settings_pkey",
"columns": [
{
"expression": "id",
"asc": true,
"nulls": "last",
"opclass": "int4_ops",
"isExpression": false
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
},
"public.login_sessions": {
"name": "login_sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"uid": {
"name": "uid",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "CURRENT_TIMESTAMP"
},
"expire_at": {
"name": "expire_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"last_used_at": {
"name": "last_used_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"ip_address": {
"name": "ip_address",
"type": "inet",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"deactivated_at": {
"name": "deactivated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"inx_login-sessions_uid": {
"name": "inx_login-sessions_uid",
"columns": [
{
"expression": "uid",
"asc": true,
"nulls": "last",
"opclass": "int4_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"login_sessions_pkey": {
"name": "login_sessions_pkey",
"columns": [
{
"expression": "id",
"asc": true,
"nulls": "last",
"opclass": "text_ops",
"isExpression": false
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": "nextval('users_id_seq'::regclass)"
},
"nickname": {
"name": "nickname",
"type": "text",
"primaryKey": false,
"notNull": false
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"unq_id": {
"name": "unq_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "gen_random_uuid()"
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'USER'"
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "CURRENT_TIMESTAMP"
}
},
"indexes": {
"users_pkey": {
"name": "users_pkey",
"columns": [
{
"expression": "id",
"asc": true,
"nulls": "last",
"opclass": "int4_ops",
"isExpression": false
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"users_pkey1": {
"name": "users_pkey1",
"columns": [
{
"expression": "id",
"asc": true,
"nulls": "last",
"opclass": "int4_ops",
"isExpression": false
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"users_unq_id_key": {
"name": "users_unq_id_key",
"columns": [
{
"expression": "unq_id",
"asc": true,
"nulls": "last",
"opclass": "text_ops",
"isExpression": false
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"users_username_key": {
"name": "users_username_key",
"columns": [
{
"expression": "username",
"asc": true,
"nulls": "last",
"opclass": "text_ops",
"isExpression": false
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {
"public.captcha_difficulty_settings_id_seq": {
"name": "captcha_difficulty_settings_id_seq",
"schema": "public",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"increment": "1",
"cycle": false,
"cache": "1"
},
"public.users_id_seq": {
"name": "users_id_seq",
"schema": "public",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"increment": "1",
"cycle": false,
"cache": "1"
}
},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {
"captcha_difficulty_settings": {
"columns": {
"id": {
"isDefaultAnExpression": true
}
}
},
"users": {
"columns": {
"id": {
"isDefaultAnExpression": true
}
}
}
}
}
}

View File

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1750513073792,
"tag": "0000_moaning_shotgun",
"breakpoints": true
}
]
}

View File

@ -0,0 +1,3 @@
import { relations } from "drizzle-orm/relations";
import { } from "./schema";

View File

@ -0,0 +1,47 @@
import { pgTable, uniqueIndex, integer, text, real, boolean, index, timestamp, inet, pgSequence } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"
export const captchaDifficultySettingsIdSeq = pgSequence("captcha_difficulty_settings_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false })
export const usersIdSeq = pgSequence("users_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false })
export const captchaDifficultySettings = pgTable("captcha_difficulty_settings", {
id: integer().default(sql`nextval('captcha_difficulty_settings_id_seq'::regclass)`).notNull(),
method: text().notNull(),
path: text().notNull(),
duration: real().notNull(),
threshold: integer().notNull(),
difficulty: integer().notNull(),
global: boolean().notNull(),
}, (table) => [
uniqueIndex("captcha_difficulty_settings_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")),
]);
export const loginSessions = pgTable("login_sessions", {
id: text().notNull(),
uid: integer().notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
expireAt: timestamp("expire_at", { withTimezone: true, mode: 'string' }),
lastUsedAt: timestamp("last_used_at", { withTimezone: true, mode: 'string' }),
ipAddress: inet("ip_address"),
userAgent: text("user_agent"),
deactivatedAt: timestamp("deactivated_at", { withTimezone: true, mode: 'string' }),
}, (table) => [
index("inx_login-sessions_uid").using("btree", table.uid.asc().nullsLast().op("int4_ops")),
uniqueIndex("login_sessions_pkey").using("btree", table.id.asc().nullsLast().op("text_ops")),
]);
export const users = pgTable("users", {
id: integer().default(sql`nextval('users_id_seq'::regclass)`).notNull(),
nickname: text(),
username: text().notNull(),
password: text().notNull(),
unqId: text("unq_id").default(sql`gen_random_uuid()`).notNull(),
role: text().default('USER').notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
}, (table) => [
uniqueIndex("users_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")),
uniqueIndex("users_pkey1").using("btree", table.id.asc().nullsLast().op("int4_ops")),
uniqueIndex("users_unq_id_key").using("btree", table.unqId.asc().nullsLast().op("text_ops")),
uniqueIndex("users_username_key").using("btree", table.username.asc().nullsLast().op("text_ops")),
]);

View File

@ -0,0 +1,7 @@
"use server";
import { drizzle } from 'drizzle-orm/postgres-js';
import { sqlCred, sql } from "@cvsa/core";
export const dbMain = drizzle(sql);
export const dbCred = drizzle(sqlCred);

View File

@ -0,0 +1,157 @@
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
CREATE SEQUENCE "public"."all_data_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1;--> statement-breakpoint
CREATE SEQUENCE "public"."labeling_result_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1;--> statement-breakpoint
CREATE SEQUENCE "public"."songs_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1;--> statement-breakpoint
CREATE SEQUENCE "public"."video_snapshot_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1;--> statement-breakpoint
CREATE SEQUENCE "public"."views_increment_rate_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1;--> statement-breakpoint
CREATE TABLE "content" (
"page_id" text PRIMARY KEY NOT NULL,
"page_content" text NOT NULL,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" timestamp with time zone,
"deleted_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "bilibili_user" (
"id" serial PRIMARY KEY NOT NULL,
"uid" bigint NOT NULL,
"username" text NOT NULL,
"desc" text NOT NULL,
"fans" integer NOT NULL,
CONSTRAINT "unq_bili-user_uid" UNIQUE("uid")
);
--> statement-breakpoint
CREATE TABLE "bilibili_metadata" (
"id" integer DEFAULT nextval('all_data_id_seq'::regclass) NOT NULL,
"aid" bigint NOT NULL,
"bvid" varchar(12),
"description" text,
"uid" bigint,
"tags" text,
"title" text,
"published_at" timestamp with time zone,
"duration" integer,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
"status" integer DEFAULT 0 NOT NULL,
"cover_url" text
);
--> statement-breakpoint
CREATE TABLE "classified_labels_human" (
"id" serial PRIMARY KEY NOT NULL,
"aid" bigint NOT NULL,
"author" uuid NOT NULL,
"label" smallint NOT NULL,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "labelling_result" (
"id" integer DEFAULT nextval('labeling_result_id_seq'::regclass) NOT NULL,
"aid" bigint NOT NULL,
"label" smallint NOT NULL,
"model_version" text NOT NULL,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"logits" smallint[]
);
--> statement-breakpoint
CREATE TABLE "latest_video_snapshot" (
"aid" bigint PRIMARY KEY NOT NULL,
"time" timestamp with time zone NOT NULL,
"views" integer NOT NULL,
"coins" integer NOT NULL,
"likes" integer NOT NULL,
"favorites" integer NOT NULL,
"replies" integer NOT NULL,
"danmakus" integer NOT NULL,
"shares" integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE "video_snapshot" (
"id" integer DEFAULT nextval('video_snapshot_id_seq'::regclass) NOT NULL,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"views" integer NOT NULL,
"coins" integer NOT NULL,
"likes" integer NOT NULL,
"favorites" integer NOT NULL,
"shares" integer NOT NULL,
"danmakus" integer NOT NULL,
"aid" bigint NOT NULL,
"replies" integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE "snapshot_schedule" (
"id" bigserial NOT NULL,
"aid" bigint NOT NULL,
"type" text,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"started_at" timestamp with time zone,
"finished_at" timestamp with time zone,
"status" text DEFAULT 'pending' NOT NULL
);
--> statement-breakpoint
CREATE TABLE "songs" (
"id" integer DEFAULT nextval('songs_id_seq'::regclass) NOT NULL,
"name" text,
"aid" bigint,
"published_at" timestamp with time zone,
"duration" integer,
"type" smallint,
"romanized_name" text,
"netease_id" bigint,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"deleted" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
CREATE TABLE "views_increment_rate" (
"id" integer DEFAULT nextval('views_increment_rate_id_seq'::regclass) NOT NULL,
"aid" bigint NOT NULL,
"old_time" timestamp with time zone NOT NULL,
"new_time" timestamp with time zone NOT NULL,
"old_views" integer NOT NULL,
"new_views" integer NOT NULL,
"interval" interval NOT NULL,
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"speed" real
);
--> statement-breakpoint
CREATE INDEX "idx_content_created-at" ON "content" USING btree ("created_at" timestamptz_ops);--> statement-breakpoint
CREATE INDEX "idx_bili-user_uid" ON "bilibili_user" USING btree ("uid" int8_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "all_data_pkey" ON "bilibili_metadata" USING btree ("id" int4_ops);--> statement-breakpoint
CREATE INDEX "idx_all-data_aid" ON "bilibili_metadata" USING btree ("aid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_all-data_bvid" ON "bilibili_metadata" USING btree ("bvid" text_ops);--> statement-breakpoint
CREATE INDEX "idx_all-data_uid" ON "bilibili_metadata" USING btree ("uid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_bili-meta_status" ON "bilibili_metadata" USING btree ("status" int4_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "unq_all-data_aid" ON "bilibili_metadata" USING btree ("aid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_classified-labels-human_aid" ON "classified_labels_human" USING btree ("aid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_classified-labels-human_author" ON "classified_labels_human" USING btree ("author" uuid_ops);--> statement-breakpoint
CREATE INDEX "idx_classified-labels-human_created-at" ON "classified_labels_human" USING btree ("created_at" timestamptz_ops);--> statement-breakpoint
CREATE INDEX "idx_classified-labels-human_label" ON "classified_labels_human" USING btree ("label" int2_ops);--> statement-breakpoint
CREATE INDEX "idx_labeling_label_model-version" ON "labelling_result" USING btree ("label" int2_ops,"model_version" int2_ops);--> statement-breakpoint
CREATE INDEX "idx_labeling_model-version" ON "labelling_result" USING btree ("model_version" text_ops);--> statement-breakpoint
CREATE INDEX "idx_labelling_aid-label" ON "labelling_result" USING btree ("aid" int2_ops,"label" int2_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "labeling_result_pkey" ON "labelling_result" USING btree ("id" int4_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "unq_labelling-result_aid_model-version" ON "labelling_result" USING btree ("aid" int8_ops,"model_version" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_latest-video-snapshot_time" ON "latest_video_snapshot" USING btree ("time" timestamptz_ops);--> statement-breakpoint
CREATE INDEX "idx_latest-video-snapshot_views" ON "latest_video_snapshot" USING btree ("views" int4_ops);--> statement-breakpoint
CREATE INDEX "idx_vid_snapshot_aid" ON "video_snapshot" USING btree ("aid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_vid_snapshot_time" ON "video_snapshot" USING btree ("created_at" timestamptz_ops);--> statement-breakpoint
CREATE INDEX "idx_vid_snapshot_views" ON "video_snapshot" USING btree ("views" int4_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "video_snapshot_pkey" ON "video_snapshot" USING btree ("id" int4_ops);--> statement-breakpoint
CREATE INDEX "idx_snapshot_schedule_aid" ON "snapshot_schedule" USING btree ("aid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_snapshot_schedule_started_at" ON "snapshot_schedule" USING btree ("started_at" timestamptz_ops);--> statement-breakpoint
CREATE INDEX "idx_snapshot_schedule_status" ON "snapshot_schedule" USING btree ("status" text_ops);--> statement-breakpoint
CREATE INDEX "idx_snapshot_schedule_type" ON "snapshot_schedule" USING btree ("type" text_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "snapshot_schedule_pkey" ON "snapshot_schedule" USING btree ("id" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_aid" ON "songs" USING btree ("aid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_hash_songs_aid" ON "songs" USING hash ("aid" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_netease_id" ON "songs" USING btree ("netease_id" int8_ops);--> statement-breakpoint
CREATE INDEX "idx_published_at" ON "songs" USING btree ("published_at" timestamptz_ops);--> statement-breakpoint
CREATE INDEX "idx_type" ON "songs" USING btree ("type" int2_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "songs_pkey" ON "songs" USING btree ("id" int4_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "unq_songs_aid" ON "songs" USING btree ("aid" int8_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "unq_songs_netease_id" ON "songs" USING btree ("netease_id" int8_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "unq_views-increment-rate_aid_interval" ON "views_increment_rate" USING btree ("aid" int8_ops,"interval" int8_ops);--> statement-breakpoint
CREATE UNIQUE INDEX "views_increment_rate_pkey" ON "views_increment_rate" USING btree ("id" int4_ops);
*/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1750513105905,
"tag": "0000_fresh_mac_gargan",
"breakpoints": true
}
]
}

View File

@ -0,0 +1,3 @@
import { relations } from "drizzle-orm/relations";
import { } from "./schema";

View File

@ -0,0 +1,178 @@
import { pgTable, index, text, timestamp, unique, serial, bigint, integer, uniqueIndex, varchar, uuid, smallint, bigserial, boolean, interval, real, pgSequence } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"
export const allDataIdSeq = pgSequence("all_data_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false })
export const labelingResultIdSeq = pgSequence("labeling_result_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false })
export const songsIdSeq = pgSequence("songs_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false })
export const videoSnapshotIdSeq = pgSequence("video_snapshot_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false })
export const viewsIncrementRateIdSeq = pgSequence("views_increment_rate_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "9223372036854775807", cache: "1", cycle: false })
export const content = pgTable("content", {
pageId: text("page_id").primaryKey().notNull(),
pageContent: text("page_content").notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }),
deletedAt: timestamp("deleted_at", { withTimezone: true, mode: 'string' }),
}, (table) => [
index("idx_content_created-at").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")),
]);
export const bilibiliUser = pgTable("bilibili_user", {
id: serial().primaryKey().notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
uid: bigint({ mode: "number" }).notNull(),
username: text().notNull(),
desc: text().notNull(),
fans: integer().notNull(),
}, (table) => [
index("idx_bili-user_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")),
unique("unq_bili-user_uid").on(table.uid),
]);
export const bilibiliMetadata = pgTable("bilibili_metadata", {
id: integer().default(sql`nextval('all_data_id_seq'::regclass)`).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }).notNull(),
bvid: varchar({ length: 12 }),
description: text(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
uid: bigint({ mode: "number" }),
tags: text(),
title: text(),
publishedAt: timestamp("published_at", { withTimezone: true, mode: 'string' }),
duration: integer(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
status: integer().default(0).notNull(),
coverUrl: text("cover_url"),
}, (table) => [
uniqueIndex("all_data_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")),
index("idx_all-data_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")),
index("idx_all-data_bvid").using("btree", table.bvid.asc().nullsLast().op("text_ops")),
index("idx_all-data_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")),
index("idx_bili-meta_status").using("btree", table.status.asc().nullsLast().op("int4_ops")),
uniqueIndex("unq_all-data_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")),
]);
export const classifiedLabelsHuman = pgTable("classified_labels_human", {
id: serial().primaryKey().notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }).notNull(),
author: uuid().notNull(),
label: smallint().notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
}, (table) => [
index("idx_classified-labels-human_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")),
index("idx_classified-labels-human_author").using("btree", table.author.asc().nullsLast().op("uuid_ops")),
index("idx_classified-labels-human_created-at").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")),
index("idx_classified-labels-human_label").using("btree", table.label.asc().nullsLast().op("int2_ops")),
]);
export const labellingResult = pgTable("labelling_result", {
id: integer().default(sql`nextval('labeling_result_id_seq'::regclass)`).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }).notNull(),
label: smallint().notNull(),
modelVersion: text("model_version").notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
logits: smallint().array(),
}, (table) => [
index("idx_labeling_label_model-version").using("btree", table.label.asc().nullsLast().op("int2_ops"), table.modelVersion.asc().nullsLast().op("int2_ops")),
index("idx_labeling_model-version").using("btree", table.modelVersion.asc().nullsLast().op("text_ops")),
index("idx_labelling_aid-label").using("btree", table.aid.asc().nullsLast().op("int2_ops"), table.label.asc().nullsLast().op("int2_ops")),
uniqueIndex("labeling_result_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")),
uniqueIndex("unq_labelling-result_aid_model-version").using("btree", table.aid.asc().nullsLast().op("int8_ops"), table.modelVersion.asc().nullsLast().op("int8_ops")),
]);
export const latestVideoSnapshot = pgTable("latest_video_snapshot", {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }).primaryKey().notNull(),
time: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
views: integer().notNull(),
coins: integer().notNull(),
likes: integer().notNull(),
favorites: integer().notNull(),
replies: integer().notNull(),
danmakus: integer().notNull(),
shares: integer().notNull(),
}, (table) => [
index("idx_latest-video-snapshot_time").using("btree", table.time.asc().nullsLast().op("timestamptz_ops")),
index("idx_latest-video-snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")),
]);
export const videoSnapshot = pgTable("video_snapshot", {
id: integer().default(sql`nextval('video_snapshot_id_seq'::regclass)`).notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
views: integer().notNull(),
coins: integer().notNull(),
likes: integer().notNull(),
favorites: integer().notNull(),
shares: integer().notNull(),
danmakus: integer().notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }).notNull(),
replies: integer().notNull(),
}, (table) => [
index("idx_vid_snapshot_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")),
index("idx_vid_snapshot_time").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")),
index("idx_vid_snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")),
uniqueIndex("video_snapshot_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")),
]);
export const snapshotSchedule = pgTable("snapshot_schedule", {
id: bigserial({ mode: "bigint" }).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }).notNull(),
type: text(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
startedAt: timestamp("started_at", { withTimezone: true, mode: 'string' }),
finishedAt: timestamp("finished_at", { withTimezone: true, mode: 'string' }),
status: text().default('pending').notNull(),
}, (table) => [
index("idx_snapshot_schedule_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")),
index("idx_snapshot_schedule_started_at").using("btree", table.startedAt.asc().nullsLast().op("timestamptz_ops")),
index("idx_snapshot_schedule_status").using("btree", table.status.asc().nullsLast().op("text_ops")),
index("idx_snapshot_schedule_type").using("btree", table.type.asc().nullsLast().op("text_ops")),
uniqueIndex("snapshot_schedule_pkey").using("btree", table.id.asc().nullsLast().op("int8_ops")),
]);
export const songs = pgTable("songs", {
id: integer().default(sql`nextval('songs_id_seq'::regclass)`).notNull(),
name: text(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }),
publishedAt: timestamp("published_at", { withTimezone: true, mode: 'string' }),
duration: integer(),
type: smallint(),
romanizedName: text("romanized_name"),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
neteaseId: bigint("netease_id", { mode: "number" }),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
deleted: boolean().default(false).notNull(),
}, (table) => [
index("idx_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")),
index("idx_hash_songs_aid").using("hash", table.aid.asc().nullsLast().op("int8_ops")),
index("idx_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")),
index("idx_published_at").using("btree", table.publishedAt.asc().nullsLast().op("timestamptz_ops")),
index("idx_type").using("btree", table.type.asc().nullsLast().op("int2_ops")),
uniqueIndex("songs_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")),
uniqueIndex("unq_songs_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")),
uniqueIndex("unq_songs_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")),
]);
export const viewsIncrementRate = pgTable("views_increment_rate", {
id: integer().default(sql`nextval('views_increment_rate_id_seq'::regclass)`).notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
aid: bigint({ mode: "number" }).notNull(),
oldTime: timestamp("old_time", { withTimezone: true, mode: 'string' }).notNull(),
newTime: timestamp("new_time", { withTimezone: true, mode: 'string' }).notNull(),
oldViews: integer("old_views").notNull(),
newViews: integer("new_views").notNull(),
interval: interval().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(),
speed: real(),
}, (table) => [
uniqueIndex("unq_views-increment-rate_aid_interval").using("btree", table.aid.asc().nullsLast().op("int8_ops"), table.interval.asc().nullsLast().op("int8_ops")),
uniqueIndex("views_increment_rate_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")),
]);

View File

@ -0,0 +1,8 @@
import type { InferSelectModel } from "drizzle-orm";
import { users } from "~db/cred/schema";
import { bilibiliMetadata, videoSnapshot } from "~db/main/schema";
export type UserType = InferSelectModel<typeof users>;
export type SensitiveUserFields = "password" | "unqId";
export type BilibiliMetadataType = InferSelectModel<typeof bilibiliMetadata>;
export type VideoSnapshotType = InferSelectModel<typeof videoSnapshot>;

View File

@ -0,0 +1,15 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client";
import { RequestContextProvider } from "./components/requestContext";
import { MetaProvider } from "@solidjs/meta";
mount(
() => (
<RequestContextProvider>
<MetaProvider>
<StartClient />
</MetaProvider>
</RequestContextProvider>
),
document.getElementById("app")!
);

View File

@ -0,0 +1,27 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server";
import { MetaProvider } from "@solidjs/meta";
import { RequestContextProvider } from "~/components/requestContext";
export default createHandler(() => (
<RequestContextProvider>
<MetaProvider>
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{/*<link rel="icon" href="/favicon.ico" />*/}
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
</MetaProvider>
</RequestContextProvider>
));

BIN
packages/solid/src/fonts/InterFont/Inter-Black.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-BlackItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Bold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-BoldItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-ExtraBold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-ExtraLight.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Italic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Light.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-LightItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Medium.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-MediumItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Regular.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-SemiBold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Thin.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-ThinItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,449 @@
@font-face {
font-family: "Inter Variable";
font-style: normal;
font-weight: 100 900;
font-display: fallback;
src: url("InterVariable.woff2") format("woff2");
}
@font-face {
font-family: "Inter Variable";
font-style: italic;
font-weight: 100 900;
font-display: fallback;
src: url("InterVariable-Italic.woff2") format("woff2");
}
/* static fonts */
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 100;
font-display: fallback;
src: url("Inter-Thin.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 100;
font-display: fallback;
src: url("Inter-ThinItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 200;
font-display: fallback;
src: url("Inter-ExtraLight.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 200;
font-display: fallback;
src: url("Inter-ExtraLightItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url("Inter-Light.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url("Inter-LightItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url("Inter-Regular.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url("Inter-Italic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 500;
font-display: fallback;
src: url("Inter-Medium.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 500;
font-display: fallback;
src: url("Inter-MediumItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url("Inter-SemiBold.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url("Inter-SemiBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url("Inter-Bold.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url("Inter-BoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 800;
font-display: fallback;
src: url("Inter-ExtraBold.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 800;
font-display: fallback;
src: url("Inter-ExtraBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 900;
font-display: fallback;
src: url("Inter-Black.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 900;
font-display: fallback;
src: url("Inter-BlackItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 100;
font-display: fallback;
src: url("InterDisplay-Thin.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 100;
font-display: fallback;
src: url("InterDisplay-ThinItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 200;
font-display: fallback;
src: url("InterDisplay-ExtraLight.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 200;
font-display: fallback;
src: url("InterDisplay-ExtraLightItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url("InterDisplay-Light.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url("InterDisplay-LightItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url("InterDisplay-Regular.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url("InterDisplay-Italic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 500;
font-display: fallback;
src: url("InterDisplay-Medium.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 500;
font-display: fallback;
src: url("InterDisplay-MediumItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url("InterDisplay-SemiBold.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url("InterDisplay-SemiBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url("InterDisplay-Bold.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url("InterDisplay-BoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 800;
font-display: fallback;
src: url("InterDisplay-ExtraBold.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 800;
font-display: fallback;
src: url("InterDisplay-ExtraBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 900;
font-display: fallback;
src: url("InterDisplay-Black.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 900;
font-display: fallback;
src: url("InterDisplay-BlackItalic.woff2") format("woff2");
}
@font-feature-values InterVariable {
@character-variant {
cv01: 1;
cv02: 2;
cv03: 3;
cv04: 4;
cv05: 5;
cv06: 6;
cv07: 7;
cv08: 8;
cv09: 9;
cv10: 10;
cv11: 11;
cv12: 12;
cv13: 13;
alt-1: 1; /* Alternate one */
alt-3: 9; /* Flat-top three */
open-4: 2; /* Open four */
open-6: 3; /* Open six */
open-9: 4; /* Open nine */
lc-l-with-tail: 5; /* Lower-case L with tail */
simplified-u: 6; /* Simplified u */
alt-double-s: 7; /* Alternate German double s */
uc-i-with-serif: 8; /* Upper-case i with serif */
uc-g-with-spur: 10; /* Capital G with spur */
single-story-a: 11; /* Single-story a */
compact-lc-f: 12; /* Compact f */
compact-lc-t: 13; /* Compact t */
}
@styleset {
ss01: 1;
ss02: 2;
ss03: 3;
ss04: 4;
ss05: 5;
ss06: 6;
ss07: 7;
ss08: 8;
open-digits: 1; /* Open digits */
disambiguation: 2; /* Disambiguation (with zero) */
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
round-quotes-and-commas: 3; /* Round quotes &amp; commas */
square-punctuation: 7; /* Square punctuation */
square-quotes: 8; /* Square quotes */
circled-characters: 5; /* Circled characters */
squared-characters: 6; /* Squared characters */
}
}
@font-feature-values Inter {
@character-variant {
cv01: 1;
cv02: 2;
cv03: 3;
cv04: 4;
cv05: 5;
cv06: 6;
cv07: 7;
cv08: 8;
cv09: 9;
cv10: 10;
cv11: 11;
cv12: 12;
cv13: 13;
alt-1: 1; /* Alternate one */
alt-3: 9; /* Flat-top three */
open-4: 2; /* Open four */
open-6: 3; /* Open six */
open-9: 4; /* Open nine */
lc-l-with-tail: 5; /* Lower-case L with tail */
simplified-u: 6; /* Simplified u */
alt-double-s: 7; /* Alternate German double s */
uc-i-with-serif: 8; /* Upper-case i with serif */
uc-g-with-spur: 10; /* Capital G with spur */
single-story-a: 11; /* Single-story a */
compact-lc-f: 12; /* Compact f */
compact-lc-t: 13; /* Compact t */
}
@styleset {
ss01: 1;
ss02: 2;
ss03: 3;
ss04: 4;
ss05: 5;
ss06: 6;
ss07: 7;
ss08: 8;
open-digits: 1; /* Open digits */
disambiguation: 2; /* Disambiguation (with zero) */
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
round-quotes-and-commas: 3; /* Round quotes &amp; commas */
square-punctuation: 7; /* Square punctuation */
square-quotes: 8; /* Square quotes */
circled-characters: 5; /* Circled characters */
squared-characters: 6; /* Squared characters */
}
}
@font-feature-values InterDisplay {
@character-variant {
cv01: 1;
cv02: 2;
cv03: 3;
cv04: 4;
cv05: 5;
cv06: 6;
cv07: 7;
cv08: 8;
cv09: 9;
cv10: 10;
cv11: 11;
cv12: 12;
cv13: 13;
alt-1: 1; /* Alternate one */
alt-3: 9; /* Flat-top three */
open-4: 2; /* Open four */
open-6: 3; /* Open six */
open-9: 4; /* Open nine */
lc-l-with-tail: 5; /* Lower-case L with tail */
simplified-u: 6; /* Simplified u */
alt-double-s: 7; /* Alternate German double s */
uc-i-with-serif: 8; /* Upper-case i with serif */
uc-g-with-spur: 10; /* Capital G with spur */
single-story-a: 11; /* Single-story a */
compact-lc-f: 12; /* Compact f */
compact-lc-t: 13; /* Compact t */
}
@styleset {
ss01: 1;
ss02: 2;
ss03: 3;
ss04: 4;
ss05: 5;
ss06: 6;
ss07: 7;
ss08: 8;
open-digits: 1; /* Open digits */
disambiguation: 2; /* Disambiguation (with zero) */
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
round-quotes-and-commas: 3; /* Round quotes &amp; commas */
square-punctuation: 7; /* Square punctuation */
square-quotes: 8; /* Square quotes */
circled-characters: 5; /* Circled characters */
squared-characters: 6; /* Squared characters */
}
}

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Black.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Bold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Italic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Light.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Medium.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Thin.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterVariable.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans VF.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Bold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Demibold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-ExtraLight.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Heavy.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Light.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Medium.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Normal.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Regular.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Semibold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Thin.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,87 @@
@font-face {
font-family: "MiSans VF";
font-style: normal;
font-weight: 150 700;
font-display: fallback;
src: url("MiSans VF.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 100;
font-display: fallback;
src: url("MiSans-Thin.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 200;
font-display: fallback;
src: url("MiSans-ExtraLight.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url("MiSans-Light.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 360;
font-display: fallback;
src: url("MiSans-Normal.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url("MiSans-Regular.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 500;
font-display: fallback;
src: url("MiSans-Medium.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url("MiSans-Demibold.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url("MiSans-Semibold.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 800;
font-display: fallback;
src: url("MiSans-Bold.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 900;
font-display: fallback;
src: url("MiSans-Heavy.woff2") format("woff2");
}

1
packages/solid/src/global.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="@solidjs/start/env" />

View File

@ -0,0 +1,25 @@
const N_1024 = BigInt(
"129023318876534346704360951712586568674758913224876821534686030409476129469193481910786173836188085930974906857867802234113909470848523288588793477904039083513378341278558405407018889387577114155572311708428733260891448259786041525189132461448841652472631435226032063278124857443496954605482776113964107326943"
);
const N_2048 = BigInt(
"23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109"
);
const N_1792 = BigInt(
"23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109"
);
const N_1536 = BigInt(
"1694330250214463438908848400950857073137355630337290254958754184668036770489801447652464038218330711288158361242955860326168191830448553710492926795708495297280933502917598985378231124113971732841791156356676046934277122699383776036675381503510992810963611269045078440132744168908318454891211962146563551929591147663448816841024591820348784855441153716551049843185172472891407933214238000452095646085222944171689449292644270516031799660928056315886939284985905227"
);
const N_3072 = BigInt(
"4432919939296042464443862503456460073874727648022810391370558006281079088795179408238989283371442564716849343712703672836423961818025813387453469700639513190304802553045342607888612037304066433501317127429264242784608682213025490491212489901736408833027611579294436675682774458141490718959615677971745638214649336218217578937534746160749039668886450447773018369168258067682196337978245372237157696236362344796867228581553446331915147012787367438751646936429739232247148712001806846526947508445039707404287951727838234648917450736371192435665040644040487427986702098273581288935278964444790007953559851323281510927332862225214878776790605026472021669614552481167977412450477230442015077669503312683966631454347169703030544483487968842349634064181183599641180349414682042575010303056241481622837185325228233789954078775053744988023738762706404546546146837242590884760044438874357295029411988267287001033032827035809135092270843"
);
const N_4096 = BigInt(
"703671044356805218391078271512201582198770553281951369783674142891088501340774249238173262580562112786670043634665390581120113644316651934154746357220932310140476300088580654571796404198410555061275065442553506658401183560336140989074165998202690496991174269748740565700402715364422506782445179963440819952745241176450402011121226863984008975377353558155910994380700267903933205531681076494639818328879475919332604951949178075254600102192323286738973253864238076198710173840170988339024438220034106150475640983877458155141500313471699516670799821379238743709125064098477109094533426340852518505385314780319279862586851512004686798362431227795743253799490998475141728082088984359237540124375439664236138519644100625154580910233437864328111620708697941949936338367445851449766581651338876219676721272448769082914348242483068204896479076062102236087066428603930888978596966798402915747531679758905013008059396214343112694563043918465373870648649652122703709658068801764236979191262744515840224548957285182453209028157886219424802426566456408109642062498413592155064289314088837031184200671561102160059065729282902863248815224399131391716503171191977463328439766546574118092303414702384104112719959325482439604572518549918705623086363111"
);
export const N_ARRAY = [N_1024, N_1536, N_1792, N_2048, N_3072, N_4096];

View File

@ -0,0 +1,32 @@
import { Context } from "~/components/requestContext";
export async function useCachedFetch<T>(
fetcher: () => Promise<T>,
identifier: string,
context: Context,
deps: any[]
): Promise<T> {
const [contextSignal, updateContext] = context;
const hooks = contextSignal();
let hook = hooks.get(identifier);
if (hook && hook.promise) {
return hook.promise;
}
hook = {
memoizedValue: null,
deps: deps,
promise: null
};
const promise = fetcher().then((result) => {
hook!.memoizedValue = result;
hooks.set(identifier, hook!);
updateContext(hooks);
return result;
});
hook.promise = promise;
hooks.set(identifier, hook!);
updateContext(hooks);
return promise;
}

View File

@ -0,0 +1,70 @@
import axios, { AxiosRequestConfig, AxiosError, Method, AxiosResponse } from "axios";
export class ApiRequestError extends Error {
public code: number | undefined;
public response: unknown | undefined;
constructor(message: string, res?: unknown, code?: number) {
super(message);
this.name = "ApiRequestError";
this.code = code;
this.response = res;
}
}
type HttpMethod = Extract<Method, "GET" | "POST" | "PUT" | "DELETE" | "PATCH">;
const httpMethods = {
get: axios.get,
post: axios.post,
put: axios.put,
delete: axios.delete,
patch: axios.patch
};
export function fetcher(url: string): Promise<unknown>;
export function fetcher<JSON = unknown>(
url: string,
init?: Omit<AxiosRequestConfig, "method"> & { method?: Exclude<HttpMethod, "DELETE"> }
): Promise<JSON>;
export function fetcher(
url: string,
init: Omit<AxiosRequestConfig, "method"> & { method: "DELETE" }
): Promise<AxiosResponse>;
export async function fetcher<JSON = unknown>(
url: string,
init?: Omit<AxiosRequestConfig, "method"> & { method?: HttpMethod }
): Promise<JSON | AxiosResponse<any, any>> {
const { method = "get", data, ...config } = init || {};
const fullConfig: AxiosRequestConfig = {
method,
...config,
timeout: 10000
};
try {
const m = method.toLowerCase() as keyof typeof httpMethods;
if (["post", "patch", "put"].includes(m)) {
const response = await httpMethods[m](url, data, fullConfig);
return response.data;
} else if (m === "delete") {
const response = await axios.delete(url, fullConfig);
return response;
} else {
const response = await httpMethods[m](url, fullConfig);
return response.data;
}
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response) {
const { status, data } = axiosError.response;
throw new ApiRequestError(`HTTP error! status: ${status}`, data, status);
} else if (axiosError.request) {
throw new ApiRequestError("No response received", undefined, -1);
} else {
throw new ApiRequestError(axiosError.message || "Unknown error");
}
}
}

View File

@ -0,0 +1,117 @@
// Define interfaces for input and output
interface VdfProgressCallback {
(progress: number): void;
}
interface VdfResult {
result: bigint;
time: number; // Time taken in milliseconds
}
// The content of the Web Worker script
const workerContent = `addEventListener("message", async (event) => {
const { g, N, difficulty } = event.data;
// Although pow is not used in the iterative VDF, it's good to keep the original worker code structure.
// The iterative computeVDFWithProgress is better for progress reporting.
function pow(base, exponent, mod) {
let result = 1n;
base = base % mod;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exponent = exponent / 2n;
// Using BigInt division (/) which performs integer division
}
return result;
}
// Compute VDF iteratively to report progress
function computeVDFWithProgress(g, N, T, postProgress) {
let result = g;
let latestTime = performance.now();
const totalSteps = T; // T is the difficulty, representing 2^T squaring steps
for (let i = 0n; i < totalSteps; i++) {
result = (result * result) % N;
// Report progress periodically (approx. every 16ms to match typical frame rate)
if (performance.now() - latestTime > 16) {
// Calculate progress as a percentage
const progress = Number((i + 1n) * 10000n / totalSteps) / 100; // Using 10000 for better precision before dividing by 100
postProgress(progress);
latestTime = performance.now();
}
}
// Ensure final progress is reported
postProgress(100);
return result;
}
const startTime = performance.now();
// The worker computes g^(2^difficulty) mod N. The loop runs 'difficulty' times, performing squaring.
const result = computeVDFWithProgress(g, N, difficulty, (progress) => {
// Post progress back to the main thread
postMessage({ type: "progress", progress: progress });
});
const endTime = performance.now();
const timeTaken = endTime - startTime;
// Post the final result and time taken back to the main thread
postMessage({ type: "result", result: result.toString(), time: timeTaken });
});
`;
/**
* Computes the Verifiable Delay Function (VDF) result g^(2^difficulty) mod N
* in a Web Worker and reports progress.
* @param g - The base (bigint).
* @param N - The modulus (bigint).
* @param difficulty - The number of squaring steps (T) (bigint).
* @param onProgress - Optional callback function to receive progress updates (0-100).
* @returns A Promise that resolves with the VDF result and time taken.
*/
export function computeVdfInWorker(
g: bigint,
N: bigint,
difficulty: bigint,
onProgress?: VdfProgressCallback
): Promise<VdfResult> {
return new Promise((resolve, reject) => {
// Create a Blob containing the worker script
const blob = new Blob([workerContent], { type: "text/javascript" });
// Create a URL for the Blob
const workerUrl = URL.createObjectURL(blob);
// Create a new Web Worker
const worker = new window.Worker(workerUrl);
// Handle messages from the worker
worker.onmessage = (event) => {
const { type, progress, result, time } = event.data;
if (type === "progress") {
if (onProgress) {
onProgress(progress);
}
} else if (type === "result") {
// Resolve the promise with the result and time
resolve({ result: BigInt(result), time });
// Terminate the worker and revoke the URL
worker.terminate();
URL.revokeObjectURL(workerUrl);
}
};
// Handle potential errors in the worker
worker.onerror = (error) => {
reject(error);
// Terminate the worker and revoke the URL in case of error
worker.terminate();
URL.revokeObjectURL(workerUrl);
};
// Post the data to the worker to start the computation
worker.postMessage({ g, N, difficulty });
});
}

View File

@ -0,0 +1,19 @@
import { Title } from "@solidjs/meta";
import { HttpStatusCode } from "@solidjs/start";
export default function NotFound() {
return (
<main>
<Title>Not Found</Title>
<HttpStatusCode code={404} />
<h1>Page Not Found</h1>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}

View File

@ -0,0 +1,9 @@
import { Header } from "~/components/shell/Header";
export default function Home() {
return (
<div>
<Header />
</div>
);
}

View File

@ -0,0 +1,210 @@
import { DateTime } from "luxon";
import { useParams } from "@solidjs/router";
import { createResource } from "solid-js";
import { Suspense } from "solid-js";
import { For } from "solid-js";
import { useCachedFetch } from "~/lib/dbCache";
import { dbMain } from "~/drizzle";
import { bilibiliMetadata, videoSnapshot } from "~db/main/schema";
import { desc, eq } from "drizzle-orm";
import { BilibiliMetadataType, VideoSnapshotType } from "~db/outerSchema";
import { Context, useRequestContext } from "~/components/requestContext";
import { Header } from "~/components/shell/Header";
async function getAllSnapshots(aid: number, context: Context) {
"use server";
return useCachedFetch(
async () => {
return dbMain
.select()
.from(videoSnapshot)
.where(eq(videoSnapshot.aid, aid))
.orderBy(desc(videoSnapshot.createdAt));
},
"all-snapshots",
context,
[aid]
);
}
async function getVideoMetadata(avORbv: number | string, context: Context) {
"use server";
if (typeof avORbv === "number") {
return useCachedFetch(
async () => {
return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.aid, avORbv)).limit(1);
},
"bili-metadata",
context,
[avORbv]
);
} else {
return useCachedFetch(
async () => {
return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.bvid, avORbv)).limit(1);
},
"bili-metadata",
context,
[avORbv]
);
}
}
const MetadataRow = ({ title, desc }: { title: string; desc: string | number | undefined | null }) => {
if (!desc) return <></>;
return (
<tr>
<td class="max-w-14 min-w-14 md:max-w-24 md:min-w-24 border dark:border-zinc-500 px-2 md:px-3 py-2 font-semibold">
{title}
</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{desc}</td>
</tr>
);
};
export default function VideoInfoPage() {
const params = useParams();
const { id } = params;
const context = useRequestContext();
const [data] = createResource(async () => {
let videoInfo: BilibiliMetadataType | null = null;
let snapshots: VideoSnapshotType[] = [];
try {
const videoData = await getVideoMetadata(id, context);
if (videoData.length === 0) {
return null;
}
const snapshotsData = await getAllSnapshots(videoData[0].aid, context);
videoInfo = videoData[0];
if (snapshotsData) {
snapshots = snapshotsData;
}
} catch (e) {
console.error(e);
}
if (!videoInfo) {
return null;
}
const title = `${videoInfo.title} - 歌曲信息 - 中 V 档案馆`;
return {
v: videoInfo,
s: snapshots,
t: title
};
});
return (
<>
<Header />
<main class="flex flex-col items-center min-h-screen gap-8 mt-10 md:mt-6 relative z-0 overflow-x-auto pb-8">
<div class="w-full lg:max-w-4xl lg:mx-auto lg:p-6">
<Suspense fallback={<div>loading</div>}>
<title>{data()?.t}</title>
<span>{data()?.t}</span>
<h1 class="text-2xl font-medium ml-2 mb-4">
:{" "}
<a href={`https://www.bilibili.com/video/av${data()?.v.aid}`} class="underline">
av{data()?.v.aid}
</a>
</h1>
<div class="mb-6">
<h2 class="px-2 mb-2 text-xl font-medium"></h2>
<div class="overflow-x-auto max-w-full px-2">
<table class="table-fixed">
<tbody>
<MetadataRow title="ID" desc={data()?.v.id} />
<MetadataRow title="av 号" desc={data()?.v.aid} />
<MetadataRow title="BV 号" desc={data()?.v.bvid} />
<MetadataRow title="标题" desc={data()?.v.title} />
<MetadataRow title="描述" desc={data()?.v.description} />
<MetadataRow title="UID" desc={data()?.v.uid} />
<MetadataRow title="标签" desc={data()?.v.tags} />
<MetadataRow
title="发布时间"
desc={
data()?.v.publishedAt
? DateTime.fromJSDate(
new Date(data()?.v.publishedAt || "")
).toFormat("yyyy-MM-dd HH:mm:ss")
: null
}
/>
<MetadataRow title="时长 (秒)" desc={data()?.v.duration} />
<MetadataRow
title="创建时间"
desc={DateTime.fromJSDate(new Date(data()?.v.createdAt || "")).toFormat(
"yyyy-MM-dd HH:mm:ss"
)}
/>
<MetadataRow title="封面" desc={data()?.v?.coverUrl} />
</tbody>
</table>
</div>
</div>
<div>
<h2 class="px-2 mb-2 text-xl font-medium"></h2>
<div class="overflow-x-auto px-2">
<table class="table-auto w-full">
<thead>
<tr>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
</tr>
</thead>
<tbody>
<For each={data()?.s}>
{(snapshot) => (
<tr>
<td class="border dark:border-zinc-500 px-4 py-2">
{DateTime.fromJSDate(new Date(snapshot.createdAt)).toFormat(
"yyyy-MM-dd HH:mm:ss"
)}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.views}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.coins}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.likes}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.favorites}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.shares}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.danmakus}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.replies}
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</div>
</Suspense>
</div>
</main>
</>
);
}

Some files were not shown because too many files have changed in this diff Show More