Initial commit: restore project after Git corruption
This commit is contained in:
3
server/src/app.ts
Normal file
3
server/src/app.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { App } from '@kevisual/router'
|
||||
|
||||
export const app = new App()
|
||||
23
server/src/cache/index.ts
vendored
Normal file
23
server/src/cache/index.ts
vendored
Normal 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'));
|
||||
52
server/src/db/collections/project.ts
Normal file
52
server/src/db/collections/project.ts
Normal 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
37
server/src/db/init.ts
Normal 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()
|
||||
26
server/src/db/types/collection.ts
Normal file
26
server/src/db/types/collection.ts
Normal 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;
|
||||
}
|
||||
1
server/src/db/types/index.ts
Normal file
1
server/src/db/types/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './collection.ts';
|
||||
19
server/src/index.ts
Normal file
19
server/src/index.ts
Normal 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 }
|
||||
7
server/src/modules/config.ts
Normal file
7
server/src/modules/config.ts
Normal 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
24
server/src/modules/db.ts
Normal 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);
|
||||
50
server/src/modules/run-code/run.ts
Normal file
50
server/src/modules/run-code/run.ts
Normal 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)
|
||||
});
|
||||
}
|
||||
8
server/src/routes/auth.ts
Normal file
8
server/src/routes/auth.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { app } from '../app.ts'
|
||||
|
||||
app.route({
|
||||
path: 'auth',
|
||||
id: 'auth'
|
||||
}).define(async (ctx) => {
|
||||
// Authentication logic here
|
||||
}).addTo(app);
|
||||
24
server/src/routes/call/index.ts
Normal file
24
server/src/routes/call/index.ts
Normal 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)
|
||||
68
server/src/routes/file-code/index.ts
Normal file
68
server/src/routes/file-code/index.ts
Normal 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)
|
||||
5
server/src/routes/index.ts
Normal file
5
server/src/routes/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import './call/index.ts';
|
||||
|
||||
import './file-code/index.ts';
|
||||
|
||||
import './auth.ts'
|
||||
37
server/src/test/check-code.ts
Normal file
37
server/src/test/check-code.ts
Normal 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)
|
||||
5
server/src/test/common.ts
Normal file
5
server/src/test/common.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Query } from '@kevisual/query'
|
||||
|
||||
export const query = new Query({
|
||||
url: 'http://localhost:4005/api/router',
|
||||
})
|
||||
48
server/src/test/test-upload.ts
Normal file
48
server/src/test/test-upload.ts
Normal 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();
|
||||
Reference in New Issue
Block a user