feat: scaffold independent workbench service
This commit is contained in:
@@ -0,0 +1,474 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.118im.com/im_team/im_workbench/internal/store"
|
||||
)
|
||||
|
||||
func workbenchModulePayloads(modules []store.WorkbenchModule) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(modules))
|
||||
for _, module := range modules {
|
||||
items = append(items, map[string]any{
|
||||
"code": module.Code,
|
||||
"name": module.Name,
|
||||
"icon_file_id": module.IconFileID,
|
||||
"route": module.Route,
|
||||
"sort_order": module.SortOrder,
|
||||
"unread_count": module.UnreadCount,
|
||||
"enabled": module.Enabled,
|
||||
"permission": firstNonEmpty(module.Permission, "enabled"),
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func organizationDepartmentPayloads(departments []store.OrganizationDepartment) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(departments))
|
||||
for _, department := range departments {
|
||||
items = append(items, organizationDepartmentPayload(department))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func organizationDepartmentPayload(department store.OrganizationDepartment) map[string]any {
|
||||
return map[string]any{
|
||||
"department_id": department.DepartmentID,
|
||||
"name": department.Name,
|
||||
"parent_id": department.ParentID,
|
||||
"child_count": department.ChildCount,
|
||||
"member_count": department.MemberCount,
|
||||
"version": department.Version,
|
||||
}
|
||||
}
|
||||
|
||||
func organizationMemberPayloads(members []store.OrganizationMember) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(members))
|
||||
for _, member := range members {
|
||||
items = append(items, organizationMemberPayload(member))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func organizationMemberPayload(member store.OrganizationMember) map[string]any {
|
||||
payload := map[string]any{
|
||||
"user_id": member.UserID,
|
||||
"department_id": member.DepartmentID,
|
||||
"display_name": member.DisplayName,
|
||||
"position": member.Position,
|
||||
"avatar_file_id": member.AvatarFileID,
|
||||
"status": member.Status,
|
||||
"contactable": member.Contactable,
|
||||
"version": member.Version,
|
||||
}
|
||||
if member.Mobile != "" {
|
||||
payload["mobile"] = member.Mobile
|
||||
}
|
||||
if member.Email != "" {
|
||||
payload["email"] = member.Email
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func attendanceConfigPayload(config *store.AttendanceConfig) map[string]any {
|
||||
return map[string]any{
|
||||
"date": config.Date.Format("2006-01-02"),
|
||||
"timezone": config.Timezone,
|
||||
"rule": attendanceRulePayload(config.Rule),
|
||||
"shifts": attendanceShiftPayloads(config.Shifts),
|
||||
"allowed_actions": []string{"check_in", "check_out"},
|
||||
"today_record": attendanceRecordPayload(config.TodayRecord),
|
||||
"permissions": map[string]any{
|
||||
"can_checkin": true,
|
||||
"can_view_managed": true,
|
||||
"can_appeal": config.Rule.AppealAllowed,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func attendanceRulePayload(rule store.AttendanceRule) map[string]any {
|
||||
data := map[string]any{
|
||||
"rule_id": rule.RuleID,
|
||||
"name": rule.Name,
|
||||
"workday": rule.Workday,
|
||||
"location_required": rule.LocationRequired,
|
||||
"wifi_required": rule.WifiRequired,
|
||||
"allow_outside_checkin": rule.AllowOutsideCheckin,
|
||||
"appeal_allowed": rule.AppealAllowed,
|
||||
}
|
||||
if rule.AppealDeadline != nil {
|
||||
data["appeal_deadline"] = rule.AppealDeadline.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func attendanceShiftPayloads(shifts []store.AttendanceShift) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(shifts))
|
||||
for _, shift := range shifts {
|
||||
items = append(items, map[string]any{
|
||||
"shift_id": shift.ShiftID,
|
||||
"name": shift.Name,
|
||||
"work_start": shift.WorkStart,
|
||||
"work_end": shift.WorkEnd,
|
||||
"checkin_start": shift.CheckinStart,
|
||||
"checkout_end": shift.CheckoutEnd,
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func attendanceRecordPayloads(records []store.AttendanceRecord) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(records))
|
||||
for i := range records {
|
||||
items = append(items, attendanceRecordPayload(&records[i]))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func attendanceRecordPayload(record *store.AttendanceRecord) map[string]any {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"record_id": record.RecordID,
|
||||
"user_id": record.UserID,
|
||||
"date": record.Date.Format("2006-01-02"),
|
||||
"rule_id": record.RuleID,
|
||||
"shift_id": record.ShiftID,
|
||||
"status": record.Status,
|
||||
"checkins": attendanceCheckinPayloads(record.Checkins),
|
||||
"exceptions": jsonList(record.Exceptions),
|
||||
"appeal_status": record.AppealStatus,
|
||||
"version": record.Version,
|
||||
"created_at": record.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": record.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
|
||||
func attendanceCheckinPayloads(checkins []store.AttendanceCheckin) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(checkins))
|
||||
for _, checkin := range checkins {
|
||||
items = append(items, attendanceCheckinPayload(checkin))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func attendanceCheckinPayload(checkin store.AttendanceCheckin) map[string]any {
|
||||
return map[string]any{
|
||||
"checkin_id": checkin.CheckinID,
|
||||
"action": checkin.Action,
|
||||
"client_time": checkin.ClientTime.UTC().Format(time.RFC3339Nano),
|
||||
"server_time": checkin.ServerTime.UTC().Format(time.RFC3339Nano),
|
||||
"result": checkin.Result,
|
||||
"location": checkin.Location,
|
||||
"wifi": checkin.Wifi,
|
||||
"device": checkin.Device,
|
||||
}
|
||||
}
|
||||
|
||||
func attendanceStatsPayload(stats *store.AttendanceStats) map[string]any {
|
||||
if stats == nil {
|
||||
return map[string]any{"expected_count": 0, "checked_count": 0, "exception_count": 0, "not_checked_count": 0}
|
||||
}
|
||||
return map[string]any{
|
||||
"expected_count": stats.ExpectedCount,
|
||||
"checked_count": stats.CheckedCount,
|
||||
"exception_count": stats.ExceptionCount,
|
||||
"not_checked_count": stats.NotCheckedCount,
|
||||
}
|
||||
}
|
||||
|
||||
func attendanceAppealPayloads(appeals []store.AttendanceAppeal) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(appeals))
|
||||
for i := range appeals {
|
||||
items = append(items, attendanceAppealPayload(&appeals[i]))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func attendanceAppealPayload(appeal *store.AttendanceAppeal) map[string]any {
|
||||
if appeal == nil {
|
||||
return nil
|
||||
}
|
||||
data := map[string]any{
|
||||
"appeal_id": appeal.AppealID,
|
||||
"record_id": appeal.RecordID,
|
||||
"user_id": appeal.UserID,
|
||||
"appeal_type": appeal.AppealType,
|
||||
"reason": appeal.Reason,
|
||||
"target_action": appeal.TargetAction,
|
||||
"attachment_file_ids": jsonList(appeal.AttachmentFileIDs),
|
||||
"status": appeal.Status,
|
||||
"version": appeal.Version,
|
||||
"submitted_at": appeal.SubmittedAt.UTC().Format(time.RFC3339Nano),
|
||||
"created_at": appeal.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": appeal.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
if appeal.TargetTime != nil {
|
||||
data["target_time"] = appeal.TargetTime.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func meetingPayloads(meetings []store.Meeting, viewer int64) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(meetings))
|
||||
for _, meeting := range meetings {
|
||||
items = append(items, meetingPayload(meeting, viewer))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func meetingPayload(meeting store.Meeting, viewer int64) map[string]any {
|
||||
participants := meetingParticipantPayloads(meeting.Participants)
|
||||
recorderID := meeting.RecorderID
|
||||
if recorderID == 0 {
|
||||
recorderID = meeting.OrganizerID
|
||||
}
|
||||
data := map[string]any{
|
||||
"meeting_id": meeting.MeetingID,
|
||||
"title": meeting.Title,
|
||||
"organizer_id": meeting.OrganizerID,
|
||||
"organizer": userSummary(meeting.OrganizerID),
|
||||
"recorder_user_id": recorderID,
|
||||
"recorder": userSummary(recorderID),
|
||||
"start_at": meeting.StartAt.UTC().Format(time.RFC3339Nano),
|
||||
"end_at": meeting.EndAt.UTC().Format(time.RFC3339Nano),
|
||||
"meeting_time": timeRange(meeting.StartAt, meeting.EndAt),
|
||||
"time_range": timeRange(meeting.StartAt, meeting.EndAt),
|
||||
"location": meeting.Location,
|
||||
"state": meeting.State,
|
||||
"reminder_minutes": jsonList(meeting.ReminderMinutes),
|
||||
"version": meeting.Version,
|
||||
"participants": participants,
|
||||
"participant_count": len(participants),
|
||||
"participant_names": []string{},
|
||||
"permissions": meetingPermissions(meeting, viewer),
|
||||
"created_at": meeting.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": meeting.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
if meeting.CanceledAt != nil {
|
||||
data["canceled_at"] = meeting.CanceledAt.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
for _, participant := range meeting.Participants {
|
||||
if participant.UserID == viewer {
|
||||
data["my_response"] = participant.Response
|
||||
break
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func meetingParticipantPayloads(participants []store.MeetingParticipant) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(participants))
|
||||
for _, participant := range participants {
|
||||
item := map[string]any{"user_id": participant.UserID, "response": participant.Response}
|
||||
if participant.ResponseAt != nil {
|
||||
item["response_at"] = participant.ResponseAt.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func meetingNoteListPayloads(meetings []store.Meeting, viewer int64) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(meetings))
|
||||
for _, meeting := range meetings {
|
||||
item := meetingPayload(meeting, viewer)
|
||||
item["note_status"] = "empty"
|
||||
item["file_count"] = 0
|
||||
item["permissions"] = meetingNotePermissions(meeting, viewer)
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func meetingNotesDetailPayload(meeting store.Meeting, files []store.MeetingNoteFile, viewer int64) map[string]any {
|
||||
meetingData := meetingPayload(meeting, viewer)
|
||||
fileItems := make([]map[string]any, 0, len(files))
|
||||
for _, file := range files {
|
||||
fileItems = append(fileItems, meetingNoteFilePayload(file, viewer))
|
||||
}
|
||||
data := map[string]any{}
|
||||
for k, v := range meetingData {
|
||||
data[k] = v
|
||||
}
|
||||
data["meeting"] = meetingData
|
||||
data["note_status"] = "empty"
|
||||
if len(files) > 0 {
|
||||
data["note_status"] = "available"
|
||||
}
|
||||
data["file_count"] = len(files)
|
||||
data["files"] = fileItems
|
||||
data["permissions"] = meetingNotePermissions(meeting, viewer)
|
||||
return data
|
||||
}
|
||||
|
||||
func meetingNoteFilePayload(file store.MeetingNoteFile, viewer int64) map[string]any {
|
||||
return map[string]any{
|
||||
"note_file_id": file.NoteFileID,
|
||||
"meeting_id": file.MeetingID,
|
||||
"file_id": file.FileID,
|
||||
"file_name": file.FileName,
|
||||
"mime_type": file.MIMEType,
|
||||
"file_size": file.FileSize,
|
||||
"file_etag": file.FileETag,
|
||||
"status": file.Status,
|
||||
"preview_status": file.PreviewStatus,
|
||||
"preview_mode": file.PreviewMode,
|
||||
"preview_file_id": file.PreviewFileID,
|
||||
"uploader_user_id": file.UploaderID,
|
||||
"uploader": userSummary(file.UploaderID),
|
||||
"version": file.Version,
|
||||
"permissions": map[string]any{
|
||||
"can_preview": file.Status == "active",
|
||||
"can_download": file.Status == "active",
|
||||
"can_replace": file.Status == "active" && file.UploaderID == viewer,
|
||||
"can_delete": file.Status == "active" && file.UploaderID == viewer,
|
||||
},
|
||||
"created_at": file.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": file.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
|
||||
func dailyReportPayloads(reports []store.DailyReport) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(reports))
|
||||
for _, report := range reports {
|
||||
items = append(items, dailyReportPayload(report))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func dailyReportPayload(report store.DailyReport) map[string]any {
|
||||
data := map[string]any{
|
||||
"report_id": report.ReportID,
|
||||
"author_id": report.AuthorID,
|
||||
"report_date": report.ReportDate.Format("2006-01-02"),
|
||||
"content": report.Content,
|
||||
"content_short": preview(report.Content),
|
||||
"attachments": jsonList(report.Attachments),
|
||||
"status": report.Status,
|
||||
"read_state": report.ReadState,
|
||||
"version": report.Version,
|
||||
"draft_updated_at": report.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"created_at": report.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": report.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
if report.SubmittedAt != nil {
|
||||
data["submitted_at"] = report.SubmittedAt.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func monthlyReportPayloads(reports []store.MonthlyReport) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(reports))
|
||||
for _, report := range reports {
|
||||
items = append(items, monthlyReportPayload(report))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func monthlyReportPayload(report store.MonthlyReport) map[string]any {
|
||||
data := map[string]any{
|
||||
"report_id": report.ReportID,
|
||||
"author_id": report.AuthorID,
|
||||
"month": report.Month,
|
||||
"work_summary": report.WorkSummary,
|
||||
"next_plan": report.NextPlan,
|
||||
"attachments": jsonList(report.Attachments),
|
||||
"status": report.Status,
|
||||
"read_state": report.ReadState,
|
||||
"version": report.Version,
|
||||
"draft_updated_at": report.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"created_at": report.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": report.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
if report.SubmittedAt != nil {
|
||||
data["submitted_at"] = report.SubmittedAt.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func mailboxConfigPayload(config store.MailboxConfig) map[string]any {
|
||||
return map[string]any{
|
||||
"enabled": config.Enabled,
|
||||
"allow_anonymous": config.AllowAnonymous,
|
||||
"required_fields": jsonList(config.RequiredFields),
|
||||
"max_length": config.MaxLength,
|
||||
"attachment_policy": jsonObject(config.AttachmentPolicy),
|
||||
"notice": map[string]any{
|
||||
"title": config.NoticeTitle,
|
||||
"content": config.NoticeContent,
|
||||
"version": config.Version,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mailboxSubmissionPayload(submission store.MailboxSubmission) map[string]any {
|
||||
return map[string]any{
|
||||
"submission_id": submission.SubmissionID,
|
||||
"sender_id": submission.SenderID,
|
||||
"subject": submission.Subject,
|
||||
"anonymous": submission.Anonymous,
|
||||
"attachment_file_ids": jsonList(submission.AttachmentFileIDs),
|
||||
"notice_version": submission.NoticeVersion,
|
||||
"status": submission.Status,
|
||||
"submitted_at": submission.SubmittedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
|
||||
func meetingPermissions(meeting store.Meeting, viewer int64) map[string]any {
|
||||
isOrganizer := meeting.OrganizerID == viewer
|
||||
isScheduled := meeting.State == "scheduled"
|
||||
return map[string]any{
|
||||
"can_update": isOrganizer && isScheduled,
|
||||
"can_cancel": isOrganizer && isScheduled,
|
||||
"can_remind": isOrganizer && isScheduled,
|
||||
"can_respond": !isOrganizer && isScheduled,
|
||||
}
|
||||
}
|
||||
|
||||
func meetingNotePermissions(meeting store.Meeting, viewer int64) map[string]any {
|
||||
recorderID := meeting.RecorderID
|
||||
if recorderID == 0 {
|
||||
recorderID = meeting.OrganizerID
|
||||
}
|
||||
canWrite := recorderID == viewer || meeting.OrganizerID == viewer
|
||||
return map[string]any{"can_view": true, "can_upload": canWrite, "can_replace": canWrite, "can_delete": canWrite, "can_download": true}
|
||||
}
|
||||
|
||||
func userSummary(userID int64) map[string]any {
|
||||
return map[string]any{"user_id": userID, "display_name": userID}
|
||||
}
|
||||
|
||||
func jsonList(raw string) []any {
|
||||
var items []any
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &items); err != nil {
|
||||
return []any{}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func jsonObject(raw string) map[string]any {
|
||||
var item map[string]any
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &item); err != nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func preview(value string) string {
|
||||
runes := []rune(strings.TrimSpace(value))
|
||||
if len(runes) <= 40 {
|
||||
return string(runes)
|
||||
}
|
||||
return string(runes[:40])
|
||||
}
|
||||
|
||||
func timeRange(startAt, endAt time.Time) string {
|
||||
if startAt.Format("2006-01-02") == endAt.Format("2006-01-02") {
|
||||
return startAt.Format("2006-01-02 15:04") + "-" + endAt.Format("15:04")
|
||||
}
|
||||
return startAt.Format("2006-01-02 15:04") + "-" + endAt.Format("2006-01-02 15:04")
|
||||
}
|
||||
Reference in New Issue
Block a user