release: v1.4.0 — 标签系统、操作日志扩展与文档截图更新
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
- 每用户标签与彩色打标,图库 OR/AND 筛选与批量打标 - 操作日志记录登录、删除、设置与标签操作 - API 标签/打标接口;README 与文档站截图同步
This commit is contained in:
1 parent
f17d79b236
commit
737235acff
110 files changed
+5881
-778
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
const tempDirs: string[] = []
|
||||
|
||||
afterEach(() => {
|
||||
while (tempDirs.length) {
|
||||
const dir = tempDirs.pop()
|
||||
if (dir) rmSync(dir, { recursive: true, force: true })
|
||||
}
|
||||
process.env.DATA_DIR = undefined
|
||||
})
|
||||
|
||||
function useTempDataDir(): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'pichost-smoke-'))
|
||||
tempDirs.push(dir)
|
||||
process.env.DATA_DIR = dir
|
||||
return dir
|
||||
}
|
||||
|
||||
async function loadDbModule() {
|
||||
const mod = await import('./db')
|
||||
mod.closeDb()
|
||||
return mod
|
||||
}
|
||||
|
||||
describe('activity log smoke', () => {
|
||||
it('migrates legacy activity_logs constraints and accepts new action types', async () => {
|
||||
const dir = useTempDataDir()
|
||||
const dbPath = join(dir, 'pichost.db')
|
||||
const legacy = new DatabaseSync(dbPath)
|
||||
legacy.exec(`
|
||||
CREATE TABLE activity_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
action TEXT NOT NULL CHECK(action IN ('upload', 'delete')),
|
||||
key TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
content_type TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL CHECK(source IN ('web', 'api')),
|
||||
created_at TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
backend_id TEXT,
|
||||
ip_address TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'success'
|
||||
);
|
||||
INSERT INTO activity_logs (action, key, original_name, size, content_type, source, created_at)
|
||||
VALUES ('upload', 'images/a.webp', 'a.webp', 100, 'image/webp', 'web', '2026-01-01T00:00:00.000Z');
|
||||
CREATE TABLE settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
`)
|
||||
legacy.close()
|
||||
|
||||
const { getDb, insertActivityLog, listActivityLogs, closeDb } = await loadDbModule()
|
||||
getDb()
|
||||
const { ensureStorageSchema } = await import('./storage-backends')
|
||||
ensureStorageSchema()
|
||||
|
||||
insertActivityLog({
|
||||
action: 'login',
|
||||
key: 'auth/login',
|
||||
originalName: 'admin',
|
||||
size: 0,
|
||||
contentType: '',
|
||||
source: 'web',
|
||||
userId: 1,
|
||||
ipAddress: '127.0.0.1'
|
||||
})
|
||||
insertActivityLog({
|
||||
action: 'tags',
|
||||
key: 'tags/1',
|
||||
originalName: '生活',
|
||||
size: 0,
|
||||
contentType: '',
|
||||
source: 'admin',
|
||||
userId: 1
|
||||
})
|
||||
|
||||
const result = listActivityLogs({ limit: 10, page: 1 })
|
||||
expect(result.total).toBe(3)
|
||||
expect(result.items.map(item => item.action).sort()).toEqual(['login', 'tags', 'upload'])
|
||||
|
||||
const migrated = getDb().prepare(`
|
||||
SELECT value FROM settings WHERE key = 'activity_logs_schema_v2'
|
||||
`).get() as { value: string } | undefined
|
||||
expect(migrated?.value).toBe('1')
|
||||
|
||||
closeDb()
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
})
|
||||
|
||||
it('opens the project data database without throwing', async () => {
|
||||
const projectData = join(process.cwd(), 'data')
|
||||
process.env.DATA_DIR = projectData
|
||||
|
||||
const { getDb, listActivityLogs, closeDb } = await loadDbModule()
|
||||
getDb()
|
||||
const result = listActivityLogs({ limit: 5, page: 1 })
|
||||
expect(result.page).toBeGreaterThan(0)
|
||||
expect(Array.isArray(result.items)).toBe(true)
|
||||
closeDb()
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user