Merge pull request 'dev' (#1) from dev into main
All checks were successful
Deploy to Server / deploy (push) Successful in 5s

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-01-09 02:37:05 +00:00
17 changed files with 822 additions and 265 deletions

View File

@@ -0,0 +1,47 @@
# Gitea Actions 部署配置说明
## 前提条件
- Gitea Runner 已配置为 `self-hosted` 模式,直接在宿主机上运行
- 宿主机上已安装 `bun``pm2`
- Runner 用户有权限访问 `/home/score-backend` 目录
## 工作流说明
- **触发条件**:
- 推送到 `dev``main` 分支时自动触发
- 也可以手动触发workflow_dispatch
- **部署步骤**:
1. 检出代码到 runner 工作目录
2. 使用 rsync 同步文件到 `/home/score-backend`(排除不需要的文件)
3. 在目标目录执行 `bun install` 安装依赖
4. 使用 `pm2 restart media-backend` 重启应用(如果应用不存在则启动)
## 文件排除规则
以下文件/目录不会被同步到服务器:
- `.git` - Git 仓库文件
- `node_modules` - 依赖包(会在服务器上重新安装)
- `.DS_Store` - macOS 系统文件
- `*.db` - 数据库文件
- `bun.lockb` - Bun 锁文件
- `.env` / `.env.local` - 环境变量文件(保留服务器上的配置)
- `.gitea` - Gitea 工作流文件
## PM2 配置
如果 PM2 中还没有 `media-backend` 应用,工作流会自动创建。你也可以手动配置:
```bash
cd /home/score-backend
pm2 start main.ts --name media-backend
pm2 save
pm2 startup # 设置开机自启
```
## 注意事项
- 确保 Runner 用户有写入 `/home/score-backend` 的权限
- 确保环境变量在服务器上已正确配置(不会覆盖 `.env` 文件)
- 数据库文件不会被覆盖,确保数据安全

View File

@@ -0,0 +1,42 @@
name: Deploy to Server
on:
push:
branches:
- dev
- main
workflow_dispatch:
jobs:
deploy:
runs-on: host
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Copy files to deployment directory
run: |
# 创建目标目录(如果不存在)
mkdir -p /home/score-backend
# 使用 rsync 同步文件(排除不需要的文件)
rsync -av \
--exclude='.git' \
--exclude='node_modules' \
--exclude='.DS_Store' \
--exclude='*.db' \
--exclude='bun.lockb' \
--exclude='.env' \
--exclude='.env.local' \
--exclude='.gitea' \
./ /home/score-backend/
- name: Install dependencies
working-directory: /home/score-backend
run: |
bun install
- name: Restart PM2 application
run: |
cd /home/score-backend
pm2 restart media-backend || pm2 start main.ts --name media-backend

4
.gitignore vendored
View File

@@ -1,3 +1,7 @@
.vscode
.DS_Store
**/*.db
node_modules/
bun.lockb
.env
.env.local

137
MIGRATION.md Normal file
View File

@@ -0,0 +1,137 @@
# SQLite 到 PostgreSQL 迁移指南
本文档说明如何将项目从 SQLite 迁移到 PostgreSQL。
## 主要变更
### 1. 依赖更新
- 添加了 `postgres` 库用于 PostgreSQL 连接
- 移除了 `node:sqlite` 依赖
### 2. 数据库连接
数据库连接现在通过环境变量配置,支持以下方式:
**方式一:使用 DATABASE_URL**
```bash
export DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
```
**方式二:使用独立的环境变量**
```bash
export DB_USER=postgres
export DB_PASSWORD=postgres
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=media
```
### 3. SQL 语法变更
#### 占位符
- **SQLite**: 使用 `?` 作为占位符
- **PostgreSQL**: 使用 `$1, $2, $3...` 作为占位符
- **解决方案**: 代码中已创建适配器自动转换
#### 日期时间函数
- **SQLite**: `datetime('now')`
- **PostgreSQL**: `NOW()``CURRENT_TIMESTAMP`
- **已更新**: 所有 `datetime('now')` 已替换为 `NOW()`
#### 获取插入的 ID
- **SQLite**: `result.lastInsertRowid`
- **PostgreSQL**: 使用 `RETURNING id` 子句
- **已更新**: INSERT 语句已添加 `RETURNING id`
### 4. 数据库 Schema
需要创建以下表结构:
#### media 表
```sql
CREATE TABLE media (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
rating INTEGER,
notes TEXT,
platform VARCHAR(100),
date DATE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
#### users 表
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
```
## 迁移步骤
### 1. 安装 PostgreSQL
确保已安装并运行 PostgreSQL 数据库。
### 2. 创建数据库
```bash
createdb media
# 或使用 psql
psql -U postgres
CREATE DATABASE media;
```
### 3. 创建表结构
使用上面提供的 SQL 语句创建表。
### 4. 迁移数据(可选)
如果需要从 SQLite 迁移现有数据,可以使用以下方法:
```bash
# 导出 SQLite 数据
sqlite3 media.db .dump > data.sql
# 手动转换并导入到 PostgreSQL
# 注意:需要修改 SQL 语法以兼容 PostgreSQL
```
### 5. 配置环境变量
设置数据库连接信息(见上面的环境变量配置)。
### 6. 测试连接
运行应用并测试数据库连接是否正常。
## 注意事项
1. **数据类型差异**:
- SQLite 的 `INTEGER` 对应 PostgreSQL 的 `INTEGER``SERIAL`
- SQLite 的 `TEXT` 对应 PostgreSQL 的 `VARCHAR``TEXT`
- SQLite 的 `REAL` 对应 PostgreSQL 的 `REAL``DOUBLE PRECISION`
2. **事务处理**:
- PostgreSQL 支持更完善的事务和并发控制
- 代码中的适配器已处理基本的异步操作
3. **性能优化**:
- PostgreSQL 支持索引优化
- 建议为常用查询字段添加索引:
```sql
CREATE INDEX idx_media_type ON media(type);
CREATE INDEX idx_media_rating ON media(rating);
CREATE INDEX idx_media_date ON media(date);
```
4. **连接池**:
- `postgres` 库默认使用连接池
- 可以通过配置选项调整连接池大小
## 回滚方案
如果需要回滚到 SQLite
1. 恢复 `db/index.ts` 中的 SQLite 连接代码
2. 恢复所有 SQL 查询中的 `datetime('now')`
3. 移除 INSERT 语句中的 `RETURNING id`
4. 更新 `deno.json` 移除 postgres 依赖

View File

@@ -1,3 +1,49 @@
## 安装依赖
```bash
bun install
```
deno task start
## 运行项目
```bash
# 开发模式(带热重载)
bun run dev
# 生产模式
bun run start
```
## 数据库初始化
首次运行前,需要初始化数据库:
```bash
# 方式一:使用初始化脚本(推荐,会自动创建数据库和表)
bun run init-db
# 方式二:手动创建数据库和表
# 1. 创建数据库
createdb media
# 或使用 psql
psql -U postgres -c "CREATE DATABASE media;"
# 2. 创建表结构
psql -U postgres -d media -f db/schema.sql
```
## 环境变量
配置以下环境变量:
- `PORT`: 服务器端口(默认: 8000
- `AUTH_SECRET`: JWT 密钥(默认: it-is-a-secret
- `DATABASE_URL`: PostgreSQL 连接字符串
- 或使用独立变量:
- `DB_USER`: 数据库用户(默认: postgres
- `DB_PASSWORD`: 数据库密码(默认: postgres
- `DB_HOST`: 数据库主机(默认: localhost
- `DB_PORT`: 数据库端口(默认: 5432
- `DB_NAME`: 数据库名称(默认: media
**注意**:应用启动时会自动检查并创建表结构(如果不存在),但不会自动创建数据库。请确保数据库已存在。

29
bun.lock Normal file
View File

@@ -0,0 +1,29 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "my-score-backend",
"dependencies": {
"hono": "^4.7.11",
"postgres": "^3.4.3",
},
"devDependencies": {
"@types/bun": "latest",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
"hono": ["hono@4.11.3", "", {}, "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w=="],
"postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
}
}

View File

@@ -4,13 +4,143 @@
* @LastEditTime: 2025-06-13 14:23:03
* @FilePath: /my-score/honoback/db/index.ts
*/
import { DatabaseSync } from "node:sqlite"
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import postgres from "postgres";
import { readFileSync } from "fs";
import { join, dirname } from "path";
const __dirname = dirname(fileURLToPath(import.meta.url))
// 获取当前文件所在目录
const __dirname =
typeof import.meta.dir !== "undefined"
? import.meta.dir
: dirname(new URL(import.meta.url).pathname);
// 初始化数据库连接
const db = new DatabaseSync(join(__dirname, 'media.db'))
// 从环境变量获取数据库连接信息
const DATABASE_URL =
process.env.DATABASE_URL ||
`postgresql://${process.env.DB_USER || "postgres"}:${
process.env.DB_PASSWORD || "postgres"
}@${process.env.DB_HOST || "localhost"}:${process.env.DB_PORT || "5432"}/${
process.env.DB_NAME || "media"
}`;
export { db }
// 初始化 PostgreSQL 连接
const sql = postgres(DATABASE_URL);
// 自动初始化表结构(如果表不存在)
async function initTables() {
try {
// 检查 media 表是否存在
const tableExists = await sql`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'media'
)
`;
// 如果表不存在,创建表
if (!tableExists[0].exists) {
console.log("Initializing database tables...");
const schemaPath = join(__dirname, "schema.sql");
const schema = readFileSync(schemaPath, "utf-8");
await sql.unsafe(schema);
console.log("Database tables initialized successfully");
}
} catch (error: any) {
// 如果是因为数据库不存在,尝试创建数据库
if (
error.message?.includes("database") &&
error.message?.includes("does not exist")
) {
console.error("Database does not exist. Please create it first:");
console.error(" createdb media");
console.error(" or: psql -U postgres -c 'CREATE DATABASE media;'");
throw error;
}
// 其他错误也抛出
throw error;
}
}
// 在模块加载时初始化表(但不阻塞)
initTables().catch((error) => {
console.error("Failed to initialize tables:", error.message);
// 不抛出错误,让应用继续运行,但会在第一次查询时失败
});
// 将 SQLite 的 ? 占位符转换为 PostgreSQL 的 $1, $2, $3...
function convertQuery(query: string, params: any[]): [string, any[]] {
let pgQuery = query;
let paramIndex = 1;
const pgParams: any[] = [];
// 替换 ? 为 $1, $2, $3...
pgQuery = pgQuery.replace(/\?/g, () => {
if (paramIndex <= params.length) {
pgParams.push(params[paramIndex - 1]);
}
return `$${paramIndex++}`;
});
return [pgQuery, pgParams];
}
// 创建一个兼容的数据库接口,模拟 SQLite 的 prepare 方法
class DatabaseAdapter {
private sql: ReturnType<typeof postgres>;
constructor(sql: ReturnType<typeof postgres>) {
this.sql = sql;
}
prepare(query: string) {
return {
// all() 方法用于查询多条记录
all: async (...params: any[]) => {
const [pgQuery, pgParams] = convertQuery(query, params);
const result = await this.sql.unsafe(pgQuery, pgParams);
return result;
},
// get() 方法用于查询单条记录
get: async (...params: any[]) => {
const [pgQuery, pgParams] = convertQuery(query, params);
const result = await this.sql.unsafe(pgQuery, pgParams);
return result[0] || null;
},
// run() 方法用于执行 INSERT/UPDATE/DELETE
run: async (...params: any[]) => {
const [pgQuery, pgParams] = convertQuery(query, params);
const result = await this.sql.unsafe(pgQuery, pgParams);
// 如果是 INSERT 语句,返回类似 SQLite 的 lastInsertRowid
if (query.trim().toUpperCase().startsWith("INSERT")) {
// PostgreSQL 使用 RETURNING 子句获取插入的 ID
if (query.includes("RETURNING")) {
return {
lastInsertRowid: result[0]?.id || null,
changes: result.length,
};
} else {
// 如果没有 RETURNING需要查询最后插入的 ID
// 注意:这需要表有 id 列且是 SERIAL 类型
const lastIdResult = await this.sql.unsafe(
"SELECT lastval() as id"
);
return {
lastInsertRowid: lastIdResult[0]?.id || null,
changes: result.length || 1,
};
}
}
return {
changes: result.length || 0,
};
},
};
}
}
const db = new DatabaseAdapter(sql);
export { db, sql };

83
db/init.ts Normal file
View File

@@ -0,0 +1,83 @@
/*
* 数据库初始化脚本
* 自动创建数据库和表结构
*/
import postgres from "postgres";
import { readFileSync } from "fs";
import { join, dirname } from "path";
// 获取当前文件所在目录
const __dirname =
typeof import.meta.dir !== "undefined"
? import.meta.dir
: dirname(new URL(import.meta.url).pathname);
// 从环境变量获取数据库连接信息(不包含数据库名)
const getBaseConnection = () => {
const user = process.env.DB_USER || "postgres";
const password = process.env.DB_PASSWORD || "postgres";
const host = process.env.DB_HOST || "localhost";
const port = process.env.DB_PORT || "5432";
return `postgresql://${user}:${password}@${host}:${port}`;
};
// 初始化数据库
export async function initDatabase() {
const dbName = process.env.DB_NAME || "media";
const baseUrl = getBaseConnection();
try {
// 连接到 PostgreSQL 服务器(使用默认的 postgres 数据库)
const adminSql = postgres(`${baseUrl}/postgres`);
// 检查数据库是否存在
const dbExists = await adminSql`
SELECT 1 FROM pg_database WHERE datname = ${dbName}
`;
// 如果数据库不存在,创建它
if (dbExists.length === 0) {
console.log(`Creating database: ${dbName}`);
await adminSql.unsafe(`CREATE DATABASE ${dbName}`);
console.log(`Database ${dbName} created successfully`);
} else {
console.log(`Database ${dbName} already exists`);
}
await adminSql.end();
// 连接到新创建的数据库
const sql = postgres(`${baseUrl}/${dbName}`);
// 读取并执行 schema.sql
const schemaPath = join(__dirname, "schema.sql");
const schema = readFileSync(schemaPath, "utf-8");
// 执行 schema 中的 SQL 语句
console.log("Creating tables...");
await sql.unsafe(schema);
console.log("Tables created successfully");
await sql.end();
console.log("Database initialization completed");
} catch (error: any) {
console.error("Database initialization failed:", error.message);
throw error;
}
}
// 如果直接运行此文件,执行初始化
if (
import.meta.main ||
(typeof Bun !== "undefined" && Bun.main === import.meta.path)
) {
initDatabase()
.then(() => {
console.log("Initialization complete");
process.exit(0);
})
.catch((error) => {
console.error("Initialization failed:", error);
process.exit(1);
});
}

29
db/schema.sql Normal file
View File

@@ -0,0 +1,29 @@
-- PostgreSQL 数据库 Schema
-- 用于创建 media 和 users 表
-- 创建 media 表
CREATE TABLE IF NOT EXISTS media (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
rating INTEGER,
notes TEXT,
platform VARCHAR(100),
date DATE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 users 表
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- 创建索引以优化查询性能
CREATE INDEX IF NOT EXISTS idx_media_type ON media(type);
CREATE INDEX IF NOT EXISTS idx_media_rating ON media(rating);
CREATE INDEX IF NOT EXISTS idx_media_date ON media(date);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);

View File

@@ -1,12 +0,0 @@
{
"imports": {
"hono": "jsr:@hono/hono@^4.7.11"
},
"tasks": {
"start": "deno run --allow-net --allow-read --allow-write --allow-env --watch main.ts"
},
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "hono/jsx"
}
}

121
deno.lock generated
View File

@@ -1,121 +0,0 @@
{
"version": "5",
"specifiers": {
"jsr:@hono/hono@^4.7.11": "4.7.11",
"npm:@types/node@*": "22.15.15"
},
"jsr": {
"@hono/hono@4.7.11": {
"integrity": "52b9395e4f2448fbaf82a6bd7925d15e59f4055037e74d6c2f657f3618b11193"
}
},
"npm": {
"@types/node@22.15.15": {
"integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
"dependencies": [
"undici-types"
]
},
"undici-types@6.21.0": {
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
}
},
"redirects": {
"https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts"
},
"remote": {
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
"https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
"https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2",
"https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c",
"https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c",
"https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
"https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b",
"https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf",
"https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d",
"https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
"https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3",
"https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607",
"https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a",
"https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883",
"https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0",
"https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
"https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e",
"https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643",
"https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36",
"https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c",
"https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441",
"https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac",
"https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069",
"https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972",
"https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7",
"https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141",
"https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a",
"https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0",
"https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd",
"https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352",
"https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f",
"https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a",
"https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
"https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0",
"https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
"https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1",
"https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00",
"https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2",
"https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1",
"https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40",
"https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f",
"https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede",
"https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
"https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63",
"https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
"https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
"https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91",
"https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
"https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a",
"https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c",
"https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf",
"https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf",
"https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0",
"https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add",
"https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d",
"https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b",
"https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40",
"https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
"https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660",
"https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
"https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5",
"https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9",
"https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef",
"https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6",
"https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01",
"https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8",
"https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a",
"https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
"https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
"https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
"https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
"https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780",
"https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
"https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707",
"https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
"https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
"https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e",
"https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c",
"https://deno.land/x/sqlite@v3.8/build/sqlite.js": "72f63689fffcb9bb5ae10b1e8f7db09ea845cdf713e0e3a9693d8416a28f92a6",
"https://deno.land/x/sqlite@v3.8/build/vfs.js": "08533cc78fb29b9d9bd62f6bb93e5ef333407013fed185776808f11223ba0e70",
"https://deno.land/x/sqlite@v3.8/mod.ts": "e09fc79d8065fe222578114b109b1fd60077bff1bb75448532077f784f4d6a83",
"https://deno.land/x/sqlite@v3.8/src/constants.ts": "90f3be047ec0a89bcb5d6fc30db121685fc82cb00b1c476124ff47a4b0472aa9",
"https://deno.land/x/sqlite@v3.8/src/db.ts": "7d3251021756fa80f382c3952217c7446c5c8c1642b63511da0938fe33562663",
"https://deno.land/x/sqlite@v3.8/src/error.ts": "f7a15cb00d7c3797da1aefee3cf86d23e0ae92e73f0ba3165496c3816ab9503a",
"https://deno.land/x/sqlite@v3.8/src/function.ts": "e4c83b8ec64bf88bafad2407376b0c6a3b54e777593c70336fb40d43a79865f2",
"https://deno.land/x/sqlite@v3.8/src/query.ts": "d58abda928f6582d77bad685ecf551b1be8a15e8e38403e293ec38522e030cad",
"https://deno.land/x/sqlite@v3.8/src/wasm.ts": "e79d0baa6e42423257fb3c7cc98091c54399254867e0f34a09b5bdef37bd9487"
},
"workspace": {
"dependencies": [
"jsr:@hono/hono@^4.7.11"
]
}
}

View File

@@ -2,7 +2,7 @@
###
# @Date: 2025-06-13 16:11:38
# @LastEditors: 陈子健
# @LastEditTime: 2025-06-23 16:56:58
# @LastEditTime: 2025-06-23 17:24:22
# @FilePath: /my-score/honoback/deploy.sh
###
@@ -13,18 +13,23 @@ REMOTE_DIR="/home/media-backend"
# 本地构建
echo "Building project..."
deno cache main.ts
bun install
# 创建远程目录
echo "Creating remote directory..."
ssh $USER@$SERVER "mkdir -p $REMOTE_DIR"
# 同步文件到服务器
echo "Syncing files to server..."
rsync -avz --exclude 'db/media.db' \
--exclude '.git' \
./ $USER@$SERVER:$REMOTE_DIR/
# 1. 远程备份数据库
ssh $USER@$SERVER "cp $REMOTE_DIR/db/media.db $REMOTE_DIR/db/media.db.bak 2>/dev/null || true"
# 2. 同步代码到服务器(只同步白名单内容,且排除 media.db
rsync -avz --exclude='.git/' --exclude='node_modules/' --exclude='db/media.db' \
--include='*.ts' --include='*.json' --include='*.sh' --include='*.service' \
--include='package.json' --include='package-lock.json' --include='bun.lockb' \
--include='db/' --include='db/*.ts' --include='routes/' --include='routes/*.ts' \
--exclude='*' ./ $USER@$SERVER:$REMOTE_DIR
# 在服务器上安装依赖并重启服务
echo "Installing and starting systemd service..."
ssh $USER@$SERVER "systemctl restart my-score"
echo "Installing dependencies and restarting service..."
ssh $USER@$SERVER "cd $REMOTE_DIR && bun install && systemctl restart my-score"

14
main.ts
View File

@@ -13,7 +13,7 @@ import auth from './routes/auth.ts'
const app = new Hono<{ Variables: JwtVariables }>()
const AUTH_SECRET = Deno.env.get('AUTH_SECRET') || 'it-is-a-secret'
const AUTH_SECRET = process.env.AUTH_SECRET || 'it-is-a-secret'
// 添加请求日志中间件
app.use('*', async (c, next) => {
@@ -41,10 +41,12 @@ app.notFound((c) => {
})
// 获取端口配置
const port = parseInt(Deno.env.get('PORT') || '8000')
const port = parseInt(process.env.PORT || '8000')
// 启动服务器
if (import.meta.main) {
Deno.serve({ port }, app.fetch)
console.log(`Server running on port ${port}`)
}
Bun.serve({
port,
fetch: app.fetch,
})
console.log(`Server running on port ${port}`)

View File

@@ -1,5 +1,5 @@
[Unit]
Description=My Score Deno Service
Description=My Score Bun Service
After=network.target
[Service]
@@ -9,7 +9,7 @@ WorkingDirectory=/home/media-backend
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=PORT=8000
Environment=AUTH_SECRET=it-is-a-secret
ExecStart=/usr/local/bin/deno run --allow-net --allow-read --allow-write --allow-env main.ts
ExecStart=/usr/local/bin/bun run main.ts
Restart=always
RestartSec=10
StandardOutput=journal

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "my-score-backend",
"version": "1.0.0",
"description": "My Score Backend API",
"type": "module",
"main": "main.ts",
"scripts": {
"start": "bun run main.ts",
"dev": "bun --watch main.ts",
"init-db": "bun run db/init.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"hono": "^4.7.11",
"postgres": "^3.4.3"
},
"devDependencies": {
"@types/bun": "latest"
}
}

View File

@@ -1,157 +1,267 @@
import { Hono } from 'hono'
import { db } from '../db/index.ts'
import type { JwtVariables } from 'hono/jwt'
import { authMiddleware } from '../auth.ts'
import { Hono } from "hono";
import { db } from "../db/index.ts";
import type { JwtVariables } from "hono/jwt";
import { authMiddleware } from "../auth.ts";
const media = new Hono<{ Variables: JwtVariables }>()
const media = new Hono<{ Variables: JwtVariables }>();
media.use('/*', authMiddleware)
// 查询满分10分作品不需要JWT验证
media.get("/perfect", async (c) => {
try {
const perfectList = await db
.prepare("SELECT * FROM media WHERE rating = 10")
.all();
return c.json({
code: 0,
data: perfectList,
message: "Success",
});
} catch (error: any) {
return c.json({ code: 1, data: {}, message: error.message }, 500);
}
});
media.use("/*", authMiddleware);
// 获取所有媒体记录
media.get('/list', (c) => {
media.get("/list", async (c) => {
try {
const mediaList = db.prepare('SELECT * FROM media').all()
const mediaList = await db.prepare("SELECT * FROM media").all();
return c.json({
code: 0,
data: mediaList,
message: 'Success'
})
message: "Success",
});
} catch (error: any) {
return c.json({ code: 1, data: {}, message: error.message }, 500)
return c.json({ code: 1, data: {}, message: error.message }, 500);
}
})
});
// 创建新的媒体记录
media.post('/create', async (c) => {
media.post("/create", async (c) => {
try {
const data = await c.req.json()
const { title, type, rating, notes, platform, date } = data
const data = await c.req.json();
const { title, type, rating, notes, platform, date } = data;
const result = db.prepare(`
const result = await db
.prepare(
`
INSERT INTO media (title, type, rating, notes, platform, date, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
`).run(title, type, rating, notes, platform, date)
VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
RETURNING id
`
)
.run(title, type, rating, notes, platform, date);
const newMedia = db.prepare('SELECT * FROM media WHERE id = ?').get(result.lastInsertRowid)
const newMedia = await db
.prepare("SELECT * FROM media WHERE id = ?")
.get(result.lastInsertRowid);
return c.json({
code: 0,
data: newMedia,
message: 'Created successfully'
}, 201)
return c.json(
{
code: 0,
data: newMedia,
message: "Created successfully",
},
201
);
} catch (error: any) {
return c.json({ code: 2, data: {}, message: error.message }, 500)
return c.json({ code: 2, data: {}, message: error.message }, 500);
}
})
});
// 更新媒体记录
media.put('/updateById/:id', async (c) => {
media.put("/updateById/:id", async (c) => {
try {
const id = c.req.param('id')
const data = await c.req.json()
const { title, type, rating, notes, platform, date } = data
const id = c.req.param("id");
const data = await c.req.json();
const { title, type, rating, notes, platform, date } = data;
db.prepare(`
await db
.prepare(
`
UPDATE media
SET title = ?, type = ?, rating = ?, notes = ?, platform = ?, date = ?, updated_at = datetime('now')
SET title = ?, type = ?, rating = ?, notes = ?, platform = ?, date = ?, updated_at = NOW()
WHERE id = ?
`).run(title, type, rating, notes, platform, date, id)
`
)
.run(title, type, rating, notes, platform, date, id);
const updatedMedia = db.prepare('SELECT * FROM media WHERE id = ?').get(id)
const updatedMedia = await db
.prepare("SELECT * FROM media WHERE id = ?")
.get(id);
if (!updatedMedia) {
return c.json({ code: 1, data: {}, message: 'Media not found' }, 404)
return c.json({ code: 1, data: {}, message: "Media not found" }, 404);
}
return c.json({
code: 0,
data: updatedMedia,
message: 'Updated successfully'
})
message: "Updated successfully",
});
} catch (error: any) {
return c.json({ code: 2, data: {}, message: error.message }, 500)
return c.json({ code: 2, data: {}, message: error.message }, 500);
}
})
});
// 删除媒体记录
media.delete('/deleteById/:id', async (c) => {
media.delete("/deleteById/:id", async (c) => {
try {
const id = c.req.param('id')
db.prepare('DELETE FROM media WHERE id = ?').run(id)
return c.json({ code: 0, data: {}, message: 'Deleted successfully' })
const id = c.req.param("id");
await db.prepare("DELETE FROM media WHERE id = ?").run(id);
return c.json({ code: 0, data: {}, message: "Deleted successfully" });
} catch (error: any) {
return c.json({ code: 2, data: {}, message: error.message }, 500)
return c.json({ code: 2, data: {}, message: error.message }, 500);
}
})
});
// 分页查询媒体记录
media.get('/page', async (c) => {
media.get("/page", async (c) => {
try {
const type = c.req.query('type')
const currentPage = parseInt(c.req.query('currentPage') || '1')
const pageSize = parseInt(c.req.query('pageSize') || '10')
const title = c.req.query('title') || ''
const startDate = c.req.query('startDate')
const endDate = c.req.query('endDate')
const sortBy = c.req.query('sortBy') || 'date'
const sortType = c.req.query('sortType') || 'desc'
const type = c.req.query("type");
const currentPage = parseInt(c.req.query("currentPage") || "1");
const pageSize = parseInt(c.req.query("pageSize") || "10");
const title = c.req.query("title") || "";
const startDate = c.req.query("startDate");
const endDate = c.req.query("endDate");
const sortBy = c.req.query("sortBy") || "date";
const sortType = c.req.query("sortType") || "desc";
if (!type) {
return c.json({ code: 1, data: {}, message: 'Type is required' }, 400)
return c.json({ code: 1, data: {}, message: "Type is required" }, 400);
}
let query = 'SELECT * FROM media WHERE type = ?'
const params = [type]
let query = "SELECT * FROM media WHERE type = ?";
const params = [type];
if (title) {
query += ' AND title LIKE ?'
params.push(`%${title}%`)
query += " AND title LIKE ?";
params.push(`%${title}%`);
}
if (startDate) {
query += ' AND date >= ?'
params.push(startDate)
query += " AND date >= ?";
params.push(startDate);
}
if (endDate) {
query += ' AND date <= ?'
params.push(endDate)
query += " AND date <= ?";
params.push(endDate);
}
// 添加排序
if (sortBy === 'date') {
query += ` ORDER BY date ${sortType.toUpperCase()}`
} else if (sortBy === 'score') {
query += ` ORDER BY rating ${sortType.toUpperCase()}`
if (sortBy === "date") {
query += ` ORDER BY date ${sortType.toUpperCase()}`;
} else if (sortBy === "score") {
query += ` ORDER BY rating ${sortType.toUpperCase()}`;
}
// 添加分页
const offset = (currentPage - 1) * pageSize
query += ' LIMIT ? OFFSET ?'
params.push(pageSize.toString(), offset.toString())
const offset = (currentPage - 1) * pageSize;
query += " LIMIT ? OFFSET ?";
params.push(pageSize.toString(), offset.toString());
// 获取总数
const countQuery = query
.replace('SELECT *', 'SELECT COUNT(*) as total')
.replace(/ORDER BY.*$/, '') // 移除 ORDER BY 子句
.replace(/LIMIT.*$/, '') // 移除 LIMIT 和 OFFSET 子句
const totalResult = db.prepare(countQuery).get(...params.slice(0, -2))
const total = totalResult?.total || 0
.replace("SELECT *", "SELECT COUNT(*) as total")
.replace(/ORDER BY.*$/, "") // 移除 ORDER BY 子句
.replace(/LIMIT.*$/, ""); // 移除 LIMIT 和 OFFSET 子句
const totalResult = await db
.prepare(countQuery)
.get(...params.slice(0, -2));
const total = totalResult?.total || 0;
// 获取分页数据
const mediaList = db.prepare(query).all(...params)
const mediaList = await db.prepare(query).all(...params);
// 格式化 date 字段只返回日期部分YYYY-MM-DD
const formattedList = mediaList.map((item: any) => {
if (item.date) {
// 如果是 Date 对象或字符串,格式化为 YYYY-MM-DD
const date = new Date(item.date);
if (!isNaN(date.getTime())) {
item.date = date.toISOString().split("T")[0];
}
}
return item;
});
return c.json({
code: 0,
data: {
list: mediaList,
list: formattedList,
total,
currentPage,
pageSize
pageSize,
},
message: 'Success'
})
message: "Success",
});
} catch (error: any) {
return c.json({ code: 1, data: {}, message: error.message }, 500)
return c.json({ code: 1, data: {}, message: error.message }, 500);
}
})
});
export default media
// 获取媒体统计信息
media.get("/stats", async (c) => {
try {
// 获取总数
const totalResult = await db
.prepare("SELECT COUNT(*) as total FROM media")
.get();
const total = totalResult?.total || 0;
// 获取满分作品数
const perfectResult = await db
.prepare("SELECT COUNT(*) as count FROM media WHERE rating = 10")
.get();
const perfectCount = perfectResult?.count || 0;
// 按类型统计
const typeStats = await db
.prepare(
"SELECT type, COUNT(*) as count FROM media GROUP BY type ORDER BY count DESC"
)
.all();
// 获取平均评分
const avgRatingResult = await db
.prepare("SELECT AVG(rating) as avg FROM media WHERE rating IS NOT NULL")
.get();
const avgRating = avgRatingResult?.avg
? parseFloat(avgRatingResult.avg).toFixed(2)
: "0.00";
// 获取最新添加的作品
const latestResult = await db
.prepare(
"SELECT title, type, rating, date FROM media ORDER BY created_at DESC LIMIT 5"
)
.all();
// 格式化最新作品的日期
const latest = latestResult.map((item: any) => {
if (item.date) {
const date = new Date(item.date);
if (!isNaN(date.getTime())) {
item.date = date.toISOString().split("T")[0];
}
}
return item;
});
return c.json({
code: 0,
data: {
total,
perfectCount,
avgRating,
typeStats,
latest,
},
message: "Success",
});
} catch (error: any) {
return c.json({ code: 1, data: {}, message: error.message }, 500);
}
});
export default media;

View File

@@ -4,36 +4,40 @@
* @LastEditTime: 2025-06-16 11:17:09
* @FilePath: /my-score/honoback/routes/user.ts
*/
import { Hono } from 'hono'
import { sign } from 'hono/jwt'
import type { JwtVariables } from 'hono/jwt'
import { db } from '../db/index.ts'
import { Hono } from "hono";
import { sign } from "hono/jwt";
import type { JwtVariables } from "hono/jwt";
import { db } from "../db/index.ts";
const user = new Hono<{ Variables: JwtVariables }>()
const user = new Hono<{ Variables: JwtVariables }>();
const AUTH_SECRET = 'it-is-a-secret'
const AUTH_SECRET = "it-is-a-secret";
// 登录路由
user.post('/login', async (c) => {
const { username, password } = await c.req.json()
user.post("/login", async (c) => {
const { username, password } = await c.req.json();
// 从数据库验证用户
const user = db.prepare('SELECT * FROM users WHERE username = ? AND password = ?')
.get(username, password)
if (user) {
const token = await sign({ username: user.username }, AUTH_SECRET)
return c.json({
const userRecord = await db
.prepare("SELECT * FROM users WHERE username = ? AND password = ?")
.get(username, password);
if (userRecord) {
const token = await sign({ username: userRecord.username }, AUTH_SECRET);
return c.json({
code: 200,
data: { token },
message: '登录成功'
})
message: "登录成功",
});
}
return c.json({
code: 401,
message: '用户名或密码错误'
}, 401)
})
export default user
return c.json(
{
code: 401,
message: "用户名或密码错误",
},
401
);
});
export default user;