feat(diary): Phase 1.3 完善修复 — 贴纸/主题 CRUD + 管理端对接 + HMS 清理
H7 贴纸 CRUD: - POST /diary/sticker-packs — 创建贴纸包 - DELETE /diary/sticker-packs/:id — 软删除贴纸包 - POST /diary/sticker-packs/:id/stickers — 添加贴纸 H8 主题编辑/停用: - PUT /diary/topics/:id — 编辑主题 (标题/描述/截止日期) - PATCH /diary/topics/:id/deactivate — 停用主题 管理端前端: - ClassList.tsx 对接 update/deactivate/reset-code (含 Popconfirm 确认) - JournalList.tsx 班级筛选改用 classApi.listAll() - classes.ts 新增 listAll/update/deactivate/resetCode API M2 HMS 遗留清理: - 删除 copilot.ts, healthFixtures.ts, healthHandlers.ts - AuditLogViewer 资源类型 → 日记模块 - auth.test.ts / renderWithProviders health.* → diary.* M4 编辑器加载: - EditorPage journalId 非空时从 Isar 恢复笔画/元素/标签/心情/标题 77 tests passed, cargo check ✅, tsc ✅, flutter analyze ✅
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
// 贴纸服务 — 贴纸包与贴纸管理
|
||||
|
||||
use sea_orm::{
|
||||
ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait,
|
||||
QueryFilter, QueryOrder, Set,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::dto::{StickerPackResp, StickerResp, TemplateResp};
|
||||
use crate::dto::{CreateStickerPackReq, CreateStickerReq, StickerPackResp, StickerResp, TemplateResp};
|
||||
use crate::entity::{sticker, sticker_pack, template};
|
||||
use crate::error::{DiaryError, DiaryResult};
|
||||
|
||||
@@ -151,6 +152,116 @@ impl StickerService {
|
||||
is_free: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// 创建贴纸包(管理端)
|
||||
pub async fn create_sticker_pack(
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
req: &CreateStickerPackReq,
|
||||
db: &DatabaseConnection,
|
||||
) -> DiaryResult<StickerPackResp> {
|
||||
let now = chrono::Utc::now();
|
||||
let id = Uuid::now_v7();
|
||||
|
||||
let model = sticker_pack::ActiveModel {
|
||||
id: Set(id),
|
||||
tenant_id: Set(tenant_id),
|
||||
name: Set(req.name.clone()),
|
||||
description: Set(req.description.clone()),
|
||||
thumbnail_url: Set(req.thumbnail_url.clone()),
|
||||
is_free: Set(req.is_free),
|
||||
price: Set(req.price),
|
||||
category: Set(req.category.clone()),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
created_by: Set(user_id),
|
||||
updated_by: Set(user_id),
|
||||
deleted_at: Set(None),
|
||||
version: Set(1),
|
||||
};
|
||||
let inserted = model.insert(db).await?;
|
||||
|
||||
Ok(StickerPackResp {
|
||||
id: inserted.id,
|
||||
name: inserted.name,
|
||||
description: inserted.description,
|
||||
cover_image_url: inserted.thumbnail_url,
|
||||
sticker_count: 0,
|
||||
is_free: inserted.is_free,
|
||||
category: inserted.category,
|
||||
})
|
||||
}
|
||||
|
||||
/// 删除贴纸包(软删除)
|
||||
pub async fn delete_sticker_pack(
|
||||
tenant_id: Uuid,
|
||||
pack_id: Uuid,
|
||||
user_id: Uuid,
|
||||
db: &DatabaseConnection,
|
||||
) -> DiaryResult<()> {
|
||||
let model = sticker_pack::Entity::find()
|
||||
.filter(sticker_pack::Column::Id.eq(pack_id))
|
||||
.filter(sticker_pack::Column::TenantId.eq(tenant_id))
|
||||
.filter(sticker_pack::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| DiaryError::NotFound(format!("贴纸包 {} 不存在", pack_id)))?;
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let mut active: sticker_pack::ActiveModel = model.into();
|
||||
active.deleted_at = Set(Some(now));
|
||||
active.updated_at = Set(now);
|
||||
active.updated_by = Set(user_id);
|
||||
active.update(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 创建贴纸(管理端)
|
||||
pub async fn create_sticker(
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
pack_id: Uuid,
|
||||
req: &CreateStickerReq,
|
||||
db: &DatabaseConnection,
|
||||
) -> DiaryResult<StickerResp> {
|
||||
// 验证贴纸包存在
|
||||
let _pack = sticker_pack::Entity::find()
|
||||
.filter(sticker_pack::Column::Id.eq(pack_id))
|
||||
.filter(sticker_pack::Column::TenantId.eq(tenant_id))
|
||||
.filter(sticker_pack::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| DiaryError::NotFound(format!("贴纸包 {} 不存在", pack_id)))?;
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let id = Uuid::now_v7();
|
||||
|
||||
let model = sticker::ActiveModel {
|
||||
id: Set(id),
|
||||
tenant_id: Set(tenant_id),
|
||||
pack_id: Set(pack_id),
|
||||
name: Set(req.name.clone()),
|
||||
image_url: Set(req.image_url.clone()),
|
||||
category: Set(req.category.clone()),
|
||||
tags: Set(None),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
created_by: Set(user_id),
|
||||
updated_by: Set(user_id),
|
||||
deleted_at: Set(None),
|
||||
version: Set(1),
|
||||
};
|
||||
let inserted = model.insert(db).await?;
|
||||
|
||||
Ok(StickerResp {
|
||||
id: inserted.id,
|
||||
pack_id: inserted.pack_id,
|
||||
name: inserted.name,
|
||||
image_url: inserted.image_url,
|
||||
category: inserted.category,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -7,7 +7,7 @@ use sea_orm::{
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::dto::{CreateTopicReq, TopicResp};
|
||||
use crate::dto::{CreateTopicReq, TopicResp, UpdateTopicReq};
|
||||
use crate::entity::topic_assignment;
|
||||
use crate::error::{DiaryError, DiaryResult};
|
||||
use crate::service::notification_service::NotificationService;
|
||||
@@ -112,6 +112,93 @@ impl TopicService {
|
||||
|
||||
Ok(topics.into_iter().map(topic_model_to_resp).collect())
|
||||
}
|
||||
|
||||
/// 更新主题信息(老师)
|
||||
///
|
||||
/// 仅主题创建者可修改标题、描述、截止日期。
|
||||
pub async fn update_topic(
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
topic_id: Uuid,
|
||||
req: &UpdateTopicReq,
|
||||
db: &DatabaseConnection,
|
||||
) -> DiaryResult<TopicResp> {
|
||||
let model = topic_assignment::Entity::find()
|
||||
.filter(topic_assignment::Column::Id.eq(topic_id))
|
||||
.filter(topic_assignment::Column::TenantId.eq(tenant_id))
|
||||
.filter(topic_assignment::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| DiaryError::NotFound(format!("主题 {} 不存在", topic_id)))?;
|
||||
|
||||
// 仅布置者可编辑
|
||||
if model.teacher_id != user_id {
|
||||
return Err(DiaryError::Forbidden);
|
||||
}
|
||||
|
||||
// 乐观锁
|
||||
if model.version != req.version {
|
||||
return Err(DiaryError::VersionConflict {
|
||||
local: req.version,
|
||||
server: model.version,
|
||||
});
|
||||
}
|
||||
|
||||
let now = Utc::now();
|
||||
let mut active: topic_assignment::ActiveModel = model.into();
|
||||
if let Some(ref title) = req.title {
|
||||
active.title = Set(title.clone());
|
||||
}
|
||||
if let Some(ref desc) = req.description {
|
||||
active.description = Set(Some(desc.clone()));
|
||||
}
|
||||
if req.due_date.is_some() {
|
||||
active.due_date = Set(req.due_date);
|
||||
}
|
||||
active.updated_at = Set(now);
|
||||
active.updated_by = Set(user_id);
|
||||
active.version = Set(req.version + 1);
|
||||
|
||||
let updated = active.update(db).await?;
|
||||
Ok(topic_model_to_resp(updated))
|
||||
}
|
||||
|
||||
/// 停用主题(老师)
|
||||
///
|
||||
/// 停用后主题不再显示给学生,已提交的日记不受影响。
|
||||
pub async fn deactivate_topic(
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
topic_id: Uuid,
|
||||
db: &DatabaseConnection,
|
||||
) -> DiaryResult<TopicResp> {
|
||||
let model = topic_assignment::Entity::find()
|
||||
.filter(topic_assignment::Column::Id.eq(topic_id))
|
||||
.filter(topic_assignment::Column::TenantId.eq(tenant_id))
|
||||
.filter(topic_assignment::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| DiaryError::NotFound(format!("主题 {} 不存在", topic_id)))?;
|
||||
|
||||
if model.teacher_id != user_id {
|
||||
return Err(DiaryError::Forbidden);
|
||||
}
|
||||
|
||||
if !model.is_active {
|
||||
return Err(DiaryError::BadRequest("主题已处于停用状态".to_string()));
|
||||
}
|
||||
|
||||
let now = Utc::now();
|
||||
let current_version = model.version;
|
||||
let mut active: topic_assignment::ActiveModel = model.into();
|
||||
active.is_active = Set(false);
|
||||
active.updated_at = Set(now);
|
||||
active.updated_by = Set(user_id);
|
||||
active.version = Set(current_version + 1);
|
||||
let updated = active.update(db).await?;
|
||||
|
||||
Ok(topic_model_to_resp(updated))
|
||||
}
|
||||
}
|
||||
|
||||
/// topic_assignment::Model -> TopicResp
|
||||
|
||||
Reference in New Issue
Block a user