Преглед на файлове

✨ feat(crud): 添加只读模式支持

- 新增readOnly选项,默认为false
- 当readOnly=true时,仅注册GET路由(列表和详情)
- 当readOnly=false时,注册完整CRUD路由(列表、创建、详情、更新、删除)
- 优化路由注册逻辑,使用条件分支组织不同模式的路由定义
yourname преди 4 месеца
родител
ревизия
ce0b5e37ae
променени са 2 файла, в които са добавени 202 реда и са изтрити 123 реда
  1. 201 123
      src/server/utils/generic-crud.routes.ts
  2. 1 0
      src/server/utils/generic-crud.service.ts

+ 201 - 123
src/server/utils/generic-crud.routes.ts

@@ -14,7 +14,7 @@ export function createCrudRoutes<
   GetSchema extends z.ZodSchema = z.ZodSchema,
   ListSchema extends z.ZodSchema = z.ZodSchema
 >(options: CrudOptions<T, CreateSchema, UpdateSchema, GetSchema, ListSchema>) {
-  const { entity, createSchema, updateSchema, getSchema, listSchema, searchFields, relations, middleware = [], userTracking, relationFields } = options;
+  const { entity, createSchema, updateSchema, getSchema, listSchema, searchFields, relations, middleware = [], userTracking, relationFields, readOnly = false } = options;
   
   // 创建CRUD服务实例
   // 抽象类不能直接实例化,需要创建具体实现类
@@ -218,135 +218,213 @@ export function createCrudRoutes<
   });
   
   // 注册路由处理函数
-  const routes = app
-    .openapi(listRoute, async (c) => {
-      try {
-        const query = c.req.valid('query') as any;
-        const { page, pageSize, keyword, sortBy, sortOrder, filters } = query;
-        
-        // 构建排序对象
-        const order: any = {};
-        if (sortBy) {
-          order[sortBy] = sortOrder || 'DESC';
-        } else {
-          order['id'] = 'DESC';
-        }
-        
-        // 解析筛选条件
-        let parsedFilters: any = undefined;
-        if (filters) {
-          try {
-            parsedFilters = JSON.parse(filters);
-          } catch (e) {
-            return c.json({ code: 400, message: '筛选条件格式错误' }, 400);
+  
+  // 只读模式下只注册 GET 路由
+  if (!readOnly) {
+    // 完整 CRUD 路由
+    const routes = app
+      .openapi(listRoute, async (c) => {
+        try {
+          const query = c.req.valid('query') as any;
+          const { page, pageSize, keyword, sortBy, sortOrder, filters } = query;
+          
+          // 构建排序对象
+          const order: any = {};
+          if (sortBy) {
+            order[sortBy] = sortOrder || 'DESC';
+          } else {
+            order['id'] = 'DESC';
           }
+          
+          // 解析筛选条件
+          let parsedFilters: any = undefined;
+          if (filters) {
+            try {
+              parsedFilters = JSON.parse(filters);
+            } catch (e) {
+              return c.json({ code: 400, message: '筛选条件格式错误' }, 400);
+            }
+          }
+          
+          const [data, total] = await crudService.getList(
+            page,
+            pageSize,
+            keyword,
+            searchFields,
+            undefined,
+            relations || [],
+            order,
+            parsedFilters
+          );
+          
+          return c.json({
+            // data: z.array(listSchema).parse(data),
+            data: await parseWithAwait(z.array(listSchema), data),
+            pagination: { total, current: page, pageSize }
+          }, 200);
+        } catch (error) {
+          if (error instanceof z.ZodError) {
+            return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+          }
+          return c.json({
+            code: 500,
+            message: error instanceof Error ? error.message : '获取列表失败'
+          }, 500);
         }
-        
-        const [data, total] = await crudService.getList(
-          page,
-          pageSize,
-          keyword,
-          searchFields,
-          undefined,
-          relations || [],
-          order,
-          parsedFilters
-        );
-        
-        return c.json({
-          // data: z.array(listSchema).parse(data),
-          data: await parseWithAwait(z.array(listSchema), data),
-          pagination: { total, current: page, pageSize }
-        }, 200);
-      } catch (error) {
-        if (error instanceof z.ZodError) {
-          return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
-        }
-        return c.json({
-          code: 500,
-          message: error instanceof Error ? error.message : '获取列表失败'
-        }, 500);
-      }
-    })
-    .openapi(createRouteDef, async (c: any) => {
-      try {
-        const data = c.req.valid('json');
-        const user = c.get('user');
-        const result = await crudService.create(data, user?.id);
-        return c.json(result, 201);
-      } catch (error) {
-        if (error instanceof z.ZodError) {
-          return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
-        }
-        return c.json({
-          code: 500,
-          message: error instanceof Error ? error.message : '创建资源失败'
-        }, 500);
-      }
-    })
-    .openapi(getRouteDef, async (c: any) => {
-      try {
-        const { id } = c.req.valid('param');
-        const result = await crudService.getById(id, relations || []);
-        
-        if (!result) {
-          return c.json({ code: 404, message: '资源不存在' }, 404);
+      })
+      .openapi(createRouteDef, async (c: any) => {
+        try {
+          const data = c.req.valid('json');
+          const user = c.get('user');
+          const result = await crudService.create(data, user?.id);
+          return c.json(result, 201);
+        } catch (error) {
+          if (error instanceof z.ZodError) {
+            return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+          }
+          return c.json({
+            code: 500,
+            message: error instanceof Error ? error.message : '创建资源失败'
+          }, 500);
         }
-        
-        // return c.json(await getSchema.parseAsync(result), 200);
-        return c.json(await parseWithAwait(getSchema, result), 200);
-      } catch (error) {
-        if (error instanceof z.ZodError) {
-          return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+      })
+      .openapi(getRouteDef, async (c: any) => {
+        try {
+          const { id } = c.req.valid('param');
+          const result = await crudService.getById(id, relations || []);
+          
+          if (!result) {
+            return c.json({ code: 404, message: '资源不存在' }, 404);
+          }
+          
+          // return c.json(await getSchema.parseAsync(result), 200);
+          return c.json(await parseWithAwait(getSchema, result), 200);
+        } catch (error) {
+          if (error instanceof z.ZodError) {
+            return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+          }
+          return c.json({
+            code: 500,
+            message: error instanceof Error ? error.message : '获取资源失败'
+          }, 500);
         }
-        return c.json({
-          code: 500,
-          message: error instanceof Error ? error.message : '获取资源失败'
-        }, 500);
-      }
-    })
-    .openapi(updateRouteDef, async (c: any) => {
-      try {
-        const { id } = c.req.valid('param');
-        const data = c.req.valid('json');
-        const user = c.get('user');
-        const result = await crudService.update(id, data, user?.id);
-        
-        if (!result) {
-          return c.json({ code: 404, message: '资源不存在' }, 404);
+      })
+      .openapi(updateRouteDef, async (c: any) => {
+        try {
+          const { id } = c.req.valid('param');
+          const data = c.req.valid('json');
+          const user = c.get('user');
+          const result = await crudService.update(id, data, user?.id);
+          
+          if (!result) {
+            return c.json({ code: 404, message: '资源不存在' }, 404);
+          }
+          
+          return c.json(result, 200);
+        } catch (error) {
+          if (error instanceof z.ZodError) {
+            return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+          }
+          return c.json({
+            code: 500,
+            message: error instanceof Error ? error.message : '更新资源失败'
+          }, 500);
         }
-        
-        return c.json(result, 200);
-      } catch (error) {
-        if (error instanceof z.ZodError) {
-          return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+      })
+      .openapi(deleteRouteDef, async (c: any) => {
+        try {
+          const { id } = c.req.valid('param');
+          const success = await crudService.delete(id);
+          
+          if (!success) {
+            return c.json({ code: 404, message: '资源不存在' }, 404);
+          }
+          
+          return c.body(null, 204);
+        } catch (error) {
+          if (error instanceof z.ZodError) {
+            return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+          }
+          return c.json({
+            code: 500,
+            message: error instanceof Error ? error.message : '删除资源失败'
+          }, 500);
         }
-        return c.json({
-          code: 500,
-          message: error instanceof Error ? error.message : '更新资源失败'
-        }, 500);
-      }
-    })
-    .openapi(deleteRouteDef, async (c: any) => {
-      try {
-        const { id } = c.req.valid('param');
-        const success = await crudService.delete(id);
-        
-        if (!success) {
-          return c.json({ code: 404, message: '资源不存在' }, 404);
+      });
+
+    return routes;
+  } else {
+    // 只读模式,只注册 GET 路由
+    const routes = app
+      .openapi(listRoute, async (c) => {
+        try {
+          const query = c.req.valid('query') as any;
+          const { page, pageSize, keyword, sortBy, sortOrder, filters } = query;
+          
+          // 构建排序对象
+          const order: any = {};
+          if (sortBy) {
+            order[sortBy] = sortOrder || 'DESC';
+          } else {
+            order['id'] = 'DESC';
+          }
+          
+          // 解析筛选条件
+          let parsedFilters: any = undefined;
+          if (filters) {
+            try {
+              parsedFilters = JSON.parse(filters);
+            } catch (e) {
+              return c.json({ code: 400, message: '筛选条件格式错误' }, 400);
+            }
+          }
+          
+          const [data, total] = await crudService.getList(
+            page,
+            pageSize,
+            keyword,
+            searchFields,
+            undefined,
+            relations || [],
+            order,
+            parsedFilters
+          );
+          
+          return c.json({
+            data: await parseWithAwait(z.array(listSchema), data),
+            pagination: { total, current: page, pageSize }
+          }, 200);
+        } catch (error) {
+          if (error instanceof z.ZodError) {
+            return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+          }
+          return c.json({
+            code: 500,
+            message: error instanceof Error ? error.message : '获取列表失败'
+          }, 500);
         }
-        
-        return c.body(null, 204);
-      } catch (error) {
-        if (error instanceof z.ZodError) {
-          return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+      })
+      .openapi(getRouteDef, async (c: any) => {
+        try {
+          const { id } = c.req.valid('param');
+          const result = await crudService.getById(id, relations || []);
+          
+          if (!result) {
+            return c.json({ code: 404, message: '资源不存在' }, 404);
+          }
+          
+          return c.json(await parseWithAwait(getSchema, result), 200);
+        } catch (error) {
+          if (error instanceof z.ZodError) {
+            return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400);
+          }
+          return c.json({
+            code: 500,
+            message: error instanceof Error ? error.message : '获取资源失败'
+          }, 500);
         }
-        return c.json({
-          code: 500,
-          message: error instanceof Error ? error.message : '删除资源失败'
-        }, 500);
-      }
-    });
+      });
+    return routes;
+  }
   
-  return routes;
 }

+ 1 - 0
src/server/utils/generic-crud.service.ts

@@ -289,4 +289,5 @@ export type CrudOptions<
   middleware?: any[];
   userTracking?: UserTrackingOptions;
   relationFields?: RelationFieldOptions;
+  readOnly?: boolean;
 };