Procházet zdrojové kódy

✨ feat(api): add elderly university and policy news modules

- add ElderlyUniversity entity with complete fields and CRUD operations
- add PolicyNews entity with complete fields and CRUD operations
- register new entities in data source
- create API routes for elderly universities and policy news
- add client API wrappers for new modules

♻️ refactor(api): simplify API client structure

- remove timeBankClient and related routes
- simplify client path structure by removing redundant 'api.v1' prefix
- update default export to include new modules
- clean up and reorganize API client exports
yourname před 8 měsíci
rodič
revize
62a33f1822

+ 10 - 13
src/client/api.ts

@@ -53,20 +53,16 @@ const client = hc<any>('/api/v1', {
   fetch: axiosFetch,
 })
 
-// 时间银行相关客户端
-export const timeBankClient = {
-  intros: client.api.v1['time-bank'].intros,
-  cases: client.api.v1['time-bank'].cases,
-  stats: client.api.v1['time-bank'].stats,
-  silverTimeBanks: client.api.v1['silver-users']['time-banks']
-}
+// 老年大学客户端
+export const elderlyUniversityClient = client['elderly-universities']
+export const policyNewsClient = client['policy-news']
 
 // 其他客户端
-export const authClient = client.api.v1.auth
-export const userClient = client.api.v1.users
-export const fileClient = client.api.v1.files
-export const silverJobsClient = client.api.v1['silver-jobs']
-export const silverUsersClient = client.api.v1['silver-users']
+export const authClient = client.auth
+export const userClient = client.users
+export const fileClient = client.files
+export const silverJobsClient = client['silver-jobs']
+export const silverUsersClient = client['silver-users']
 
 // 默认导出
 export default {
@@ -75,5 +71,6 @@ export default {
   files: fileClient,
   silverJobs: silverJobsClient,
   silverUsers: silverUsersClient,
-  timeBank: timeBankClient
+  elderlyUniversity: elderlyUniversityClient,
+  policyNews: policyNewsClient
 }

+ 6 - 6
src/server/api.ts

@@ -6,6 +6,8 @@ import rolesRoute from './api/roles/index'
 import fileRoutes from './api/files/index'
 import silverJobsRoutes from './api/silver-jobs/index'
 import silverUsersRoutes from './api/silver-users/index'
+import elderlyUniversityRoutes from './api/elderly-universities/index'
+import policyNewsRoutes from './api/policy-news/index'
 import { AuthContext } from './types/context'
 import { AppDataSource } from './data-source'
 
@@ -57,9 +59,8 @@ const roleRoutes = api.route('/api/v1/roles', rolesRoute)
 const fileApiRoutes = api.route('/api/v1/files', fileRoutes)
 const silverJobsApiRoutes = api.route('/api/v1/silver-jobs', silverJobsRoutes)
 const silverUsersApiRoutes = api.route('/api/v1/silver-users', silverUsersRoutes)
-const timeBankIntroApiRoutes = api.route('/api/v1/time-bank/intros', timeBankIntroRoutes)
-const timeBankCaseApiRoutes = api.route('/api/v1/time-bank/cases', timeBankCaseRoutes)
-const timeBankStatsApiRoutes = api.route('/api/v1/time-bank/stats', timeBankStatsRoutes)
+const elderlyUniversityApiRoutes = api.route('/api/v1/elderly-universities', elderlyUniversityRoutes)
+const policyNewsApiRoutes = api.route('/api/v1/policy-news', policyNewsRoutes)
 
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
@@ -67,8 +68,7 @@ export type RoleRoutes = typeof roleRoutes
 export type FileRoutes = typeof fileApiRoutes
 export type SilverJobsRoutes = typeof silverJobsApiRoutes
 export type SilverUsersRoutes = typeof silverUsersApiRoutes
-export type TimeBankIntroRoutes = typeof timeBankIntroApiRoutes
-export type TimeBankCaseRoutes = typeof timeBankCaseApiRoutes
-export type TimeBankStatsRoutes = typeof timeBankStatsApiRoutes
+export type ElderlyUniversityRoutes = typeof elderlyUniversityApiRoutes
+export type PolicyNewsRoutes = typeof policyNewsApiRoutes
 
 export default api

+ 15 - 0
src/server/api/elderly-universities/index.ts

@@ -0,0 +1,15 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { ElderlyUniversity, ElderlyUniversitySchema, CreateElderlyUniversityDto, UpdateElderlyUniversityDto } from '@/server/modules/silver-users/elderly-university.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const elderlyUniversityRoutes = createCrudRoutes({
+  entity: ElderlyUniversity,
+  createSchema: CreateElderlyUniversityDto,
+  updateSchema: UpdateElderlyUniversityDto,
+  getSchema: ElderlyUniversitySchema,
+  listSchema: ElderlyUniversitySchema,
+  searchFields: ['schoolName', 'schoolIntroduction', 'teacherResources', 'courseTypes'],
+  middleware: [authMiddleware]
+});
+
+export default elderlyUniversityRoutes;

+ 15 - 0
src/server/api/policy-news/index.ts

@@ -0,0 +1,15 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { PolicyNews, PolicyNewsSchema, CreatePolicyNewsDto, UpdatePolicyNewsDto } from '@/server/modules/silver-users/policy-news.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const policyNewsRoutes = createCrudRoutes({
+  entity: PolicyNews,
+  createSchema: CreatePolicyNewsDto,
+  updateSchema: UpdatePolicyNewsDto,
+  getSchema: PolicyNewsSchema,
+  listSchema: PolicyNewsSchema,
+  searchFields: ['newsTitle', 'newsContent', 'summary', 'source', 'category'],
+  middleware: [authMiddleware]
+});
+
+export default policyNewsRoutes;

+ 3 - 0
src/server/data-source.ts

@@ -25,6 +25,8 @@ import { SilverKnowledgeTag } from "./modules/silver-users/silver-knowledge-tag.
 import { SilverKnowledgeTagRelation } from "./modules/silver-users/silver-knowledge-tag-relation.entity"
 import { SilverKnowledgeStats } from "./modules/silver-users/silver-knowledge-stats.entity"
 import { SilverKnowledgeInteraction } from "./modules/silver-users/silver-knowledge-interaction.entity"
+import { ElderlyUniversity } from "./modules/silver-users/elderly-university.entity"
+import { PolicyNews } from "./modules/silver-users/policy-news.entity"
 
 export const AppDataSource = new DataSource({
   type: "mysql",
@@ -39,6 +41,7 @@ export const AppDataSource = new DataSource({
     TimeBankIntro, TimeBankCase, TimeBankStats,
     SilverKnowledge, SilverKnowledgeCategory, SilverKnowledgeTag,
     SilverKnowledgeTagRelation, SilverKnowledgeStats, SilverKnowledgeInteraction,
+    ElderlyUniversity, PolicyNews,
   ],
   migrations: [],
   synchronize: process.env.DB_SYNCHRONIZE !== "false",

+ 99 - 0
src/server/modules/silver-users/elderly-university.entity.ts

@@ -0,0 +1,99 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('elderly_universities')
+export class ElderlyUniversity {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ name: 'school_name', type: 'varchar', length: 255, comment: '学校名称' })
+  schoolName!: string;
+
+  @Column({ name: 'school_introduction', type: 'text', comment: '学校简介' })
+  schoolIntroduction!: string;
+
+  @Column({ name: 'teacher_resources', type: 'text', comment: '师资力量介绍' })
+  teacherResources!: string;
+
+  @Column({ name: 'course_count', type: 'int', unsigned: true, default: 0, comment: '课程数量' })
+  courseCount!: number;
+
+  @Column({ name: 'course_types', type: 'text', comment: '课程类型,多个类型用逗号分隔' })
+  courseTypes!: string;
+
+  @Column({ name: 'class_schedule', type: 'varchar', length: 500, comment: '上课时间' })
+  classSchedule!: string;
+
+  @Column({ name: 'contact_teacher', type: 'varchar', length: 100, comment: '联系老师' })
+  contactTeacher!: string;
+
+  @Column({ name: 'contact_phone', type: 'varchar', length: 20, comment: '联系电话' })
+  contactPhone!: string;
+
+  @Column({ name: 'school_address', type: 'varchar', length: 500, comment: '学校地址' })
+  schoolAddress!: string;
+
+  @Column({ name: 'favorite_count', type: 'int', unsigned: true, default: 0, comment: '收藏数' })
+  favoriteCount!: number;
+
+  @Column({ name: 'view_count', type: 'int', unsigned: true, default: 0, comment: '阅读数' })
+  viewCount!: number;
+
+  @Column({ name: 'like_count', type: 'int', unsigned: true, default: 0, comment: '点赞数' })
+  likeCount!: number;
+
+  @Column({ name: 'is_deleted', type: 'tinyint', default: 0, comment: '删除状态 0:未删除 1:已删除' })
+  isDeleted!: number;
+
+  @CreateDateColumn({ name: 'created_at' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ name: 'updated_at' })
+  updatedAt!: Date;
+}
+
+// 基础Schema定义
+export const ElderlyUniversitySchema = z.object({
+  id: z.number().int().positive().openapi({ description: '学校ID', example: 1 }),
+  schoolName: z.string().max(255).openapi({ description: '学校名称', example: '北京市老年大学' }),
+  schoolIntroduction: z.string().openapi({ description: '学校简介', example: '北京市老年大学成立于1985年,致力于为老年人提供优质教育服务...' }),
+  teacherResources: z.string().openapi({ description: '师资力量', example: '拥有专业教师50余人,其中教授10人,副教授20人...' }),
+  courseCount: z.coerce.number().int().min(0).openapi({ description: '课程数量', example: 25 }),
+  courseTypes: z.string().openapi({ description: '课程类型,多个用逗号分隔', example: '书法,绘画,舞蹈,音乐,计算机' }),
+  classSchedule: z.string().max(500).openapi({ description: '上课时间', example: '周一至周五 上午9:00-11:30,下午14:00-16:30' }),
+  contactTeacher: z.string().max(100).openapi({ description: '联系老师', example: '张老师' }),
+  contactPhone: z.string().max(20).openapi({ description: '联系电话', example: '010-12345678' }),
+  schoolAddress: z.string().max(500).openapi({ description: '学校地址', example: '北京市朝阳区某某街道123号' }),
+  favoriteCount: z.coerce.number().int().min(0).openapi({ description: '收藏数', example: 156 }),
+  viewCount: z.coerce.number().int().min(0).openapi({ description: '阅读数', example: 2345 }),
+  likeCount: z.coerce.number().int().min(0).openapi({ description: '点赞数', example: 89 }),
+  isDeleted: z.coerce.number().int().min(0).max(1).openapi({ description: '删除状态', example: 0 }),
+  createdAt: z.date().openapi({ description: '创建时间', example: '2024-01-15T08:00:00Z' }),
+  updatedAt: z.date().openapi({ description: '更新时间', example: '2024-01-15T08:00:00Z' })
+});
+
+// 创建DTO
+export const CreateElderlyUniversityDto = z.object({
+  schoolName: z.string().max(255).openapi({ description: '学校名称', example: '北京市老年大学' }),
+  schoolIntroduction: z.string().openapi({ description: '学校简介', example: '北京市老年大学成立于1985年...' }),
+  teacherResources: z.string().openapi({ description: '师资力量', example: '拥有专业教师50余人...' }),
+  courseCount: z.coerce.number().int().min(0).optional().default(0).openapi({ description: '课程数量', example: 25 }),
+  courseTypes: z.string().openapi({ description: '课程类型,多个用逗号分隔', example: '书法,绘画,舞蹈,音乐,计算机' }),
+  classSchedule: z.string().max(500).openapi({ description: '上课时间', example: '周一至周五 上午9:00-11:30...' }),
+  contactTeacher: z.string().max(100).openapi({ description: '联系老师', example: '张老师' }),
+  contactPhone: z.string().max(20).openapi({ description: '联系电话', example: '010-12345678' }),
+  schoolAddress: z.string().max(500).openapi({ description: '学校地址', example: '北京市朝阳区某某街道123号' })
+});
+
+// 更新DTO
+export const UpdateElderlyUniversityDto = z.object({
+  schoolName: z.string().max(255).optional().openapi({ description: '学校名称', example: '北京市老年大学' }),
+  schoolIntroduction: z.string().optional().openapi({ description: '学校简介', example: '北京市老年大学成立于1985年...' }),
+  teacherResources: z.string().optional().openapi({ description: '师资力量', example: '拥有专业教师50余人...' }),
+  courseCount: z.coerce.number().int().min(0).optional().openapi({ description: '课程数量', example: 30 }),
+  courseTypes: z.string().optional().openapi({ description: '课程类型,多个用逗号分隔', example: '书法,绘画,舞蹈,音乐,计算机' }),
+  classSchedule: z.string().max(500).optional().openapi({ description: '上课时间', example: '周一至周五 上午9:00-11:30...' }),
+  contactTeacher: z.string().max(100).optional().openapi({ description: '联系老师', example: '张老师' }),
+  contactPhone: z.string().max(20).optional().openapi({ description: '联系电话', example: '010-12345678' }),
+  schoolAddress: z.string().max(500).optional().openapi({ description: '学校地址', example: '北京市朝阳区某某街道123号' })
+});

+ 86 - 0
src/server/modules/silver-users/policy-news.entity.ts

@@ -0,0 +1,86 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('policy_news')
+export class PolicyNews {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ name: 'news_title', type: 'varchar', length: 255, comment: '资讯标题' })
+  newsTitle!: string;
+
+  @Column({ name: 'news_content', type: 'text', comment: '资讯内容' })
+  newsContent!: string;
+
+  @Column({ name: 'publish_time', type: 'timestamp', comment: '发布时间' })
+  publishTime!: Date;
+
+  @Column({ name: 'view_count', type: 'int', unsigned: true, default: 0, comment: '阅读量' })
+  viewCount!: number;
+
+  @Column({ name: 'images', type: 'text', nullable: true, comment: '图片URL,多个用逗号分隔' })
+  images!: string | null;
+
+  @Column({ name: 'summary', type: 'text', nullable: true, comment: '资讯摘要' })
+  summary!: string | null;
+
+  @Column({ name: 'source', type: 'varchar', length: 255, nullable: true, comment: '资讯来源' })
+  source!: string | null;
+
+  @Column({ name: 'category', type: 'varchar', length: 100, nullable: true, comment: '资讯分类' })
+  category!: string | null;
+
+  @Column({ name: 'is_featured', type: 'tinyint', default: 0, comment: '是否精选 0:否 1:是' })
+  isFeatured!: number;
+
+  @Column({ name: 'is_deleted', type: 'tinyint', default: 0, comment: '删除状态 0:未删除 1:已删除' })
+  isDeleted!: number;
+
+  @CreateDateColumn({ name: 'created_at' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ name: 'updated_at' })
+  updatedAt!: Date;
+}
+
+// 基础Schema定义
+export const PolicyNewsSchema = z.object({
+  id: z.number().int().positive().openapi({ description: '资讯ID', example: 1 }),
+  newsTitle: z.string().max(255).openapi({ description: '资讯标题', example: '关于积极应对人口老龄化的实施意见' }),
+  newsContent: z.string().openapi({ description: '资讯内容', example: '国务院办公厅近日印发《关于积极应对人口老龄化的实施意见》...' }),
+  publishTime: z.date().openapi({ description: '发布时间', example: '2024-01-15T08:00:00Z' }),
+  viewCount: z.coerce.number().int().min(0).openapi({ description: '阅读量', example: 1234 }),
+  images: z.string().nullable().openapi({ description: '图片URL,多个用逗号分隔', example: 'https://example.com/news1.jpg,https://example.com/news2.jpg' }),
+  summary: z.string().nullable().openapi({ description: '资讯摘要', example: '国务院办公厅近日印发《关于积极应对人口老龄化的实施意见》...' }),
+  source: z.string().max(255).nullable().openapi({ description: '资讯来源', example: '中国政府网' }),
+  category: z.string().max(100).nullable().openapi({ description: '资讯分类', example: '政策法规' }),
+  isFeatured: z.coerce.number().int().min(0).max(1).openapi({ description: '是否精选', example: 1 }),
+  isDeleted: z.coerce.number().int().min(0).max(1).openapi({ description: '删除状态', example: 0 }),
+  createdAt: z.date().openapi({ description: '创建时间', example: '2024-01-15T08:00:00Z' }),
+  updatedAt: z.date().openapi({ description: '更新时间', example: '2024-01-15T08:00:00Z' })
+});
+
+// 创建DTO
+export const CreatePolicyNewsDto = z.object({
+  newsTitle: z.string().max(255).openapi({ description: '资讯标题', example: '关于积极应对人口老龄化的实施意见' }),
+  newsContent: z.string().openapi({ description: '资讯内容', example: '国务院办公厅近日印发《关于积极应对人口老龄化的实施意见》...' }),
+  publishTime: z.coerce.date().openapi({ description: '发布时间', example: '2024-01-15' }),
+  images: z.string().nullable().optional().openapi({ description: '图片URL,多个用逗号分隔', example: 'https://example.com/news1.jpg' }),
+  summary: z.string().nullable().optional().openapi({ description: '资讯摘要', example: '国务院办公厅近日印发《关于积极应对人口老龄化的实施意见》...' }),
+  source: z.string().max(255).nullable().optional().openapi({ description: '资讯来源', example: '中国政府网' }),
+  category: z.string().max(100).nullable().optional().openapi({ description: '资讯分类', example: '政策法规' }),
+  isFeatured: z.coerce.number().int().min(0).max(1).default(0).optional().openapi({ description: '是否精选', example: 0 })
+});
+
+// 更新DTO
+export const UpdatePolicyNewsDto = z.object({
+  newsTitle: z.string().max(255).optional().openapi({ description: '资讯标题', example: '关于积极应对人口老龄化的实施意见' }),
+  newsContent: z.string().optional().openapi({ description: '资讯内容', example: '国务院办公厅近日印发《关于积极应对人口老龄化的实施意见》...' }),
+  publishTime: z.coerce.date().optional().openapi({ description: '发布时间', example: '2024-01-15' }),
+  images: z.string().nullable().optional().openapi({ description: '图片URL,多个用逗号分隔', example: 'https://example.com/news1.jpg' }),
+  summary: z.string().nullable().optional().openapi({ description: '资讯摘要', example: '国务院办公厅近日印发《关于积极应对人口老龄化的实施意见》...' }),
+  source: z.string().max(255).nullable().optional().openapi({ description: '资讯来源', example: '中国政府网' }),
+  category: z.string().max(100).nullable().optional().openapi({ description: '资讯分类', example: '政策法规' }),
+  isFeatured: z.coerce.number().int().min(0).max(1).optional().openapi({ description: '是否精选', example: 1 }),
+  viewCount: z.coerce.number().int().min(0).optional().openapi({ description: '阅读量', example: 1234 })
+});