Initial commit: restore project after Git corruption
This commit is contained in:
		
							
								
								
									
										25
									
								
								server/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								server/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
node_modules
 | 
			
		||||
 | 
			
		||||
dist
 | 
			
		||||
 | 
			
		||||
app.config.json5
 | 
			
		||||
 | 
			
		||||
apps.config.json
 | 
			
		||||
 | 
			
		||||
deploy.tar.gz
 | 
			
		||||
cache-file
 | 
			
		||||
 | 
			
		||||
/apps
 | 
			
		||||
 | 
			
		||||
logs
 | 
			
		||||
 | 
			
		||||
release/*
 | 
			
		||||
!release/.gitkeep
 | 
			
		||||
 | 
			
		||||
.turbo
 | 
			
		||||
 | 
			
		||||
.env*
 | 
			
		||||
!.env.example
 | 
			
		||||
 | 
			
		||||
pack-dist
 | 
			
		||||
app.config.json5.envision
 | 
			
		||||
							
								
								
									
										22
									
								
								server/bun.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								server/bun.config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
// @ts-check
 | 
			
		||||
import { resolvePath } from '@kevisual/use-config';
 | 
			
		||||
import { execSync } from 'node:child_process';
 | 
			
		||||
 | 
			
		||||
const entry = 'src/index.ts';
 | 
			
		||||
const naming = 'app';
 | 
			
		||||
const external = ['sequelize', 'pg', 'ioredis', 'pm2'];
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('bun').BuildConfig}
 | 
			
		||||
 */
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
await Bun.build({
 | 
			
		||||
  target: 'node',
 | 
			
		||||
  format: 'esm',
 | 
			
		||||
  entrypoints: [resolvePath(entry, { meta: import.meta })],
 | 
			
		||||
  outdir: resolvePath('./dist', { meta: import.meta }),
 | 
			
		||||
  naming: {
 | 
			
		||||
    entry: `${naming}.js`,
 | 
			
		||||
  },
 | 
			
		||||
  external,
 | 
			
		||||
  env: 'KEVISUAL_*',
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										24
									
								
								server/code/root/light-code-demo/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								server/code/root/light-code-demo/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import { QueryRouterServer } from "@kevisual/router";
 | 
			
		||||
 | 
			
		||||
const app = new QueryRouterServer();
 | 
			
		||||
 | 
			
		||||
app.route({
 | 
			
		||||
  path: 'main'
 | 
			
		||||
}).define(async (ctx) => {
 | 
			
		||||
  ctx.body = {
 | 
			
		||||
    message: 'this is main. filename: root/light-code-demo/main.ts',
 | 
			
		||||
    params: ctx.query
 | 
			
		||||
  }
 | 
			
		||||
}).addTo(app)
 | 
			
		||||
 | 
			
		||||
app.route({
 | 
			
		||||
  path: 'main2'
 | 
			
		||||
}).define(async (ctx) => {
 | 
			
		||||
  ctx.body = {
 | 
			
		||||
    message: 'this is main2. filename: root/light-code-demo/main.ts',
 | 
			
		||||
    params: ctx.query
 | 
			
		||||
  }
 | 
			
		||||
}).addTo(app)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app.wait()
 | 
			
		||||
							
								
								
									
										46
									
								
								server/code/root/light-code-demo/sign.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								server/code/root/light-code-demo/sign.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import { QueryRouterServer as Mini } from "@kevisual/router";
 | 
			
		||||
import { NocoApi } from "@kevisual/noco";
 | 
			
		||||
const config = {
 | 
			
		||||
  NOCODB_URL: process.env.NOCODB_URL || 'https://nocodb.xiongxiao.me',
 | 
			
		||||
  NOCODB_API_KEY: process.env.NOCODB_API_KEY || 'uca1Zx3p_0pnNUBV6ot9mBP6JCPqQ0X1TF3N3R7s'
 | 
			
		||||
}
 | 
			
		||||
const table = 'mcby44q8zrayvn9'
 | 
			
		||||
const nocoAPi = new NocoApi({
 | 
			
		||||
  baseURL: config.NOCODB_URL,
 | 
			
		||||
  token: config.NOCODB_API_KEY,
 | 
			
		||||
  table,
 | 
			
		||||
});
 | 
			
		||||
console.log('nocoAPi', await nocoAPi.record.list())
 | 
			
		||||
const app = new Mini();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app.route({
 | 
			
		||||
  path: 'sign'
 | 
			
		||||
}).define(async (ctx) => {
 | 
			
		||||
  const { Title, Description } = ctx.query
 | 
			
		||||
  // 这里可以处理签到
 | 
			
		||||
  await nocoAPi.record.create({ Title, Description })
 | 
			
		||||
  const list = await nocoAPi.record.list({ sort: '-CreatedAt' })
 | 
			
		||||
  ctx.body = { message: '签到成功', list }
 | 
			
		||||
}).addTo(app)
 | 
			
		||||
 | 
			
		||||
app.route({
 | 
			
		||||
  path: 'sign',
 | 
			
		||||
  key: 'list'
 | 
			
		||||
}).define(async (ctx) => {
 | 
			
		||||
  // 这里可以处理签到
 | 
			
		||||
  ctx.body = await nocoAPi.record.list()
 | 
			
		||||
}).addTo(app)
 | 
			
		||||
 | 
			
		||||
app.route({
 | 
			
		||||
  path: 'sign',
 | 
			
		||||
  key: 'delete'
 | 
			
		||||
}).define(async (ctx) => {
 | 
			
		||||
  const { id } = ctx.query
 | 
			
		||||
  // 这里可以处理签到
 | 
			
		||||
  await nocoAPi.record.delete({ Id: id })
 | 
			
		||||
  const list = await nocoAPi.record.list({ sort: '-CreatedAt' })
 | 
			
		||||
  ctx.body = { message: '删除成功', list }
 | 
			
		||||
}).addTo(app)
 | 
			
		||||
 | 
			
		||||
app.wait()
 | 
			
		||||
							
								
								
									
										56
									
								
								server/code/root/light-code-demo/weather.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								server/code/root/light-code-demo/weather.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
const weatherHost = 'n65khufe5n.re.qweatherapi.com';
 | 
			
		||||
const token = 'fdad5aeb2ba54949a8a1df2a0f3d1efb'
 | 
			
		||||
const xihu = '101210113'; // 西湖
 | 
			
		||||
export const getWeather = async (location: string = xihu) => {
 | 
			
		||||
  const url = `https://${weatherHost}/v7/weather/3d?location=${location}`;
 | 
			
		||||
  const headers = {
 | 
			
		||||
    'Authorization': `Bearer ${token}`
 | 
			
		||||
  };
 | 
			
		||||
  const res = await fetch(url, { headers });
 | 
			
		||||
  if (!res.ok) {
 | 
			
		||||
    throw new Error(`HTTP error! status: ${res.status}`);
 | 
			
		||||
  }
 | 
			
		||||
  const data = await res.json();
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
// getWeather().then(console.log).catch(console.error);
 | 
			
		||||
 | 
			
		||||
// https://dev.qweather.com/
 | 
			
		||||
class Weather {
 | 
			
		||||
  host: string;
 | 
			
		||||
  token: string;
 | 
			
		||||
  constructor(opts: { host: string; token: string }) {
 | 
			
		||||
    this.host = opts.host;
 | 
			
		||||
    this.token = opts.token;
 | 
			
		||||
    console.log(this.host, this.token);
 | 
			
		||||
  }
 | 
			
		||||
  getWeather(location: string) {
 | 
			
		||||
    return fetch(`https://${this.host}/v7/weather/now?location=${location}`, {
 | 
			
		||||
      headers: {
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
        'X-QW-Api-Key': '<KEY>'.replace('<KEY>', this.token),
 | 
			
		||||
      },
 | 
			
		||||
    }).then((res) => res.json());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
const newWeather = new Weather({
 | 
			
		||||
  host: process.env?.QWEATHER_HOST || weatherHost,
 | 
			
		||||
  token: process.env?.QWEATHER_TOKEN || token,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// newWeather.getWeather(xihu).then(console.log).catch(console.error);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import { QueryRouterServer as Mini } from "@kevisual/router";
 | 
			
		||||
 | 
			
		||||
const app = new Mini();
 | 
			
		||||
 | 
			
		||||
app.route({
 | 
			
		||||
  path: 'main'
 | 
			
		||||
}).define(async (ctx) => {
 | 
			
		||||
  ctx.body = await newWeather.getWeather(xihu);
 | 
			
		||||
}).addTo(app)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app.wait()
 | 
			
		||||
							
								
								
									
										57
									
								
								server/code/root/listen-demo/origin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								server/code/root/listen-demo/origin.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
const main = () => {
 | 
			
		||||
  const random = Math.random().toString(36).slice(-6)
 | 
			
		||||
  return 'hello a ' + random
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fs.writeFileSync('./a.txt', main() + '\n', { flag: 'a' })
 | 
			
		||||
console.log('pwd', process.cwd())
 | 
			
		||||
// const value = fs.readFileSync('./bun.config.ts', 'utf-8')
 | 
			
		||||
// console.log('a.txt 内容:', value)
 | 
			
		||||
const listen = async () => {
 | 
			
		||||
  console.log('子进程启动,等待消息...')
 | 
			
		||||
 | 
			
		||||
  const getParams = async () => {
 | 
			
		||||
    return new Promise((resolve) => {
 | 
			
		||||
      process.on('message', (msg) => {
 | 
			
		||||
        console.log('子进程收到消息:', msg)
 | 
			
		||||
        resolve(msg)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const params = await getParams()
 | 
			
		||||
    console.log('处理参数:', params)
 | 
			
		||||
 | 
			
		||||
    // 执行主要逻辑
 | 
			
		||||
    const result = main()
 | 
			
		||||
 | 
			
		||||
    // 发送结果回主进程
 | 
			
		||||
    const response = {
 | 
			
		||||
      foo: 'bar',
 | 
			
		||||
      params,
 | 
			
		||||
      success: true,
 | 
			
		||||
      data: { code: 200, data: result },
 | 
			
		||||
      timestamp: new Date().toISOString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    process.send?.(response, (error) => {
 | 
			
		||||
      if (error) {
 | 
			
		||||
        console.error('发送消息失败:', error)
 | 
			
		||||
      } else {
 | 
			
		||||
        console.log('成功发送响应:', response)
 | 
			
		||||
      }
 | 
			
		||||
      process.exit(0)
 | 
			
		||||
    })
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('子进程执行出错:', error)
 | 
			
		||||
    process.send?.({
 | 
			
		||||
      success: false,
 | 
			
		||||
      error: error.message
 | 
			
		||||
    })
 | 
			
		||||
    process.exit(1)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 启动监听
 | 
			
		||||
listen().catch(console.error)
 | 
			
		||||
							
								
								
									
										14
									
								
								server/code/root/listen-demo/router.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								server/code/root/listen-demo/router.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import { QueryRouterServer } from "@kevisual/router";
 | 
			
		||||
 | 
			
		||||
const app = new QueryRouterServer();
 | 
			
		||||
 | 
			
		||||
app.route({
 | 
			
		||||
  path: 'main'
 | 
			
		||||
}).define(async (ctx) => {
 | 
			
		||||
  ctx.body = {
 | 
			
		||||
    message: 'this is main. filename: root/listen-demo/router.ts',
 | 
			
		||||
    params: ctx.query
 | 
			
		||||
  }
 | 
			
		||||
}).addTo(app)
 | 
			
		||||
 | 
			
		||||
app.wait()
 | 
			
		||||
							
								
								
									
										14
									
								
								server/code/test/demo/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								server/code/test/demo/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
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()
 | 
			
		||||
							
								
								
									
										1
									
								
								server/demo/test/a/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/demo/test/a/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
测试静态页面
 | 
			
		||||
							
								
								
									
										31
									
								
								server/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								server/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "light-code",
 | 
			
		||||
  "version": "0.0.1",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "bun run --watch --hot src/index.ts",
 | 
			
		||||
    "start": "bun run src/index.ts",
 | 
			
		||||
    "prestart": "bun src/test/check-code.ts",
 | 
			
		||||
    "build": "NODE_ENV=production bun bun.config.ts"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [],
 | 
			
		||||
  "author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "packageManager": "pnpm@10.16.1",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@kevisual/local-proxy": "^0.0.6",
 | 
			
		||||
    "@kevisual/types": "^0.0.10",
 | 
			
		||||
    "@kevisual/use-config": "^1.0.19",
 | 
			
		||||
    "@types/bun": "^1.3.0"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@kevisual/noco": "^0.0.1",
 | 
			
		||||
    "@kevisual/query": "^0.0.29",
 | 
			
		||||
    "@kevisual/router": "^0.0.29",
 | 
			
		||||
    "fast-glob": "^3.3.3",
 | 
			
		||||
    "pocketbase": "^0.26.2",
 | 
			
		||||
    "unstorage": "^1.17.1"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										384
									
								
								server/pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								server/pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,384 @@
 | 
			
		||||
lockfileVersion: '9.0'
 | 
			
		||||
 | 
			
		||||
settings:
 | 
			
		||||
  autoInstallPeers: true
 | 
			
		||||
  excludeLinksFromLockfile: false
 | 
			
		||||
 | 
			
		||||
importers:
 | 
			
		||||
 | 
			
		||||
  .:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@kevisual/router':
 | 
			
		||||
        specifier: ^0.0.28
 | 
			
		||||
        version: 0.0.28
 | 
			
		||||
    devDependencies:
 | 
			
		||||
      '@kevisual/types':
 | 
			
		||||
        specifier: ^0.0.10
 | 
			
		||||
        version: 0.0.10
 | 
			
		||||
      '@kevisual/use-config':
 | 
			
		||||
        specifier: ^1.0.19
 | 
			
		||||
        version: 1.0.19(dotenv@16.6.1)
 | 
			
		||||
      '@types/bun':
 | 
			
		||||
        specifier: ^1.3.0
 | 
			
		||||
        version: 1.3.0(@types/react@19.2.2)
 | 
			
		||||
      bun:
 | 
			
		||||
        specifier: ^1.3.0
 | 
			
		||||
        version: 1.3.0
 | 
			
		||||
 | 
			
		||||
packages:
 | 
			
		||||
 | 
			
		||||
  '@kevisual/load@0.0.6':
 | 
			
		||||
    resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
 | 
			
		||||
 | 
			
		||||
  '@kevisual/router@0.0.28':
 | 
			
		||||
    resolution: {integrity: sha512-MqpnRqBRt2TkM9KyDDaz/AjbBFi8L2y2/MwChu28fK6g0OL5fJ45NQQBGNpNrj2rsUVmpCA2wDr2SqjVxE3CLA==}
 | 
			
		||||
 | 
			
		||||
  '@kevisual/types@0.0.10':
 | 
			
		||||
    resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==}
 | 
			
		||||
 | 
			
		||||
  '@kevisual/use-config@1.0.19':
 | 
			
		||||
    resolution: {integrity: sha512-Q1IH4eMqUe5w6Bq8etoqOSls9FPIy0xwwD3wHf26EsQLZadhccI9qkDuFzP/rFWDa57mwFPEfwbGE5UlqWOCkw==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      dotenv: ^16.4.7
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-darwin-aarch64@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-WeXSaL29ylJEZMYHHW28QZ6rgAbxQ1KuNSZD9gvd3fPlo0s6s2PglvPArjjP07nmvIK9m4OffN0k4M98O7WmAg==}
 | 
			
		||||
    cpu: [arm64]
 | 
			
		||||
    os: [darwin]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-darwin-x64-baseline@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-+FSr/ub5vA/EkD3fMhHJUzYioSf/sXd50OGxNDAntVxcDu4tXL/81Ka3R/gkZmjznpLFIzovU/1Ts+b7dlkrfw==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [darwin]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-darwin-x64@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-CFKjoUWQH0Oz3UHYfKbdKLq0wGryrFsTJEYq839qAwHQSECvVZYAnxVVDYUDa0yQFonhO2qSHY41f6HK+b7xtw==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [darwin]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-aarch64-musl@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-HT5sr7N8NDYbQRjAnT7ISpx64y+ewZZRQozOJb0+KQObKvg4UUNXGm4Pn1xA4/WPMZDDazjO8E2vtOQw1nJlAQ==}
 | 
			
		||||
    cpu: [arm64]
 | 
			
		||||
    os: [linux]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-aarch64@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-WHthS/eLkCNcp9pk4W8aubRl9fIUgt2XhHyLrP0GClB1FVvmodu/zIOtG0NXNpzlzB8+gglOkGo4dPjfVf4Z+g==}
 | 
			
		||||
    cpu: [arm64]
 | 
			
		||||
    os: [linux]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64-baseline@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-OmlEH3nlxQyv7HOvTH21vyNAZGv9DIPnrTznzvKiOQxkOphhCyKvPTlF13ydw4s/i18iwaUrhHy+YG9HSSxa4Q==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [linux]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64-musl-baseline@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-hrr7mDvUjMX1tuJaXz448tMsgKIqGJBY8+rJqztKOw1U5+a/v2w5HuIIW1ce7ut0ZwEn+KIDvAujlPvpH33vpQ==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [linux]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64-musl@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-rtzUEzCynl3Rhgn/iR9DQezSFiZMcAXAbU+xfROqsweMGKwvwIA2ckyyckO08psEP8XcUZTs3LT9CH7PnaMiEA==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [linux]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-sGEWoJQXO4GDr0x4t/yJQ/Bq1yNkOdX9tHbZZ+DBGJt3z3r7jeb4Digv8xQUk6gdTFC9vnGHuin+KW3/yD1Aww==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [linux]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-windows-x64-baseline@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-/jVZ8eYjpYHLDFNoT86cP+AjuWvpkzFY+0R0a1bdeu0sQ6ILuy1FV6hz1hUAP390E09VCo5oP76fnx29giHTtA==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [win32]
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-windows-x64@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-xXwtpZVVP7T+vkxcF/TUVVOGRjEfkByO4mKveKYb4xnHWV4u4NnV0oNmzyMKkvmj10to5j2h0oZxA4ZVVv4gfA==}
 | 
			
		||||
    cpu: [x64]
 | 
			
		||||
    os: [win32]
 | 
			
		||||
 | 
			
		||||
  '@types/bun@1.3.0':
 | 
			
		||||
    resolution: {integrity: sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA==}
 | 
			
		||||
 | 
			
		||||
  '@types/node@24.7.2':
 | 
			
		||||
    resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==}
 | 
			
		||||
 | 
			
		||||
  '@types/react@19.2.2':
 | 
			
		||||
    resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
 | 
			
		||||
 | 
			
		||||
  bun-types@1.3.0:
 | 
			
		||||
    resolution: {integrity: sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      '@types/react': ^19
 | 
			
		||||
 | 
			
		||||
  bun@1.3.0:
 | 
			
		||||
    resolution: {integrity: sha512-YI7mFs7iWc/VsGsh2aw6eAPD2cjzn1j+LKdYVk09x1CrdTWKYIHyd+dG5iQoN9//3hCDoZj8U6vKpZzEf5UARA==}
 | 
			
		||||
    cpu: [arm64, x64]
 | 
			
		||||
    os: [darwin, linux, win32]
 | 
			
		||||
    hasBin: true
 | 
			
		||||
 | 
			
		||||
  csstype@3.1.3:
 | 
			
		||||
    resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 | 
			
		||||
 | 
			
		||||
  debug@4.4.3:
 | 
			
		||||
    resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
 | 
			
		||||
    engines: {node: '>=6.0'}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      supports-color: '*'
 | 
			
		||||
    peerDependenciesMeta:
 | 
			
		||||
      supports-color:
 | 
			
		||||
        optional: true
 | 
			
		||||
 | 
			
		||||
  depd@2.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
 | 
			
		||||
    engines: {node: '>= 0.8'}
 | 
			
		||||
 | 
			
		||||
  dotenv@16.6.1:
 | 
			
		||||
    resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
 | 
			
		||||
    engines: {node: '>=12'}
 | 
			
		||||
 | 
			
		||||
  ee-first@1.1.1:
 | 
			
		||||
    resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
 | 
			
		||||
 | 
			
		||||
  encodeurl@2.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
 | 
			
		||||
    engines: {node: '>= 0.8'}
 | 
			
		||||
 | 
			
		||||
  escape-html@1.0.3:
 | 
			
		||||
    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
 | 
			
		||||
 | 
			
		||||
  etag@1.8.1:
 | 
			
		||||
    resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
 | 
			
		||||
    engines: {node: '>= 0.6'}
 | 
			
		||||
 | 
			
		||||
  eventemitter3@5.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
 | 
			
		||||
 | 
			
		||||
  fresh@2.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
 | 
			
		||||
    engines: {node: '>= 0.8'}
 | 
			
		||||
 | 
			
		||||
  http-errors@2.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
 | 
			
		||||
    engines: {node: '>= 0.8'}
 | 
			
		||||
 | 
			
		||||
  inherits@2.0.4:
 | 
			
		||||
    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
 | 
			
		||||
 | 
			
		||||
  mime-db@1.54.0:
 | 
			
		||||
    resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
 | 
			
		||||
    engines: {node: '>= 0.6'}
 | 
			
		||||
 | 
			
		||||
  mime-types@3.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
 | 
			
		||||
    engines: {node: '>= 0.6'}
 | 
			
		||||
 | 
			
		||||
  ms@2.1.3:
 | 
			
		||||
    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 | 
			
		||||
 | 
			
		||||
  node-forge@1.3.1:
 | 
			
		||||
    resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
 | 
			
		||||
    engines: {node: '>= 6.13.0'}
 | 
			
		||||
 | 
			
		||||
  on-finished@2.4.1:
 | 
			
		||||
    resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
 | 
			
		||||
    engines: {node: '>= 0.8'}
 | 
			
		||||
 | 
			
		||||
  path-to-regexp@8.3.0:
 | 
			
		||||
    resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
 | 
			
		||||
 | 
			
		||||
  range-parser@1.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
 | 
			
		||||
    engines: {node: '>= 0.6'}
 | 
			
		||||
 | 
			
		||||
  selfsigned@3.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-6U6w6kSLrM9Zxo0D7mC7QdGS6ZZytMWBnj/vhF9p+dAHx6CwGezuRcO4VclTbrrI7mg7SD6zNiqXUuBHOVopNQ==}
 | 
			
		||||
    engines: {node: '>=10'}
 | 
			
		||||
 | 
			
		||||
  send@1.2.0:
 | 
			
		||||
    resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
 | 
			
		||||
    engines: {node: '>= 18'}
 | 
			
		||||
 | 
			
		||||
  setprototypeof@1.2.0:
 | 
			
		||||
    resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
 | 
			
		||||
 | 
			
		||||
  statuses@2.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
 | 
			
		||||
    engines: {node: '>= 0.8'}
 | 
			
		||||
 | 
			
		||||
  statuses@2.0.2:
 | 
			
		||||
    resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
 | 
			
		||||
    engines: {node: '>= 0.8'}
 | 
			
		||||
 | 
			
		||||
  toidentifier@1.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
 | 
			
		||||
    engines: {node: '>=0.6'}
 | 
			
		||||
 | 
			
		||||
  undici-types@7.14.0:
 | 
			
		||||
    resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==}
 | 
			
		||||
 | 
			
		||||
snapshots:
 | 
			
		||||
 | 
			
		||||
  '@kevisual/load@0.0.6':
 | 
			
		||||
    dependencies:
 | 
			
		||||
      eventemitter3: 5.0.1
 | 
			
		||||
 | 
			
		||||
  '@kevisual/router@0.0.28':
 | 
			
		||||
    dependencies:
 | 
			
		||||
      path-to-regexp: 8.3.0
 | 
			
		||||
      selfsigned: 3.0.1
 | 
			
		||||
      send: 1.2.0
 | 
			
		||||
    transitivePeerDependencies:
 | 
			
		||||
      - supports-color
 | 
			
		||||
 | 
			
		||||
  '@kevisual/types@0.0.10': {}
 | 
			
		||||
 | 
			
		||||
  '@kevisual/use-config@1.0.19(dotenv@16.6.1)':
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@kevisual/load': 0.0.6
 | 
			
		||||
      dotenv: 16.6.1
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-darwin-aarch64@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-darwin-x64-baseline@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-darwin-x64@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-aarch64-musl@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-aarch64@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64-baseline@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64-musl-baseline@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64-musl@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-linux-x64@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-windows-x64-baseline@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@oven/bun-windows-x64@1.3.0':
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  '@types/bun@1.3.0(@types/react@19.2.2)':
 | 
			
		||||
    dependencies:
 | 
			
		||||
      bun-types: 1.3.0(@types/react@19.2.2)
 | 
			
		||||
    transitivePeerDependencies:
 | 
			
		||||
      - '@types/react'
 | 
			
		||||
 | 
			
		||||
  '@types/node@24.7.2':
 | 
			
		||||
    dependencies:
 | 
			
		||||
      undici-types: 7.14.0
 | 
			
		||||
 | 
			
		||||
  '@types/react@19.2.2':
 | 
			
		||||
    dependencies:
 | 
			
		||||
      csstype: 3.1.3
 | 
			
		||||
 | 
			
		||||
  bun-types@1.3.0(@types/react@19.2.2):
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@types/node': 24.7.2
 | 
			
		||||
      '@types/react': 19.2.2
 | 
			
		||||
 | 
			
		||||
  bun@1.3.0:
 | 
			
		||||
    optionalDependencies:
 | 
			
		||||
      '@oven/bun-darwin-aarch64': 1.3.0
 | 
			
		||||
      '@oven/bun-darwin-x64': 1.3.0
 | 
			
		||||
      '@oven/bun-darwin-x64-baseline': 1.3.0
 | 
			
		||||
      '@oven/bun-linux-aarch64': 1.3.0
 | 
			
		||||
      '@oven/bun-linux-aarch64-musl': 1.3.0
 | 
			
		||||
      '@oven/bun-linux-x64': 1.3.0
 | 
			
		||||
      '@oven/bun-linux-x64-baseline': 1.3.0
 | 
			
		||||
      '@oven/bun-linux-x64-musl': 1.3.0
 | 
			
		||||
      '@oven/bun-linux-x64-musl-baseline': 1.3.0
 | 
			
		||||
      '@oven/bun-windows-x64': 1.3.0
 | 
			
		||||
      '@oven/bun-windows-x64-baseline': 1.3.0
 | 
			
		||||
 | 
			
		||||
  csstype@3.1.3: {}
 | 
			
		||||
 | 
			
		||||
  debug@4.4.3:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      ms: 2.1.3
 | 
			
		||||
 | 
			
		||||
  depd@2.0.0: {}
 | 
			
		||||
 | 
			
		||||
  dotenv@16.6.1: {}
 | 
			
		||||
 | 
			
		||||
  ee-first@1.1.1: {}
 | 
			
		||||
 | 
			
		||||
  encodeurl@2.0.0: {}
 | 
			
		||||
 | 
			
		||||
  escape-html@1.0.3: {}
 | 
			
		||||
 | 
			
		||||
  etag@1.8.1: {}
 | 
			
		||||
 | 
			
		||||
  eventemitter3@5.0.1: {}
 | 
			
		||||
 | 
			
		||||
  fresh@2.0.0: {}
 | 
			
		||||
 | 
			
		||||
  http-errors@2.0.0:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      depd: 2.0.0
 | 
			
		||||
      inherits: 2.0.4
 | 
			
		||||
      setprototypeof: 1.2.0
 | 
			
		||||
      statuses: 2.0.1
 | 
			
		||||
      toidentifier: 1.0.1
 | 
			
		||||
 | 
			
		||||
  inherits@2.0.4: {}
 | 
			
		||||
 | 
			
		||||
  mime-db@1.54.0: {}
 | 
			
		||||
 | 
			
		||||
  mime-types@3.0.1:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      mime-db: 1.54.0
 | 
			
		||||
 | 
			
		||||
  ms@2.1.3: {}
 | 
			
		||||
 | 
			
		||||
  node-forge@1.3.1: {}
 | 
			
		||||
 | 
			
		||||
  on-finished@2.4.1:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      ee-first: 1.1.1
 | 
			
		||||
 | 
			
		||||
  path-to-regexp@8.3.0: {}
 | 
			
		||||
 | 
			
		||||
  range-parser@1.2.1: {}
 | 
			
		||||
 | 
			
		||||
  selfsigned@3.0.1:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      node-forge: 1.3.1
 | 
			
		||||
 | 
			
		||||
  send@1.2.0:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      debug: 4.4.3
 | 
			
		||||
      encodeurl: 2.0.0
 | 
			
		||||
      escape-html: 1.0.3
 | 
			
		||||
      etag: 1.8.1
 | 
			
		||||
      fresh: 2.0.0
 | 
			
		||||
      http-errors: 2.0.0
 | 
			
		||||
      mime-types: 3.0.1
 | 
			
		||||
      ms: 2.1.3
 | 
			
		||||
      on-finished: 2.4.1
 | 
			
		||||
      range-parser: 1.2.1
 | 
			
		||||
      statuses: 2.0.2
 | 
			
		||||
    transitivePeerDependencies:
 | 
			
		||||
      - supports-color
 | 
			
		||||
 | 
			
		||||
  setprototypeof@1.2.0: {}
 | 
			
		||||
 | 
			
		||||
  statuses@2.0.1: {}
 | 
			
		||||
 | 
			
		||||
  statuses@2.0.2: {}
 | 
			
		||||
 | 
			
		||||
  toidentifier@1.0.1: {}
 | 
			
		||||
 | 
			
		||||
  undici-types@7.14.0: {}
 | 
			
		||||
							
								
								
									
										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();
 | 
			
		||||
							
								
								
									
										19
									
								
								server/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								server/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "@kevisual/types/json/backend.json",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "module": "NodeNext",
 | 
			
		||||
    "target": "esnext",
 | 
			
		||||
    "typeRoots": [
 | 
			
		||||
      "./node_modules/@types"
 | 
			
		||||
    ],
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": [
 | 
			
		||||
        "src/*"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "src/**/*", "code/**/*",
 | 
			
		||||
  ],
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user