feat(schedule): add course table screens and navigation

Add complete schedule functionality including:
- Schedule screen with weekly course table view
- Course detail screen with transparent modal presentation
- New ScheduleStack navigator integrated into main tab bar
- Schedule service for API interactions
- Type definitions for course entities

Also includes bug fixes for group invite/request handlers
to include required groupId parameter.
This commit is contained in:
2026-03-12 08:38:14 +08:00
parent 21293644b8
commit 0a0cbacbcc
25 changed files with 3050 additions and 260 deletions

View File

@@ -38,12 +38,12 @@ func parseGroupID(c *gin.Context) string {
// parseUserIDFromPath 从路径参数获取用户IDUUID格式
func parseUserIDFromPath(c *gin.Context) string {
return c.Param("userId")
return c.Param("user_id")
}
// parseAnnouncementID 从路径参数获取公告ID
func parseAnnouncementID(c *gin.Context) string {
return c.Param("announcementId")
return c.Param("announcement_id")
}
// ==================== 群组管理 ====================
@@ -454,7 +454,7 @@ func (h *GroupHandler) GetMembers(c *gin.Context) {
// ==================== RESTful Action 端点 ====================
// HandleCreateGroup 创建群组
// POST /api/v1/groups/create
// POST /api/v1/groups
func (h *GroupHandler) HandleCreateGroup(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -478,7 +478,7 @@ func (h *GroupHandler) HandleCreateGroup(c *gin.Context) {
}
// HandleGetUserGroups 获取用户群组列表
// GET /api/v1/groups/list
// GET /api/v1/groups
func (h *GroupHandler) HandleGetUserGroups(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -499,7 +499,6 @@ func (h *GroupHandler) HandleGetUserGroups(c *gin.Context) {
}
// HandleGetMyMemberInfo 获取我在群组中的成员信息
// GET /api/v1/groups/get_my_info?group_id=xxx
// GET /api/v1/groups/:id/me
func (h *GroupHandler) HandleGetMyMemberInfo(c *gin.Context) {
userID := parseUserID(c)
@@ -551,7 +550,7 @@ func (h *GroupHandler) HandleGetMyMemberInfo(c *gin.Context) {
}
// HandleDissolveGroup 解散群组
// POST /api/v1/groups/dissolve
// DELETE /api/v1/groups/:id
func (h *GroupHandler) HandleDissolveGroup(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -559,18 +558,13 @@ func (h *GroupHandler) HandleDissolveGroup(c *gin.Context) {
return
}
var params dto.DissolveGroupParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if err := h.groupService.DissolveGroup(userID, params.GroupID); err != nil {
if err := h.groupService.DissolveGroup(userID, groupID); err != nil {
if err == service.ErrNotGroupOwner {
response.Forbidden(c, "只有群主可以解散群组")
return
@@ -587,7 +581,7 @@ func (h *GroupHandler) HandleDissolveGroup(c *gin.Context) {
}
// HandleTransferOwner 转让群主
// POST /api/v1/groups/transfer
// POST /api/v1/groups/:id/transfer
func (h *GroupHandler) HandleTransferOwner(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -595,22 +589,24 @@ func (h *GroupHandler) HandleTransferOwner(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.TransferOwnerParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if params.NewOwnerID == "" {
response.BadRequest(c, "new_owner_id is required")
return
}
if err := h.groupService.TransferOwner(userID, params.GroupID, params.NewOwnerID); err != nil {
if err := h.groupService.TransferOwner(userID, groupID, params.NewOwnerID); err != nil {
if err == service.ErrNotGroupOwner {
response.Forbidden(c, "只有群主可以转让群主")
return
@@ -631,7 +627,7 @@ func (h *GroupHandler) HandleTransferOwner(c *gin.Context) {
}
// HandleInviteMembers 邀请成员加入群组
// POST /api/v1/groups/invite_members
// POST /api/v1/groups/:id/invitations
func (h *GroupHandler) HandleInviteMembers(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -639,18 +635,19 @@ func (h *GroupHandler) HandleInviteMembers(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.InviteMembersParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if err := h.groupService.InviteMembers(userID, params.GroupID, params.MemberIDs); err != nil {
if err := h.groupService.InviteMembers(userID, groupID, params.MemberIDs); err != nil {
if err == service.ErrNotGroupMember {
response.Forbidden(c, "只有群成员可以邀请他人")
return
@@ -675,7 +672,7 @@ func (h *GroupHandler) HandleInviteMembers(c *gin.Context) {
}
// HandleJoinGroup 加入群组
// POST /api/v1/groups/join
// POST /api/v1/groups/:id/join-requests
func (h *GroupHandler) HandleJoinGroup(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -683,18 +680,13 @@ func (h *GroupHandler) HandleJoinGroup(c *gin.Context) {
return
}
var params dto.JoinGroupParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if err := h.groupService.JoinGroup(userID, params.GroupID); err != nil {
if err := h.groupService.JoinGroup(userID, groupID); err != nil {
if err == service.ErrJoinRequestPending {
response.SuccessWithMessage(c, "申请已提交,等待群主/管理员审批", nil)
return
@@ -723,7 +715,7 @@ func (h *GroupHandler) HandleJoinGroup(c *gin.Context) {
}
// HandleSetNickname 设置群内昵称
// POST /api/v1/groups/set_nickname
// PUT /api/v1/groups/:id/members/me/nickname
func (h *GroupHandler) HandleSetNickname(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -731,18 +723,19 @@ func (h *GroupHandler) HandleSetNickname(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.SetNicknameParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if err := h.groupService.SetMemberNickname(userID, params.GroupID, params.Nickname); err != nil {
if err := h.groupService.SetMemberNickname(userID, groupID, params.Nickname); err != nil {
if err == service.ErrNotGroupMember {
response.BadRequest(c, "不是群成员")
return
@@ -759,7 +752,7 @@ func (h *GroupHandler) HandleSetNickname(c *gin.Context) {
}
// HandleSetJoinType 设置加群方式
// POST /api/v1/groups/set_join_type
// PUT /api/v1/groups/:id/join-type
func (h *GroupHandler) HandleSetJoinType(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -767,18 +760,19 @@ func (h *GroupHandler) HandleSetJoinType(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.SetJoinTypeParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if err := h.groupService.SetJoinType(userID, params.GroupID, params.JoinType); err != nil {
if err := h.groupService.SetJoinType(userID, groupID, params.JoinType); err != nil {
if err == service.ErrNotGroupOwner {
response.Forbidden(c, "只有群主可以设置加群方式")
return
@@ -803,7 +797,7 @@ func (h *GroupHandler) HandleSetJoinType(c *gin.Context) {
}
// HandleCreateAnnouncement 创建群公告
// POST /api/v1/groups/create_announcement
// POST /api/v1/groups/:id/announcements
func (h *GroupHandler) HandleCreateAnnouncement(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -811,18 +805,19 @@ func (h *GroupHandler) HandleCreateAnnouncement(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.CreateAnnouncementParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
announcement, err := h.groupService.CreateAnnouncement(userID, params.GroupID, params.Content)
announcement, err := h.groupService.CreateAnnouncement(userID, groupID, params.Content)
if err != nil {
if err == service.ErrNotGroupAdmin {
response.Forbidden(c, "只有群主或管理员可以发布公告")
@@ -840,7 +835,6 @@ func (h *GroupHandler) HandleCreateAnnouncement(c *gin.Context) {
}
// HandleGetAnnouncements 获取群公告列表
// GET /api/v1/groups/get_announcements?group_id=xxx
// GET /api/v1/groups/:id/announcements
func (h *GroupHandler) HandleGetAnnouncements(c *gin.Context) {
userID := parseUserID(c)
@@ -872,7 +866,7 @@ func (h *GroupHandler) HandleGetAnnouncements(c *gin.Context) {
}
// HandleDeleteAnnouncement 删除群公告
// POST /api/v1/groups/delete_announcement
// DELETE /api/v1/groups/:id/announcements/:announcement_id
func (h *GroupHandler) HandleDeleteAnnouncement(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -880,22 +874,18 @@ func (h *GroupHandler) HandleDeleteAnnouncement(c *gin.Context) {
return
}
var params dto.DeleteAnnouncementParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if params.AnnouncementID == "" {
announcementID := parseAnnouncementID(c)
if announcementID == "" {
response.BadRequest(c, "announcement_id is required")
return
}
if err := h.groupService.DeleteAnnouncement(userID, params.AnnouncementID); err != nil {
if err := h.groupService.DeleteAnnouncement(userID, announcementID); err != nil {
if err == service.ErrNotGroupAdmin {
response.Forbidden(c, "只有群主或管理员可以删除公告")
return
@@ -1292,7 +1282,7 @@ func (h *GroupHandler) DeleteAnnouncement(c *gin.Context) {
// ==================== RESTful Action 端点 ====================
// HandleSetGroupKick 群组踢人
// POST /api/v1/groups/set_group_kick
// POST /api/v1/groups/:id/members/kick
func (h *GroupHandler) HandleSetGroupKick(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1300,23 +1290,25 @@ func (h *GroupHandler) HandleSetGroupKick(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.SetGroupKickParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if params.UserID == "" {
response.BadRequest(c, "user_id is required")
return
}
// 使用 RemoveMember 方法
err := h.groupService.RemoveMember(userID, params.GroupID, params.UserID)
err := h.groupService.RemoveMember(userID, groupID, params.UserID)
if err != nil {
if err == service.ErrNotGroupAdmin {
response.Forbidden(c, "只有群主或管理员可以移除成员")
@@ -1342,7 +1334,7 @@ func (h *GroupHandler) HandleSetGroupKick(c *gin.Context) {
}
// HandleSetGroupBan 群组单人禁言
// POST /api/v1/groups/set_group_ban
// POST /api/v1/groups/:id/members/ban
func (h *GroupHandler) HandleSetGroupBan(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1350,16 +1342,18 @@ func (h *GroupHandler) HandleSetGroupBan(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.SetGroupBanParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if params.UserID == "" {
response.BadRequest(c, "user_id is required")
return
@@ -1367,8 +1361,8 @@ func (h *GroupHandler) HandleSetGroupBan(c *gin.Context) {
// duration > 0 或 duration = -1 表示禁言duration = 0 表示解除禁言
muted := params.Duration != 0
log.Printf("[HandleSetGroupBan] 开始禁言操作: userID=%s, groupID=%s, targetUserID=%s, duration=%d, muted=%v", userID, params.GroupID, params.UserID, params.Duration, muted)
err := h.groupService.MuteMember(userID, params.GroupID, params.UserID, muted)
log.Printf("[HandleSetGroupBan] 开始禁言操作: userID=%s, groupID=%s, targetUserID=%s, duration=%d, muted=%v", userID, groupID, params.UserID, params.Duration, muted)
err := h.groupService.MuteMember(userID, groupID, params.UserID, muted)
if err != nil {
log.Printf("[HandleSetGroupBan] 禁言操作失败: %v", err)
} else {
@@ -1403,7 +1397,7 @@ func (h *GroupHandler) HandleSetGroupBan(c *gin.Context) {
}
// HandleSetGroupWholeBan 群组全员禁言
// POST /api/v1/groups/set_group_whole_ban
// PUT /api/v1/groups/:id/ban
func (h *GroupHandler) HandleSetGroupWholeBan(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1411,18 +1405,19 @@ func (h *GroupHandler) HandleSetGroupWholeBan(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.SetGroupWholeBanParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
err := h.groupService.SetMuteAll(userID, params.GroupID, params.Enable)
err := h.groupService.SetMuteAll(userID, groupID, params.Enable)
if err != nil {
if err == service.ErrNotGroupOwner {
response.Forbidden(c, "只有群主可以设置全员禁言")
@@ -1444,7 +1439,7 @@ func (h *GroupHandler) HandleSetGroupWholeBan(c *gin.Context) {
}
// HandleSetGroupAdmin 群组设置管理员
// POST /api/v1/groups/set_group_admin
// PUT /api/v1/groups/:id/members/:user_id/admin
func (h *GroupHandler) HandleSetGroupAdmin(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1452,28 +1447,30 @@ func (h *GroupHandler) HandleSetGroupAdmin(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
targetUserID := parseUserIDFromPath(c)
if targetUserID == "" {
response.BadRequest(c, "user_id is required")
return
}
var params dto.SetGroupAdminParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if params.UserID == "" {
response.BadRequest(c, "user_id is required")
return
}
// 根据 enable 参数设置角色
role := model.GroupRoleMember
if params.Enable {
role = model.GroupRoleAdmin
}
err := h.groupService.SetMemberRole(userID, params.GroupID, params.UserID, role)
err := h.groupService.SetMemberRole(userID, groupID, targetUserID, role)
if err != nil {
if err == service.ErrNotGroupOwner {
response.Forbidden(c, "只有群主可以设置管理员")
@@ -1499,7 +1496,7 @@ func (h *GroupHandler) HandleSetGroupAdmin(c *gin.Context) {
}
// HandleSetGroupName 设置群名
// POST /api/v1/groups/set_group_name
// PUT /api/v1/groups/:id/name
func (h *GroupHandler) HandleSetGroupName(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1507,16 +1504,18 @@ func (h *GroupHandler) HandleSetGroupName(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.SetGroupNameParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if params.GroupName == "" {
response.BadRequest(c, "group_name is required")
return
@@ -1526,7 +1525,7 @@ func (h *GroupHandler) HandleSetGroupName(c *gin.Context) {
"name": params.GroupName,
}
err := h.groupService.UpdateGroup(userID, params.GroupID, updates)
err := h.groupService.UpdateGroup(userID, groupID, updates)
if err != nil {
if err == service.ErrNotGroupAdmin {
response.Forbidden(c, "没有权限修改群组信息")
@@ -1541,12 +1540,12 @@ func (h *GroupHandler) HandleSetGroupName(c *gin.Context) {
}
// 获取更新后的群组信息
group, _ := h.groupService.GetGroupByID(params.GroupID)
group, _ := h.groupService.GetGroupByID(groupID)
response.Success(c, dto.GroupToResponse(group))
}
// HandleSetGroupAvatar 设置群头像
// POST /api/v1/groups/set_group_avatar
// PUT /api/v1/groups/:id/avatar
func (h *GroupHandler) HandleSetGroupAvatar(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1554,16 +1553,18 @@ func (h *GroupHandler) HandleSetGroupAvatar(c *gin.Context) {
return
}
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
var params dto.SetGroupAvatarParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
response.BadRequest(c, "group_id is required")
return
}
if params.Avatar == "" {
response.BadRequest(c, "avatar is required")
return
@@ -1573,7 +1574,7 @@ func (h *GroupHandler) HandleSetGroupAvatar(c *gin.Context) {
"avatar": params.Avatar,
}
err := h.groupService.UpdateGroup(userID, params.GroupID, updates)
err := h.groupService.UpdateGroup(userID, groupID, updates)
if err != nil {
if err == service.ErrNotGroupAdmin {
response.Forbidden(c, "没有权限修改群组信息")
@@ -1588,12 +1589,12 @@ func (h *GroupHandler) HandleSetGroupAvatar(c *gin.Context) {
}
// 获取更新后的群组信息
group, _ := h.groupService.GetGroupByID(params.GroupID)
group, _ := h.groupService.GetGroupByID(groupID)
response.Success(c, dto.GroupToResponse(group))
}
// HandleSetGroupLeave 退出群组
// POST /api/v1/groups/set_group_leave
// POST /api/v1/groups/:id/leave
func (h *GroupHandler) HandleSetGroupLeave(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1601,18 +1602,13 @@ func (h *GroupHandler) HandleSetGroupLeave(c *gin.Context) {
return
}
var params dto.SetGroupLeaveParams
if err := c.ShouldBindJSON(&params); err != nil {
response.BadRequest(c, err.Error())
return
}
if params.GroupID == "" {
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
}
err := h.groupService.LeaveGroup(userID, params.GroupID)
err := h.groupService.LeaveGroup(userID, groupID)
if err != nil {
if err == service.ErrNotGroupMember {
response.BadRequest(c, "不是群成员")
@@ -1630,7 +1626,7 @@ func (h *GroupHandler) HandleSetGroupLeave(c *gin.Context) {
}
// HandleSetGroupAddRequest 处理加群审批
// POST /api/v1/groups/set_group_add_request
// POST /api/v1/groups/:id/join-requests/handle
func (h *GroupHandler) HandleSetGroupAddRequest(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1678,7 +1674,7 @@ func (h *GroupHandler) HandleSetGroupAddRequest(c *gin.Context) {
}
// HandleRespondInvite 处理群邀请响应
// POST /api/v1/groups/respond_invite
// POST /api/v1/groups/:id/join-requests/respond
func (h *GroupHandler) HandleRespondInvite(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1725,7 +1721,6 @@ func (h *GroupHandler) HandleRespondInvite(c *gin.Context) {
}
// HandleGetGroupInfo 获取群信息
// GET /api/v1/groups/get?group_id=xxx
// GET /api/v1/groups/:id
func (h *GroupHandler) HandleGetGroupInfo(c *gin.Context) {
userID := parseUserID(c)
@@ -1761,7 +1756,6 @@ func (h *GroupHandler) HandleGetGroupInfo(c *gin.Context) {
}
// HandleGetGroupMemberList 获取群成员列表
// GET /api/v1/groups/get_members?group_id=xxx
// GET /api/v1/groups/:id/members
func (h *GroupHandler) HandleGetGroupMemberList(c *gin.Context) {
userID := parseUserID(c)