feat(container): add CRUD operations for container management

- Implemented routes for listing, retrieving, updating, and deleting containers.
- Added ContainerModel with necessary fields and methods for data handling.
- Created utility functions for fetching container data by ID.

feat(page): enhance page management with CRUD and publish functionality

- Developed routes for managing pages, including listing, updating, and deleting.
- Integrated caching and zip file generation for page exports.
- Added publish functionality to manage app versions and file uploads.

feat(prompts): implement prompt management with CRUD operations

- Created routes for listing, updating, and deleting prompts.
- Added pagination and search capabilities for prompt listing.

test: add common query utilities and prompt tests

- Implemented common query utilities for API interactions.
- Added tests for prompt listing functionality.
This commit is contained in:
2025-12-30 13:28:50 +08:00
parent 27e5fb5e82
commit 8731801b52
28 changed files with 411 additions and 103 deletions

View File

@@ -0,0 +1 @@
import './list.ts';

View File

@@ -0,0 +1,108 @@
import { CustomError } from '@kevisual/router';
import { app } from '../../app.ts';
import { ContainerModel, ContainerData, Container } from './models/index.ts';
app
.route({
path: 'container',
key: 'list',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const list = await ContainerModel.findAll({
order: [['updatedAt', 'DESC']],
where: {
uid: tokenUser.id,
},
attributes: { exclude: ['code'] },
});
ctx.body = list;
return ctx;
})
.addTo(app);
app
.route({
path: 'container',
key: 'get',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const id = ctx.query.id;
if (!id) {
throw new CustomError('id is required');
}
const container = await ContainerModel.findByPk(id);
if (!container) {
throw new CustomError('container not found');
}
if (container.uid !== tokenUser.id) {
throw new CustomError('container not found');
}
ctx.body = container;
return ctx;
})
.addTo(app);
app
.route({
path: 'container',
key: 'update',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const data = ctx.query.data;
const { id, ...container } = data;
let containerModel: ContainerModel | null = null;
if (id) {
containerModel = await ContainerModel.findByPk(id);
if (containerModel) {
containerModel.update({
...container,
publish: {
...containerModel.publish,
...container.publish,
},
});
await containerModel.save();
}
} else {
try {
containerModel = await ContainerModel.create({
...container,
uid: tokenUser.id,
});
} catch (e) {
console.log('error', e);
}
console.log('containerModel', container);
}
ctx.body = containerModel;
return ctx;
})
.addTo(app);
app
.route({
path: 'container',
key: 'delete',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const id = ctx.query.id;
const container = await ContainerModel.findByPk(id);
if (!container) {
throw new CustomError('container not found');
}
if (container.uid !== tokenUser.id) {
throw new CustomError('container not found');
}
await container.destroy();
ctx.body = container;
return ctx;
})
.addTo(app);

View File

@@ -0,0 +1,101 @@
import { sequelize } from '../../../modules/sequelize.ts';
import { DataTypes, Model } from 'sequelize';
import crypto from 'crypto';
export interface ContainerData {}
export type ContainerPublish = {
key: string;
title?: string;
description?: string;
fileName?: string;
version?: string;
};
export type Container = Partial<InstanceType<typeof ContainerModel>>;
/**
* 用户代码容器
*/
export class ContainerModel extends Model {
declare id: string;
// 标题
declare title: string;
// 描述
declare description: string;
// 类型
declare type: string;
// 标签
declare tags: string[];
// 代码
declare code: string;
// hash 值
declare hash: string;
// 数据
declare data: ContainerData;
// 发布
declare publish: ContainerPublish;
// 用户 id
declare uid: string;
declare updatedAt: Date;
declare createdAt: Date;
createHash() {
const { code } = this;
const hash = crypto.createHash('md5');
hash.update(code);
this.hash = hash.digest('hex');
}
}
ContainerModel.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
comment: 'id',
},
title: {
type: DataTypes.TEXT,
defaultValue: '',
},
description: {
type: DataTypes.TEXT,
defaultValue: '',
},
tags: {
type: DataTypes.JSON,
defaultValue: [],
},
type: {
type: DataTypes.STRING, // 代码类型, html, js, render-js
defaultValue: 'render-js',
},
code: {
type: DataTypes.TEXT,
defaultValue: '',
},
hash: {
type: DataTypes.TEXT,
defaultValue: '',
},
data: {
type: DataTypes.JSON,
defaultValue: {},
},
publish: {
type: DataTypes.JSON,
defaultValue: {},
},
uid: {
type: DataTypes.UUID,
allowNull: true,
},
},
{
sequelize,
tableName: 'kv_container',
paranoid: true,
},
);
// ContainerModel.sync({ alter: true, logging: false }).catch((e) => {
// console.error('ContainerModel sync', e);
// });

View File

@@ -0,0 +1,11 @@
import { ContainerModel } from '../models/index.ts';
export const getContainerById = async (id: string) => {
const container = await ContainerModel.findByPk(id);
const code = container?.code;
return {
code,
id: container?.id,
updatedAt: new Date(container?.updatedAt).getTime(),
};
};

View File

@@ -0,0 +1,3 @@
import { ContainerData } from './models/index.ts';
export { ContainerData };