Initial commit: restore project after Git corruption

This commit is contained in:
2025-10-21 18:29:15 +08:00
commit 0bb423fcca
112 changed files with 19665 additions and 0 deletions

3
server/src/app.ts Normal file
View File

@@ -0,0 +1,3 @@
import { App } from '@kevisual/router'
export const app = new App()

23
server/src/cache/index.ts vendored Normal file
View File

@@ -0,0 +1,23 @@
import { createStorage } from 'unstorage'
import fsLiteDriver from "unstorage/drivers/fs-lite";
import { codeRoot } from '@/modules/config.ts';
import memoryDriver from "unstorage/drivers/memory";
export const storage = createStorage({
// @ts-ignore
driver: memoryDriver(),
});
export const codeStorage = createStorage({
// @ts-ignore
driver: fsLiteDriver({
base: codeRoot
})
})
// storage.setItem('test-ke/test-key.json', 'test-value');
// console.log('Cache test-key:', await storage.getItem('test-key'));
// codeStorage.setItem('root/light-code-demo/main.ts', 'test-value2');
console.log('Cache test-key:', await codeStorage.getItem('root/light-code-demo/main.ts'));
console.log('has', await codeStorage.hasItem('root/light-code-demo/main.ts'));

View File

@@ -0,0 +1,52 @@
import { Field } from '../types/index.ts';
export const projectStatus = ['提交', '审核中', '审核通过']; // 提交,审核中,审核通过
export type projectStatus = typeof projectStatus[number];
export const projectFields: Field[] = [
{
name: 'title',
type: 'text',
},
{
name: 'description',
type: 'text',
},
{
name: 'status',
type: 'text'
},
{
name: 'key',
type: 'text',
},
{
name: 'owner',
type: 'text',
},
{
name: 'data',
type: 'json'
},
{
name: 'files',
type: 'json'
},
{
name: "createdAt",
onCreate: true,
onUpdate: false,
type: "autodate"
},
{
name: "updatedAt",
onCreate: true,
onUpdate: true,
type: "autodate"
},
];
export const name = 'xx_projects';
export const type = 'base';

37
server/src/db/init.ts Normal file
View File

@@ -0,0 +1,37 @@
import { pb, db } from '../modules/db.ts'
import * as projects from './collections/project.ts'
// 要求
// 1. collection只做新增不做修改
// 2. collection不存在就创建
// 3. 每一个collection的定义在文档中需要有
export const main = async () => {
try {
await db.ensureLogin().catch(() => { throw new Error('Login failed'); });
// 程序第一次运行的时候执行,如果已经初始化过则跳过
const collections = await db.pb.collections.getFullList({
filter: 'name ~ "xx_%"',
})
console.log('Existing collections:', collections.map(c => c.name));
const dbs = [projects]
for (const coll of dbs) {
const exists = collections.find(c => c.name === coll.name)
if (exists) {
console.log(`Collection ${coll.name} already exists, skipping creation.`);
continue;
}
// 第一步,获取那个叉叉开头的 Collection。第二步获取它的版本。
const createdCollection = await db.pb.collections.create({
name: coll.name,
type: coll?.type || 'base',
fields: coll?.projectFields,
})
console.log('Created collection:', createdCollection);
}
} catch (error) {
console.error('Error during DB initialization:', error);
}
}
main()

View File

@@ -0,0 +1,26 @@
export type Field = {
/**
* The unique identifier for the field
*/
id?: string;
/**
* The name of the field
*/
name: string;
type?: 'text' | 'json' | 'autodate' | 'boolean' | 'number' | 'email' | 'url' | 'file' | 'relation';
/**
* Indicates whether the field is required
*/
required?: boolean;
/**
* Only for 'autodate' type
* Indicates whether to set the date on record creation or update
*/
onCreate?: boolean;
/** Only for 'autodate' type
* Indicates whether to set the date on record update
*/
onUpdate?: boolean;
options?: Record<string, any>;
[key: string]: any;
}

View File

@@ -0,0 +1 @@
export * from './collection.ts';

19
server/src/index.ts Normal file
View File

@@ -0,0 +1,19 @@
import { proxyRoute, initProxy } from '@kevisual/local-proxy/proxy.ts';
// http://localhost:4005/test/a/index.html
initProxy({
pagesDir: './demo',
watch: true,
home: '/root/light-code-center',
});
import { app } from './app.ts'
import './routes/index.ts'
app.listen(4005, () => {
console.log('Server is running on http://localhost:4005')
})
app.onServerRequest(proxyRoute);
export { app }

View File

@@ -0,0 +1,7 @@
import { useConfig } from '@kevisual/use-config'
import path from 'path';
export const config = useConfig()
export const codeRoot = path.join(process.cwd(), 'code');

24
server/src/modules/db.ts Normal file
View File

@@ -0,0 +1,24 @@
import PocketBase from 'pocketbase';
const POCKETBASE_URL = 'https://pocketbase.pro.xiongxiao.me/';
export const pb = new PocketBase(POCKETBASE_URL);
export class DB {
pb: PocketBase
constructor(pb: PocketBase) {
this.pb = pb
}
async ensureLogin() {
const pb = this.pb;
if (!pb.authStore.isValid) {
await pb.collection("_superusers").authWithPassword('xiongxiao@xiongxiao.me', '123456xx');
}
return pb.authStore.record;
}
async getCollection(name: string) {
await this.ensureLogin();
return this.pb.collection(name);
}
}
export const db = new DB(pb);

View File

@@ -0,0 +1,50 @@
import { fork } from 'child_process'
export type RunCodeParams = {
path?: string;
key?: string;
payload?: string;
[key: string]: any
}
type RunCode = {
// 调用进程的功能
success?: boolean
data?: {
// 调用router的结果
code?: number
data?: any
message?: string
[key: string]: any
};
error?: any
timestamp?: string
[key: string]: any
}
export const runCode = async (tsPath: string, params: RunCodeParams = {}): Promise<RunCode> => {
return new Promise((resolve, reject) => {
// 使用 Bun 的 fork 模式启动子进程
const child = fork(tsPath)
// 监听来自子进程的消息
child.on('message', (msg: RunCode) => {
resolve(msg)
})
// child.on('exit', (code, signal) => {
// console.log('子进程已退出,退出码:', code, '信号:', signal)
// })
// child.on('close', (code, signal) => {
// console.log('子进程已关闭,退出码:', code, '信号:', signal)
// })
child.on('error', (error) => {
resolve({
success: false, error: error?.message
})
})
// 向子进程发送消息
child.send(params)
});
}

View File

@@ -0,0 +1,8 @@
import { app } from '../app.ts'
app.route({
path: 'auth',
id: 'auth'
}).define(async (ctx) => {
// Authentication logic here
}).addTo(app);

View File

@@ -0,0 +1,24 @@
import { app } from '../../app.ts'
import path from 'path'
import { runCode } from '../../modules/run-code/run.ts'
// http://localhost:4005/api/router?path=call
app.route({
path: 'call'
}).define(async (ctx) => {
const filename = ctx.query?.filename || 'root/listen-demo/router.ts'
const data = ctx.query?.data || {}
const pwd = process.cwd()
const testA = path.join(pwd, 'code', filename)
const resulst = await runCode(testA, data)
if (resulst.success) {
const callResult = resulst.data;
if (callResult.code === 200) ctx.body = callResult.data
else {
const callError = `调用程序错误: ${callResult.message}`
ctx.throw(callResult.code, callError)
}
} else {
ctx.body = `执行脚本错误: ${resulst.error}`
}
}).addTo(app)

View File

@@ -0,0 +1,68 @@
import { app } from '@/app.ts';
import path from 'node:path'
import glob from 'fast-glob';
import fs from 'node:fs'
import { codeRoot } from '@/modules/config.ts';
const list = async () => {
const files = await glob('**/*.ts', { cwd: codeRoot });
type FileContent = {
path: string;
content: string;
}
const filesContent: FileContent[] = [];
for (const file of files) {
if (file.startsWith('node_modules') || file.startsWith('dist') || file.startsWith('.git')) continue;
const fullPath = path.join(codeRoot, file);
const content = fs.readFileSync(fullPath, 'utf-8');
if (content) {
filesContent.push({ path: file, content: content });
}
}
return filesContent;
}
app.route({
path: 'file-code'
}).define(async (ctx) => {
const files = await list();
ctx.body = files
}).addTo(app);
type UploadProps = {
user: string;
key: string;
files: {
type: 'file' | 'base64';
filepath: string;
content: string;
}[];
}
app.route({
path: 'file-code',
key: 'upload',
middleware: ['auth']
}).define(async (ctx) => {
const upload = ctx.query?.upload as UploadProps;
if (!upload || !upload.user || !upload.key || !upload.files) {
ctx.throw(400, 'Invalid upload data');
}
const user = upload.user;
const key = upload.key;
for (const file of upload.files) {
if (file.type === 'file') {
const fullPath = path.join(codeRoot, user, key, file.filepath);
const dir = path.dirname(fullPath);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(fullPath, file.content, 'utf-8');
} else if (file.type === 'base64') {
const fullPath = path.join(codeRoot, user, key, file.filepath);
const dir = path.dirname(fullPath);
fs.mkdirSync(dir, { recursive: true });
const buffer = Buffer.from(file.content, 'base64');
fs.writeFileSync(fullPath, buffer);
}
}
ctx.body = { success: true };
}).addTo(app)

View File

@@ -0,0 +1,5 @@
import './call/index.ts';
import './file-code/index.ts';
import './auth.ts'

View File

@@ -0,0 +1,37 @@
import path from 'node:path'
import fs from 'node:fs'
const main = async () => {
const root = path.join(process.cwd(), 'code');
const buckupRoot = path.join(process.cwd(), 'code-backup');
// 如果 code 文件夹不存在或文件夹列表长度等于0则从 code-backup 复制
let shouldCopy = false;
if (!fs.existsSync(root)) {
console.log('code 文件夹不存在');
shouldCopy = true;
} else {
// 检查 code 文件夹下的文件夹列表
const items = await fs.promises.readdir(root, { withFileTypes: true });
const folders = items.filter(item => item.isDirectory());
if (folders.length === 0) {
console.log('code 文件夹存在但为空(无子文件夹)');
shouldCopy = true;
} else {
console.log(`code 文件夹已存在且包含 ${folders.length} 个子文件夹`);
}
}
if (shouldCopy) {
if (fs.existsSync(buckupRoot)) {
console.log('正在从 code-backup 复制...');
await fs.promises.cp(buckupRoot, root, { recursive: true });
console.log('复制完成!');
} else {
console.log('code-backup 文件夹不存在,无法复制');
}
}
}
main().catch(console.error)

View File

@@ -0,0 +1,5 @@
import { Query } from '@kevisual/query'
export const query = new Query({
url: 'http://localhost:4005/api/router',
})

View File

@@ -0,0 +1,48 @@
import { query } from './common.ts'
export const testUpload = async () => {
const res = await query.post({
path: 'file-code',
key: 'upload',
upload: {
user: 'test',
key: 'demo',
files: [
{
type: 'file',
filepath: 'main.ts',
content: `import { QueryRouterServer } from "@kevisual/router";
const app = new QueryRouterServer();
app.route({
path: 'main'
}).define(async (ctx) => {
ctx.body = {
message: 'this is main. filename: test/demo/main.ts',
params: ctx.query
}
}).addTo(app)
app.wait()`
},
]
}
})
console.log('Upload response:', res);
}
// testUpload();
const callTestDemo = async () => {
const res = await query.post({
path: 'call',
filename: 'test/demo/main.ts',
})
console.log('Call response:', res);
}
callTestDemo();