Refactor database interactions and update footer text

- Removed community sharing and self-hosted features from the features list in page.tsx.
- Updated footer text in layout.tsx to remove the MIT License mention.
- Added @libsql/client dependency to package.json and bun.lock for database management.
- Implemented a new db.ts file to handle SQLite database connections and migrations.
- Refactored user and file management functions in users.ts and store.ts to utilize the new database client.
- Ensured legacy JSON data is imported into the new SQLite database structure on first run.
This commit is contained in:
Anders Böttcher
2026-05-12 16:35:10 +02:00
parent 6e2951734e
commit 33e339bde6
8 changed files with 307 additions and 95 deletions
+53 -26
View File
@@ -1,5 +1,6 @@
import path from 'path'
import fs from 'fs/promises'
import { getDb, ensureReady } from './db'
export type FileType = 'backerup' | 'config'
@@ -17,51 +18,77 @@ export interface FileRecord {
}
const DATA_DIR = path.join(process.cwd(), 'data')
const META_FILE = path.join(DATA_DIR, 'meta.json')
const UPLOADS_DIR = path.join(DATA_DIR, 'uploads')
async function ensureDirs() {
await fs.mkdir(UPLOADS_DIR, { recursive: true })
type DbRow = Record<string, unknown>
function rowToRecord(row: DbRow): FileRecord {
return {
token: row.token as string,
originalName: row.original_name as string,
displayName: (row.display_name as string | null) ?? undefined,
size: row.size as number,
type: row.type as FileType,
uploadedAt: row.uploaded_at as string,
description: (row.description as string | null) ?? undefined,
group: (row.group_name as string | null) ?? undefined,
containerName: (row.container_name as string | null) ?? undefined,
image: (row.image as string | null) ?? undefined,
}
}
export async function readMeta(): Promise<FileRecord[]> {
try {
const text = await fs.readFile(META_FILE, 'utf-8')
return JSON.parse(text) as FileRecord[]
} catch {
return []
}
await ensureReady()
const { rows } = await getDb().execute(
'SELECT * FROM files ORDER BY uploaded_at DESC',
)
return rows.map(rowToRecord)
}
export async function listGroups(): Promise<string[]> {
const records = await readMeta()
const groups = new Set<string>()
for (const r of records) {
if (r.group) groups.add(r.group)
}
return Array.from(groups).sort()
}
async function writeMeta(records: FileRecord[]): Promise<void> {
await ensureDirs()
await fs.writeFile(META_FILE, JSON.stringify(records, null, 2), 'utf-8')
await ensureReady()
const { rows } = await getDb().execute(
'SELECT DISTINCT group_name FROM files WHERE group_name IS NOT NULL ORDER BY group_name',
)
return rows.map((r) => r.group_name as string)
}
export async function addFile(record: FileRecord, buffer: Buffer): Promise<void> {
await ensureDirs()
await ensureReady()
await fs.mkdir(UPLOADS_DIR, { recursive: true })
const dir = path.join(UPLOADS_DIR, record.token)
await fs.mkdir(dir, { recursive: true })
await fs.writeFile(path.join(dir, record.originalName), buffer)
const records = await readMeta()
records.unshift(record)
await writeMeta(records)
await getDb().execute({
sql: `INSERT INTO files
(token,original_name,display_name,size,type,uploaded_at,description,group_name,container_name,image)
VALUES (?,?,?,?,?,?,?,?,?,?)`,
args: [
record.token,
record.originalName,
record.displayName ?? null,
record.size,
record.type,
record.uploadedAt,
record.description ?? null,
record.group ?? null,
record.containerName ?? null,
record.image ?? null,
],
})
}
export async function getFile(token: string): Promise<FileRecord | undefined> {
const records = await readMeta()
return records.find((r) => r.token === token)
await ensureReady()
const { rows } = await getDb().execute({
sql: 'SELECT * FROM files WHERE token = ?',
args: [token],
})
return rows[0] ? rowToRecord(rows[0]) : undefined
}
export function getUploadPath(token: string, originalName: string): string {
return path.join(UPLOADS_DIR, token, originalName)
}