feat: add workbench and client settings APIs
This commit is contained in:
@@ -292,6 +292,39 @@ type TenantBusinessAdapter interface {
|
||||
TenantCallCreate(t.TenantID, *t.CallRecord) error
|
||||
TenantCallAccept(t.TenantID, string, int, time.Time) error
|
||||
TenantCallFinish(t.TenantID, string, int, string, int64, time.Time) error
|
||||
TenantCallList(t.TenantID, t.Uid, string, string, int) ([]t.CallRecord, string, error)
|
||||
|
||||
TenantWorkbenchModules(t.TenantID, t.Uid) ([]t.WorkbenchModule, int, error)
|
||||
TenantOrganizationDepartments(t.TenantID, t.Uid, string, string, int) ([]t.OrganizationDepartment, string, error)
|
||||
TenantOrganizationMembers(t.TenantID, t.Uid, string, string, int) ([]t.OrganizationMember, string, error)
|
||||
TenantOrganizationSearch(t.TenantID, t.Uid, string, string, string, int) ([]t.OrganizationSearchItem, string, error)
|
||||
TenantMeetingCreate(t.TenantID, t.Uid, *t.Meeting, []t.Uid) (*t.Meeting, error)
|
||||
TenantMeetingGet(t.TenantID, t.Uid, string) (*t.Meeting, error)
|
||||
TenantMeetingUpdate(t.TenantID, t.Uid, string, t.MeetingPatch, string) (*t.Meeting, error)
|
||||
TenantMeetingCancel(t.TenantID, t.Uid, string, string) (*t.Meeting, error)
|
||||
TenantMeetingRemind(t.TenantID, t.Uid, string, string) ([]t.MeetingParticipant, error)
|
||||
TenantMeetingRespond(t.TenantID, t.Uid, string, string, int) (*t.Meeting, error)
|
||||
TenantDailyReportList(t.TenantID, t.Uid, string, string, string, string, string, int) ([]t.DailyReport, string, error)
|
||||
TenantDailyReportCreateDraft(t.TenantID, t.Uid, string, time.Time) (*t.DailyReport, error)
|
||||
TenantDailyReportGet(t.TenantID, t.Uid, string) (*t.DailyReport, error)
|
||||
TenantDailyReportSaveDraft(t.TenantID, t.Uid, string, string, []string, int) (*t.DailyReport, error)
|
||||
TenantDailyReportSubmit(t.TenantID, t.Uid, string, int) (*t.DailyReport, error)
|
||||
TenantDailyReportStats(t.TenantID, t.Uid, time.Time, string) (*t.DailyReportStats, error)
|
||||
TenantDailyReportRemind(t.TenantID, t.Uid, time.Time, []t.Uid, string) ([]t.Uid, error)
|
||||
TenantMonthlyReportList(t.TenantID, t.Uid, string, string, string, string, string, int) ([]t.MonthlyReport, string, error)
|
||||
TenantMonthlyReportCreateDraft(t.TenantID, t.Uid, string, string) (*t.MonthlyReport, error)
|
||||
TenantMonthlyReportGet(t.TenantID, t.Uid, string) (*t.MonthlyReport, error)
|
||||
TenantMonthlyReportSaveDraft(t.TenantID, t.Uid, string, string, string, []string, int) (*t.MonthlyReport, error)
|
||||
TenantMonthlyReportSubmit(t.TenantID, t.Uid, string, int) (*t.MonthlyReport, error)
|
||||
TenantMonthlyReportStats(t.TenantID, t.Uid, string, string) (*t.MonthlyReportStats, error)
|
||||
TenantMailboxConfigGet(t.TenantID, t.Uid) (*t.MailboxConfig, error)
|
||||
TenantMailboxSubmissionCreate(t.TenantID, t.Uid, *t.MailboxSubmission, string) (*t.MailboxSubmission, error)
|
||||
TenantUserProfileGet(t.TenantID, t.Uid) (*t.UserProfile, error)
|
||||
TenantUserProfileUpdate(t.TenantID, t.Uid, t.UserProfilePatch) (*t.UserProfile, error)
|
||||
TenantUserPrivacyGet(t.TenantID, t.Uid) (*t.UserPrivacySettings, error)
|
||||
TenantUserPrivacyUpdate(t.TenantID, t.Uid, t.UserPrivacyPatch) (*t.UserPrivacySettings, error)
|
||||
TenantAppReleaseLatest(t.TenantID, string, string) (*t.AppRelease, error)
|
||||
TenantDevicePushTokenUpsert(t.TenantID, *t.DevicePushToken) (*t.DevicePushToken, error)
|
||||
|
||||
TenantFavoriteCreate(t.TenantID, *t.FavoriteItem) (*t.FavoriteItem, error)
|
||||
TenantFavoriteList(t.TenantID, t.Uid, string, string, string, int) ([]t.FavoriteItem, string, error)
|
||||
@@ -330,6 +363,8 @@ type TenantBusinessAdapter interface {
|
||||
TenantIndependentPasswordGet(t.TenantID, t.Uid, string) (*t.IndependentPassword, error)
|
||||
TenantIndependentPasswordUpsert(t.TenantID, *t.IndependentPassword) error
|
||||
TenantIndependentPasswordUpdateAttempts(t.TenantID, t.Uid, string, int, *time.Time, time.Time) error
|
||||
TenantYouthModeGet(t.TenantID, t.Uid) (*t.YouthModeSettings, error)
|
||||
TenantYouthModeSet(t.TenantID, t.Uid, bool, int, time.Time) (*t.YouthModeSettings, error)
|
||||
|
||||
TenantSystemNotificationCreate(t.TenantID, *t.SystemNotification) error
|
||||
TenantSystemNotificationList(t.TenantID, t.Uid, string, int) ([]t.SystemNotification, string, error)
|
||||
|
||||
@@ -794,6 +794,24 @@ func (a *adapter) CreateDb(reset bool) error {
|
||||
if err = createIMCallTables(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = createIMWorkbenchTables(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = createIMOrganizationTables(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = createIMMeetingTables(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = createIMReportTables(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = createIMMailboxTables(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = createIMUserProductTables(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deletion log
|
||||
if _, err = tx.Exec(
|
||||
@@ -950,6 +968,299 @@ func createIMCallTables(tx *sql.Tx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func createIMWorkbenchTables(tx *sql.Tx) error {
|
||||
if _, err := tx.Exec(
|
||||
`CREATE TABLE im_workbench_apps(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
icon_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
route VARCHAR(128) NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
unread_key VARCHAR(64) NOT NULL DEFAULT '',
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
permission VARCHAR(64) NOT NULL DEFAULT 'enabled',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_workbench_apps_code(code),
|
||||
INDEX idx_im_workbench_apps_enabled_order(enabled, sort_order)
|
||||
)`); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := tx.Exec(
|
||||
`INSERT INTO im_workbench_apps(code,name,route,sort_order,unread_key,enabled,permission)
|
||||
VALUES
|
||||
('organization','组织架构','/organization',10,'organization',1,'enabled'),
|
||||
('meetings','会议通知','/meetings',20,'meetings',1,'enabled'),
|
||||
('daily_reports','日报','/reports/daily',30,'daily_reports',1,'enabled'),
|
||||
('monthly_reports','月报','/reports/monthly',40,'monthly_reports',1,'enabled'),
|
||||
('executive_mailbox','总经理信箱','/executive-mailbox',50,'executive_mailbox',1,'enabled')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name=VALUES(name),
|
||||
route=VALUES(route),
|
||||
sort_order=VALUES(sort_order),
|
||||
unread_key=VALUES(unread_key),
|
||||
updated_at=CURRENT_TIMESTAMP(3)`)
|
||||
return err
|
||||
}
|
||||
|
||||
func createIMOrganizationTables(tx *sql.Tx) error {
|
||||
statements := []string{
|
||||
`CREATE TABLE im_org_departments(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
department_id VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
parent_id VARCHAR(64) NOT NULL DEFAULT 'root',
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
state VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_org_departments_department(department_id),
|
||||
INDEX idx_im_org_departments_parent(parent_id, state, sort_order, id),
|
||||
INDEX idx_im_org_departments_name(name)
|
||||
)`,
|
||||
`CREATE TABLE im_org_members(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
department_id VARCHAR(64) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
display_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
position VARCHAR(128) NOT NULL DEFAULT '',
|
||||
avatar_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
mobile VARCHAR(32) NOT NULL DEFAULT '',
|
||||
email VARCHAR(128) NOT NULL DEFAULT '',
|
||||
employment_state VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
contactable TINYINT NOT NULL DEFAULT 1,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_org_members_department_user(department_id, user_id),
|
||||
INDEX idx_im_org_members_department(department_id, employment_state, sort_order, id),
|
||||
INDEX idx_im_org_members_user(user_id),
|
||||
INDEX idx_im_org_members_display_name(display_name),
|
||||
CONSTRAINT fk_im_org_members_department FOREIGN KEY(department_id) REFERENCES im_org_departments(department_id) ON DELETE CASCADE
|
||||
)`,
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if _, err := tx.Exec(stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createIMMeetingTables(tx *sql.Tx) error {
|
||||
statements := []string{
|
||||
`CREATE TABLE im_meetings(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
meeting_id VARCHAR(64) NOT NULL,
|
||||
organizer_id BIGINT NOT NULL,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
start_at DATETIME(3) NOT NULL,
|
||||
end_at DATETIME(3) NOT NULL,
|
||||
location VARCHAR(256) NOT NULL DEFAULT '',
|
||||
state VARCHAR(16) NOT NULL DEFAULT 'scheduled',
|
||||
reminder_minutes JSON,
|
||||
idempotency_key VARCHAR(128),
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
canceled_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_meetings_meeting(meeting_id),
|
||||
UNIQUE INDEX uk_im_meetings_idempotency(organizer_id, idempotency_key),
|
||||
INDEX idx_im_meetings_organizer(organizer_id, start_at),
|
||||
INDEX idx_im_meetings_state_time(state, start_at)
|
||||
)`,
|
||||
`CREATE TABLE im_meeting_participants(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
meeting_id VARCHAR(64) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
response VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||||
response_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_meeting_participants_user(meeting_id, user_id),
|
||||
INDEX idx_im_meeting_participants_user_time(user_id, meeting_id),
|
||||
INDEX idx_im_meeting_participants_response(meeting_id, response),
|
||||
CONSTRAINT fk_im_meeting_participants_meeting FOREIGN KEY(meeting_id) REFERENCES im_meetings(meeting_id) ON DELETE CASCADE
|
||||
)`,
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if _, err := tx.Exec(stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createIMReportTables(tx *sql.Tx) error {
|
||||
if _, err := tx.Exec(
|
||||
`CREATE TABLE im_daily_reports(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
report_id VARCHAR(64) NOT NULL,
|
||||
author_id BIGINT NOT NULL,
|
||||
report_date DATE NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
attachments JSON,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'draft',
|
||||
read_state VARCHAR(16) NOT NULL DEFAULT 'unread',
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
submitted_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_daily_reports_report(report_id),
|
||||
UNIQUE INDEX uk_im_daily_reports_author_date(author_id, report_date),
|
||||
INDEX idx_im_daily_reports_author_id(author_id, id DESC),
|
||||
INDEX idx_im_daily_reports_date_status(report_date, status, id DESC)
|
||||
)`); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := tx.Exec(
|
||||
`CREATE TABLE im_monthly_reports(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
report_id VARCHAR(64) NOT NULL,
|
||||
author_id BIGINT NOT NULL,
|
||||
report_month CHAR(7) NOT NULL,
|
||||
work_summary TEXT NOT NULL,
|
||||
next_plan TEXT NOT NULL,
|
||||
attachments JSON,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'draft',
|
||||
read_state VARCHAR(16) NOT NULL DEFAULT 'unread',
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
submitted_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_monthly_reports_report(report_id),
|
||||
UNIQUE INDEX uk_im_monthly_reports_author_month(author_id, report_month),
|
||||
INDEX idx_im_monthly_reports_author_id(author_id, id DESC),
|
||||
INDEX idx_im_monthly_reports_month_status(report_month, status, id DESC)
|
||||
)`)
|
||||
return err
|
||||
}
|
||||
|
||||
func createIMMailboxTables(tx *sql.Tx) error {
|
||||
if _, err := tx.Exec(
|
||||
`CREATE TABLE im_mailbox_config(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
allow_anonymous TINYINT NOT NULL DEFAULT 1,
|
||||
required_fields JSON,
|
||||
max_length INT NOT NULL DEFAULT 5000,
|
||||
attachment_policy JSON,
|
||||
notice_title VARCHAR(128) NOT NULL DEFAULT '',
|
||||
notice_content TEXT NOT NULL,
|
||||
notice_updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id)
|
||||
)`); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO im_mailbox_config(id,enabled,allow_anonymous,required_fields,max_length,attachment_policy,notice_title,notice_content)
|
||||
VALUES(1,1,1,'["subject","content"]',5000,'{"max_files":5,"purpose":"executive_mailbox"}','总经理信箱','请如实填写诉求,匿名提交将按企业政策保护展示身份。')
|
||||
ON DUPLICATE KEY UPDATE updated_at=updated_at`); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := tx.Exec(
|
||||
`CREATE TABLE im_mailbox_submissions(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
submission_id VARCHAR(64) NOT NULL,
|
||||
sender_id BIGINT NOT NULL,
|
||||
subject VARCHAR(200) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
anonymous TINYINT NOT NULL DEFAULT 0,
|
||||
attachment_file_ids JSON,
|
||||
notice_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
idempotency_key VARCHAR(128) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'received',
|
||||
submitted_at DATETIME(3) NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_mailbox_submissions_submission(submission_id),
|
||||
UNIQUE INDEX uk_im_mailbox_submissions_idempotency(sender_id, idempotency_key),
|
||||
INDEX idx_im_mailbox_submissions_sender(sender_id, id DESC),
|
||||
INDEX idx_im_mailbox_submissions_status(status, id DESC)
|
||||
)`)
|
||||
return err
|
||||
}
|
||||
|
||||
func createIMUserProductTables(tx *sql.Tx) error {
|
||||
statements := []string{
|
||||
`CREATE TABLE im_user_profiles(
|
||||
user_id BIGINT NOT NULL,
|
||||
avatar_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
nickname VARCHAR(64) NOT NULL DEFAULT '',
|
||||
birth_date DATE,
|
||||
signature VARCHAR(256) NOT NULL DEFAULT '',
|
||||
gender VARCHAR(16) NOT NULL DEFAULT '',
|
||||
region VARCHAR(128) NOT NULL DEFAULT '',
|
||||
public_account VARCHAR(64) NOT NULL DEFAULT '',
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(user_id)
|
||||
)`,
|
||||
`CREATE TABLE im_user_privacy_settings(
|
||||
user_id BIGINT NOT NULL,
|
||||
add_me_methods JSON,
|
||||
profile_visibility VARCHAR(32) NOT NULL DEFAULT 'friends',
|
||||
last_seen_visibility VARCHAR(32) NOT NULL DEFAULT 'friends',
|
||||
read_receipts TINYINT NOT NULL DEFAULT 1,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(user_id)
|
||||
)`,
|
||||
`CREATE TABLE im_app_releases(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
platform VARCHAR(32) NOT NULL,
|
||||
channel VARCHAR(32) NOT NULL DEFAULT 'stable',
|
||||
latest_version VARCHAR(64) NOT NULL,
|
||||
min_supported_version VARCHAR(64) NOT NULL,
|
||||
update_type VARCHAR(16) NOT NULL DEFAULT 'none',
|
||||
release_notes TEXT NOT NULL,
|
||||
store_url VARCHAR(512) NOT NULL DEFAULT '',
|
||||
package_url VARCHAR(512) NOT NULL DEFAULT '',
|
||||
checksum VARCHAR(128) NOT NULL DEFAULT '',
|
||||
remind_after_seconds INT NOT NULL DEFAULT 0,
|
||||
published_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_app_releases_platform_channel(platform, channel)
|
||||
)`,
|
||||
`CREATE TABLE im_device_push_tokens(
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
provider VARCHAR(32) NOT NULL,
|
||||
token VARCHAR(512) NOT NULL,
|
||||
app_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
platform VARCHAR(32) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(device_id),
|
||||
UNIQUE INDEX uk_im_device_push_tokens_provider_token(provider, token),
|
||||
INDEX idx_im_device_push_tokens_user(user_id, updated_at)
|
||||
)`,
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if _, err := tx.Exec(stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureTenantRoutingColumns(a *adapter) error {
|
||||
statements := []string{
|
||||
"ALTER TABLE im_tenant ADD COLUMN db_name VARCHAR(128) NOT NULL DEFAULT '' AFTER tenant_desc",
|
||||
@@ -1117,6 +1428,10 @@ func createIMSecurityTables(tx *sql.Tx) error {
|
||||
user_id BIGINT NOT NULL,
|
||||
purpose VARCHAR(64) NOT NULL,
|
||||
password_hash VARBINARY(255) NOT NULL,
|
||||
enabled TINYINT NOT NULL DEFAULT 0,
|
||||
policy_version INT NOT NULL DEFAULT 1,
|
||||
restrictions JSON,
|
||||
enabled_at DATETIME(3),
|
||||
failed_attempts INT NOT NULL DEFAULT 0,
|
||||
locked_until DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1583
File diff suppressed because it is too large
Load Diff
+975
-3
File diff suppressed because it is too large
Load Diff
@@ -788,13 +788,37 @@ func main() {
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/security/realname", serveIMRealName)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/security/realname/submissions", serveIMRealNameSubmissions)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/bootstrap", serveIMBootstrap)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/workbench", serveIMWorkbench)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/organization/departments", serveIMOrganizationDepartments)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/organization/departments/", serveIMOrganizationDepartmentResource)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/organization/search", serveIMOrganizationSearch)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/meetings", serveIMMeetings)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/meetings/", serveIMMeetingResource)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/reports/daily/statistics", serveIMDailyReportStatistics)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/reports/daily/reminders", serveIMDailyReportReminders)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/reports/daily", serveIMDailyReports)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/reports/daily/", serveIMDailyReportResource)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/reports/monthly/statistics", serveIMMonthlyReportStatistics)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/reports/monthly", serveIMMonthlyReports)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/reports/monthly/", serveIMMonthlyReportResource)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/executive-mailbox/config", serveIMMailboxConfig)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/executive-mailbox/submissions", serveIMMailboxSubmissions)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/app/releases/latest", serveIMAppReleaseLatest)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/me", serveIMMe)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/me/profile", serveIMMeProfile)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/me/privacy", serveIMMePrivacy)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/settings/youth-mode/enable", serveIMYouthModeEnable)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/settings/youth-mode/disable", serveIMYouthModeDisable)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/settings/youth-mode", serveIMYouthMode)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/settings/independent-password", serveIMIndependentPassword)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/settings/independent-password/verify", serveIMIndependentPasswordVerify)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/devices/", serveIMDevicePushToken)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/search/conversations", serveIMConversationSearch)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/system-notifications", serveIMSystemNotifications)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/system-notifications/", serveIMSystemNotificationRead)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/favorites", serveIMFavorites)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/favorites/", serveIMFavoriteResource)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/calls", serveIMCalls)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/topics/", serveIMTopicMessageAction)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/conversations/", serveIMConversationContext)
|
||||
mux.HandleFunc(config.ApiPath+"v1/im/groups", serveIMGroups)
|
||||
|
||||
@@ -1018,6 +1018,7 @@ type CallsPersistenceInterface interface {
|
||||
Create(tenantID types.TenantID, record *types.CallRecord) error
|
||||
Accept(tenantID types.TenantID, callID string, seq int, acceptedAt time.Time) error
|
||||
Finish(tenantID types.TenantID, callID string, seq int, status string, durationMS int64, finishedAt time.Time) error
|
||||
List(tenantID types.TenantID, uid types.Uid, topic, cursor string, limit int) ([]types.CallRecord, string, error)
|
||||
}
|
||||
|
||||
type callsMapper struct{}
|
||||
@@ -1064,6 +1065,521 @@ func (callsMapper) Finish(tenantID types.TenantID, callID string, seq int, statu
|
||||
return tenantAdp.TenantCallFinish(tenantID, callID, seq, status, durationMS, finishedAt)
|
||||
}
|
||||
|
||||
func (callsMapper) List(tenantID types.TenantID, uid types.Uid, topic, cursor string, limit int) ([]types.CallRecord, string, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, "", types.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return tenantAdp.TenantCallList(tenantID, uid, strings.TrimSpace(topic), strings.TrimSpace(cursor), limit)
|
||||
}
|
||||
|
||||
type WorkbenchPersistenceInterface interface {
|
||||
Get(tenantID types.TenantID, uid types.Uid) ([]types.WorkbenchModule, int, error)
|
||||
}
|
||||
|
||||
type workbenchMapper struct{}
|
||||
|
||||
var Workbench WorkbenchPersistenceInterface
|
||||
|
||||
func (workbenchMapper) Get(tenantID types.TenantID, uid types.Uid) ([]types.WorkbenchModule, int, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, 0, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return tenantAdp.TenantWorkbenchModules(tenantID, uid)
|
||||
}
|
||||
|
||||
type OrganizationPersistenceInterface interface {
|
||||
ListDepartments(tenantID types.TenantID, uid types.Uid, parentID, cursor string, limit int) ([]types.OrganizationDepartment, string, error)
|
||||
ListMembers(tenantID types.TenantID, uid types.Uid, departmentID, cursor string, limit int) ([]types.OrganizationMember, string, error)
|
||||
Search(tenantID types.TenantID, uid types.Uid, queryText, resultTypes, cursor string, limit int) ([]types.OrganizationSearchItem, string, error)
|
||||
}
|
||||
|
||||
type organizationMapper struct{}
|
||||
|
||||
var Organization OrganizationPersistenceInterface
|
||||
|
||||
func (organizationMapper) ListDepartments(tenantID types.TenantID, uid types.Uid, parentID, cursor string, limit int) ([]types.OrganizationDepartment, string, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, "", types.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return tenantAdp.TenantOrganizationDepartments(tenantID, uid, strings.TrimSpace(parentID), strings.TrimSpace(cursor), limit)
|
||||
}
|
||||
|
||||
func (organizationMapper) ListMembers(tenantID types.TenantID, uid types.Uid, departmentID, cursor string, limit int) ([]types.OrganizationMember, string, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(departmentID) == "" {
|
||||
return nil, "", types.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return tenantAdp.TenantOrganizationMembers(tenantID, uid, strings.TrimSpace(departmentID), strings.TrimSpace(cursor), limit)
|
||||
}
|
||||
|
||||
func (organizationMapper) Search(tenantID types.TenantID, uid types.Uid, queryText, resultTypes, cursor string, limit int) ([]types.OrganizationSearchItem, string, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(queryText) == "" {
|
||||
return nil, "", types.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return tenantAdp.TenantOrganizationSearch(tenantID, uid, strings.TrimSpace(queryText), strings.TrimSpace(resultTypes), strings.TrimSpace(cursor), limit)
|
||||
}
|
||||
|
||||
type MeetingsPersistenceInterface interface {
|
||||
Create(tenantID types.TenantID, uid types.Uid, meeting *types.Meeting, attendeeIDs []types.Uid) (*types.Meeting, error)
|
||||
Get(tenantID types.TenantID, uid types.Uid, meetingID string) (*types.Meeting, error)
|
||||
Update(tenantID types.TenantID, uid types.Uid, meetingID string, patch types.MeetingPatch, idempotencyKey string) (*types.Meeting, error)
|
||||
Cancel(tenantID types.TenantID, uid types.Uid, meetingID, idempotencyKey string) (*types.Meeting, error)
|
||||
Remind(tenantID types.TenantID, uid types.Uid, meetingID, idempotencyKey string) ([]types.MeetingParticipant, error)
|
||||
Respond(tenantID types.TenantID, uid types.Uid, meetingID, response string, version int) (*types.Meeting, error)
|
||||
}
|
||||
|
||||
type meetingsMapper struct{}
|
||||
|
||||
var Meetings MeetingsPersistenceInterface
|
||||
|
||||
func (meetingsMapper) Create(tenantID types.TenantID, uid types.Uid, meeting *types.Meeting, attendeeIDs []types.Uid) (*types.Meeting, error) {
|
||||
if uid.IsZero() || meeting == nil || !validMeetingForCreate(meeting) {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMeetingCreate(tenantID, uid, meeting, attendeeIDs)
|
||||
}
|
||||
|
||||
func (meetingsMapper) Get(tenantID types.TenantID, uid types.Uid, meetingID string) (*types.Meeting, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(meetingID) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMeetingGet(tenantID, uid, strings.TrimSpace(meetingID))
|
||||
}
|
||||
|
||||
func (meetingsMapper) Update(tenantID types.TenantID, uid types.Uid, meetingID string, patch types.MeetingPatch, idempotencyKey string) (*types.Meeting, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(meetingID) == "" || patch.Version <= 0 || !validMeetingPatch(patch) {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMeetingUpdate(tenantID, uid, strings.TrimSpace(meetingID), patch, strings.TrimSpace(idempotencyKey))
|
||||
}
|
||||
|
||||
func (meetingsMapper) Cancel(tenantID types.TenantID, uid types.Uid, meetingID, idempotencyKey string) (*types.Meeting, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(meetingID) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMeetingCancel(tenantID, uid, strings.TrimSpace(meetingID), strings.TrimSpace(idempotencyKey))
|
||||
}
|
||||
|
||||
func (meetingsMapper) Remind(tenantID types.TenantID, uid types.Uid, meetingID, idempotencyKey string) ([]types.MeetingParticipant, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(meetingID) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMeetingRemind(tenantID, uid, strings.TrimSpace(meetingID), strings.TrimSpace(idempotencyKey))
|
||||
}
|
||||
|
||||
func (meetingsMapper) Respond(tenantID types.TenantID, uid types.Uid, meetingID, response string, version int) (*types.Meeting, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(meetingID) == "" || !validMeetingResponse(response) || version <= 0 {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMeetingRespond(tenantID, uid, strings.TrimSpace(meetingID), strings.TrimSpace(response), version)
|
||||
}
|
||||
|
||||
func validMeetingForCreate(meeting *types.Meeting) bool {
|
||||
return strings.TrimSpace(meeting.MeetingID) != "" &&
|
||||
strings.TrimSpace(meeting.Title) != "" &&
|
||||
!meeting.StartAt.IsZero() &&
|
||||
!meeting.EndAt.IsZero() &&
|
||||
meeting.EndAt.After(meeting.StartAt)
|
||||
}
|
||||
|
||||
func validMeetingPatch(patch types.MeetingPatch) bool {
|
||||
if patch.Title == nil && patch.StartAt == nil && patch.EndAt == nil && patch.Location == nil && patch.ReminderMinutes == nil {
|
||||
return false
|
||||
}
|
||||
if patch.Title != nil && strings.TrimSpace(*patch.Title) == "" {
|
||||
return false
|
||||
}
|
||||
if patch.StartAt != nil && patch.EndAt != nil && !patch.EndAt.After(*patch.StartAt) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validMeetingResponse(response string) bool {
|
||||
switch strings.TrimSpace(response) {
|
||||
case types.MeetingResponseAccepted, types.MeetingResponseDeclined, types.MeetingResponseTentative:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type DailyReportsPersistenceInterface interface {
|
||||
List(tenantID types.TenantID, uid types.Uid, scope, from, to, status, cursor string, limit int) ([]types.DailyReport, string, error)
|
||||
CreateDraft(tenantID types.TenantID, uid types.Uid, reportID string, reportDate time.Time) (*types.DailyReport, error)
|
||||
Get(tenantID types.TenantID, uid types.Uid, reportID string) (*types.DailyReport, error)
|
||||
SaveDraft(tenantID types.TenantID, uid types.Uid, reportID, content string, attachments []string, version int) (*types.DailyReport, error)
|
||||
Submit(tenantID types.TenantID, uid types.Uid, reportID string, version int) (*types.DailyReport, error)
|
||||
Stats(tenantID types.TenantID, uid types.Uid, date time.Time, departmentID string) (*types.DailyReportStats, error)
|
||||
Remind(tenantID types.TenantID, uid types.Uid, date time.Time, userIDs []types.Uid, idempotencyKey string) ([]types.Uid, error)
|
||||
}
|
||||
|
||||
type dailyReportsMapper struct{}
|
||||
|
||||
var DailyReports DailyReportsPersistenceInterface
|
||||
|
||||
func (dailyReportsMapper) List(tenantID types.TenantID, uid types.Uid, scope, from, to, status, cursor string, limit int) ([]types.DailyReport, string, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, "", types.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return tenantAdp.TenantDailyReportList(tenantID, uid, strings.TrimSpace(scope), strings.TrimSpace(from), strings.TrimSpace(to), strings.TrimSpace(status), strings.TrimSpace(cursor), limit)
|
||||
}
|
||||
|
||||
func (dailyReportsMapper) CreateDraft(tenantID types.TenantID, uid types.Uid, reportID string, reportDate time.Time) (*types.DailyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" || reportDate.IsZero() {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantDailyReportCreateDraft(tenantID, uid, strings.TrimSpace(reportID), reportDate)
|
||||
}
|
||||
|
||||
func (dailyReportsMapper) Get(tenantID types.TenantID, uid types.Uid, reportID string) (*types.DailyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantDailyReportGet(tenantID, uid, strings.TrimSpace(reportID))
|
||||
}
|
||||
|
||||
func (dailyReportsMapper) SaveDraft(tenantID types.TenantID, uid types.Uid, reportID, content string, attachments []string, version int) (*types.DailyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" || version <= 0 {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantDailyReportSaveDraft(tenantID, uid, strings.TrimSpace(reportID), content, attachments, version)
|
||||
}
|
||||
|
||||
func (dailyReportsMapper) Submit(tenantID types.TenantID, uid types.Uid, reportID string, version int) (*types.DailyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" || version <= 0 {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantDailyReportSubmit(tenantID, uid, strings.TrimSpace(reportID), version)
|
||||
}
|
||||
|
||||
func (dailyReportsMapper) Stats(tenantID types.TenantID, uid types.Uid, date time.Time, departmentID string) (*types.DailyReportStats, error) {
|
||||
if uid.IsZero() || date.IsZero() {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantDailyReportStats(tenantID, uid, date, strings.TrimSpace(departmentID))
|
||||
}
|
||||
|
||||
func (dailyReportsMapper) Remind(tenantID types.TenantID, uid types.Uid, date time.Time, userIDs []types.Uid, idempotencyKey string) ([]types.Uid, error) {
|
||||
if uid.IsZero() || date.IsZero() || strings.TrimSpace(idempotencyKey) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantDailyReportRemind(tenantID, uid, date, userIDs, strings.TrimSpace(idempotencyKey))
|
||||
}
|
||||
|
||||
type MonthlyReportsPersistenceInterface interface {
|
||||
List(tenantID types.TenantID, uid types.Uid, scope, fromMonth, toMonth, status, cursor string, limit int) ([]types.MonthlyReport, string, error)
|
||||
CreateDraft(tenantID types.TenantID, uid types.Uid, reportID, month string) (*types.MonthlyReport, error)
|
||||
Get(tenantID types.TenantID, uid types.Uid, reportID string) (*types.MonthlyReport, error)
|
||||
SaveDraft(tenantID types.TenantID, uid types.Uid, reportID, workSummary, nextPlan string, attachments []string, version int) (*types.MonthlyReport, error)
|
||||
Submit(tenantID types.TenantID, uid types.Uid, reportID string, version int) (*types.MonthlyReport, error)
|
||||
Stats(tenantID types.TenantID, uid types.Uid, month, departmentID string) (*types.MonthlyReportStats, error)
|
||||
}
|
||||
|
||||
type monthlyReportsMapper struct{}
|
||||
|
||||
var MonthlyReports MonthlyReportsPersistenceInterface
|
||||
|
||||
func (monthlyReportsMapper) List(tenantID types.TenantID, uid types.Uid, scope, fromMonth, toMonth, status, cursor string, limit int) ([]types.MonthlyReport, string, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, "", types.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return tenantAdp.TenantMonthlyReportList(tenantID, uid, strings.TrimSpace(scope), strings.TrimSpace(fromMonth), strings.TrimSpace(toMonth), strings.TrimSpace(status), strings.TrimSpace(cursor), limit)
|
||||
}
|
||||
|
||||
func (monthlyReportsMapper) CreateDraft(tenantID types.TenantID, uid types.Uid, reportID, month string) (*types.MonthlyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" || strings.TrimSpace(month) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMonthlyReportCreateDraft(tenantID, uid, strings.TrimSpace(reportID), strings.TrimSpace(month))
|
||||
}
|
||||
|
||||
func (monthlyReportsMapper) Get(tenantID types.TenantID, uid types.Uid, reportID string) (*types.MonthlyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMonthlyReportGet(tenantID, uid, strings.TrimSpace(reportID))
|
||||
}
|
||||
|
||||
func (monthlyReportsMapper) SaveDraft(tenantID types.TenantID, uid types.Uid, reportID, workSummary, nextPlan string, attachments []string, version int) (*types.MonthlyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" || version <= 0 {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMonthlyReportSaveDraft(tenantID, uid, strings.TrimSpace(reportID), workSummary, nextPlan, attachments, version)
|
||||
}
|
||||
|
||||
func (monthlyReportsMapper) Submit(tenantID types.TenantID, uid types.Uid, reportID string, version int) (*types.MonthlyReport, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(reportID) == "" || version <= 0 {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMonthlyReportSubmit(tenantID, uid, strings.TrimSpace(reportID), version)
|
||||
}
|
||||
|
||||
func (monthlyReportsMapper) Stats(tenantID types.TenantID, uid types.Uid, month, departmentID string) (*types.MonthlyReportStats, error) {
|
||||
if uid.IsZero() || strings.TrimSpace(month) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMonthlyReportStats(tenantID, uid, strings.TrimSpace(month), strings.TrimSpace(departmentID))
|
||||
}
|
||||
|
||||
type MailboxPersistenceInterface interface {
|
||||
Config(tenantID types.TenantID, uid types.Uid) (*types.MailboxConfig, error)
|
||||
Submit(tenantID types.TenantID, uid types.Uid, submission *types.MailboxSubmission, idempotencyKey string) (*types.MailboxSubmission, error)
|
||||
}
|
||||
|
||||
type mailboxMapper struct{}
|
||||
|
||||
var Mailbox MailboxPersistenceInterface
|
||||
|
||||
func (mailboxMapper) Config(tenantID types.TenantID, uid types.Uid) (*types.MailboxConfig, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMailboxConfigGet(tenantID, uid)
|
||||
}
|
||||
|
||||
func (mailboxMapper) Submit(tenantID types.TenantID, uid types.Uid, submission *types.MailboxSubmission, idempotencyKey string) (*types.MailboxSubmission, error) {
|
||||
if uid.IsZero() || submission == nil || strings.TrimSpace(submission.SubmissionID) == "" ||
|
||||
strings.TrimSpace(submission.Subject) == "" || strings.TrimSpace(submission.Content) == "" ||
|
||||
strings.TrimSpace(idempotencyKey) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMailboxSubmissionCreate(tenantID, uid, submission, strings.TrimSpace(idempotencyKey))
|
||||
}
|
||||
|
||||
type ProfilePersistenceInterface interface {
|
||||
Get(tenantID types.TenantID, uid types.Uid) (*types.UserProfile, error)
|
||||
Update(tenantID types.TenantID, uid types.Uid, patch types.UserProfilePatch) (*types.UserProfile, error)
|
||||
GetPrivacy(tenantID types.TenantID, uid types.Uid) (*types.UserPrivacySettings, error)
|
||||
UpdatePrivacy(tenantID types.TenantID, uid types.Uid, patch types.UserPrivacyPatch) (*types.UserPrivacySettings, error)
|
||||
}
|
||||
|
||||
type profileMapper struct{}
|
||||
|
||||
var Profiles ProfilePersistenceInterface
|
||||
|
||||
type AppReleasePersistenceInterface interface {
|
||||
Latest(tenantID types.TenantID, platform, channel string) (*types.AppRelease, error)
|
||||
}
|
||||
|
||||
type appReleaseMapper struct{}
|
||||
|
||||
var AppReleases AppReleasePersistenceInterface
|
||||
|
||||
func (appReleaseMapper) Latest(tenantID types.TenantID, platform, channel string) (*types.AppRelease, error) {
|
||||
platform = strings.ToLower(strings.TrimSpace(platform))
|
||||
channel = strings.ToLower(strings.TrimSpace(channel))
|
||||
if platform == "" || channel == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantAppReleaseLatest(tenantID, platform, channel)
|
||||
}
|
||||
|
||||
type DevicePushTokenPersistenceInterface interface {
|
||||
Upsert(tenantID types.TenantID, token *types.DevicePushToken) (*types.DevicePushToken, error)
|
||||
}
|
||||
|
||||
type devicePushTokenMapper struct{}
|
||||
|
||||
var DevicePushTokens DevicePushTokenPersistenceInterface
|
||||
|
||||
func (devicePushTokenMapper) Upsert(tenantID types.TenantID, token *types.DevicePushToken) (*types.DevicePushToken, error) {
|
||||
if token == nil || token.UserID.IsZero() || strings.TrimSpace(token.DeviceID) == "" ||
|
||||
strings.TrimSpace(token.Provider) == "" || strings.TrimSpace(token.Token) == "" {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantDevicePushTokenUpsert(tenantID, token)
|
||||
}
|
||||
|
||||
func (profileMapper) Get(tenantID types.TenantID, uid types.Uid) (*types.UserProfile, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantUserProfileGet(tenantID, uid)
|
||||
}
|
||||
|
||||
func (profileMapper) Update(tenantID types.TenantID, uid types.Uid, patch types.UserProfilePatch) (*types.UserProfile, error) {
|
||||
if uid.IsZero() || patch.Version <= 0 || !validUserProfilePatch(patch) {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantUserProfileUpdate(tenantID, uid, patch)
|
||||
}
|
||||
|
||||
func (profileMapper) GetPrivacy(tenantID types.TenantID, uid types.Uid) (*types.UserPrivacySettings, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantUserPrivacyGet(tenantID, uid)
|
||||
}
|
||||
|
||||
func (profileMapper) UpdatePrivacy(tenantID types.TenantID, uid types.Uid, patch types.UserPrivacyPatch) (*types.UserPrivacySettings, error) {
|
||||
if uid.IsZero() || patch.Version <= 0 {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantUserPrivacyUpdate(tenantID, uid, patch)
|
||||
}
|
||||
|
||||
func validUserProfilePatch(patch types.UserProfilePatch) bool {
|
||||
if patch.AvatarFileID == nil && patch.Nickname == nil && patch.BirthDate == nil && patch.Signature == nil && patch.Gender == nil && patch.Region == nil {
|
||||
return false
|
||||
}
|
||||
if patch.Nickname != nil {
|
||||
name := strings.TrimSpace(*patch.Nickname)
|
||||
if len([]rune(name)) == 0 || len([]rune(name)) > 32 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if patch.Signature != nil && len([]rune(*patch.Signature)) > 120 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type RelationsPersistenceInterface interface {
|
||||
CreateFriendRequest(tenantID types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error)
|
||||
ListFriendRequests(tenantID types.TenantID, uid types.Uid, direction, status, cursor string, limit int) ([]types.FriendRequestListItem, string, error)
|
||||
@@ -2015,6 +2531,8 @@ type UserSecurityPersistenceInterface interface {
|
||||
GetIndependentPassword(tenantID types.TenantID, uid types.Uid, purpose string) (*types.IndependentPassword, error)
|
||||
UpsertIndependentPassword(tenantID types.TenantID, pass *types.IndependentPassword) error
|
||||
UpdateIndependentPasswordAttempts(tenantID types.TenantID, uid types.Uid, purpose string, attempts int, lockedUntil *time.Time, updatedAt time.Time) error
|
||||
GetYouthMode(tenantID types.TenantID, uid types.Uid) (*types.YouthModeSettings, error)
|
||||
SetYouthMode(tenantID types.TenantID, uid types.Uid, enabled bool, policyVersion int, now time.Time) (*types.YouthModeSettings, error)
|
||||
CreateSystemNotification(tenantID types.TenantID, notification *types.SystemNotification) error
|
||||
ListSystemNotifications(tenantID types.TenantID, uid types.Uid, cursor string, limit int) ([]types.SystemNotification, string, error)
|
||||
MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error
|
||||
@@ -2106,6 +2624,28 @@ func (userSecurityMapper) UpdateIndependentPasswordAttempts(tenantID types.Tenan
|
||||
return tenantAdp.TenantIndependentPasswordUpdateAttempts(tenantID, uid, purpose, attempts, lockedUntil, updatedAt)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) GetYouthMode(tenantID types.TenantID, uid types.Uid) (*types.YouthModeSettings, error) {
|
||||
if uid.IsZero() {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantYouthModeGet(tenantID, uid)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) SetYouthMode(tenantID types.TenantID, uid types.Uid, enabled bool, policyVersion int, now time.Time) (*types.YouthModeSettings, error) {
|
||||
if uid.IsZero() || policyVersion <= 0 {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantYouthModeSet(tenantID, uid, enabled, policyVersion, now)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) CreateSystemNotification(tenantID types.TenantID, notification *types.SystemNotification) error {
|
||||
if notification == nil || notification.TenantID != tenantID {
|
||||
return types.ErrMalformed
|
||||
@@ -2232,6 +2772,15 @@ func init() {
|
||||
Subs = subsMapper{}
|
||||
Messages = messagesMapper{}
|
||||
Calls = callsMapper{}
|
||||
Workbench = workbenchMapper{}
|
||||
Organization = organizationMapper{}
|
||||
Meetings = meetingsMapper{}
|
||||
DailyReports = dailyReportsMapper{}
|
||||
MonthlyReports = monthlyReportsMapper{}
|
||||
Mailbox = mailboxMapper{}
|
||||
Profiles = profileMapper{}
|
||||
AppReleases = appReleaseMapper{}
|
||||
DevicePushTokens = devicePushTokenMapper{}
|
||||
Devices = deviceMapper{}
|
||||
Files = fileMapper{}
|
||||
PCache = pcacheMapper{}
|
||||
|
||||
@@ -12,6 +12,7 @@ const (
|
||||
)
|
||||
|
||||
type CallRecord struct {
|
||||
ID int64 `db:"id"`
|
||||
TenantID TenantID `db:"tenant_id" json:"-" bson:"tenant_id"`
|
||||
CallID string `db:"call_id"`
|
||||
Topic string
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
const MailboxSubmissionStatusReceived = "received"
|
||||
|
||||
type MailboxNotice struct {
|
||||
Title string
|
||||
Content string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type MailboxConfig struct {
|
||||
Enabled bool
|
||||
AllowAnonymous bool
|
||||
RequiredFields []string
|
||||
MaxLength int
|
||||
AttachmentPolicy map[string]any
|
||||
Notice MailboxNotice
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type MailboxSubmission struct {
|
||||
ID int64
|
||||
SubmissionID string
|
||||
SenderID Uid
|
||||
Subject string
|
||||
Content string
|
||||
Anonymous bool
|
||||
AttachmentFileIDs []string
|
||||
NoticeVersion string
|
||||
Status string
|
||||
SubmittedAt time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
MeetingStateScheduled = "scheduled"
|
||||
MeetingStateCanceled = "canceled"
|
||||
|
||||
MeetingResponsePending = "pending"
|
||||
MeetingResponseAccepted = "accepted"
|
||||
MeetingResponseDeclined = "declined"
|
||||
MeetingResponseTentative = "tentative"
|
||||
)
|
||||
|
||||
type Meeting struct {
|
||||
ID int64
|
||||
MeetingID string
|
||||
OrganizerID Uid
|
||||
Title string
|
||||
StartAt time.Time
|
||||
EndAt time.Time
|
||||
Location string
|
||||
State string
|
||||
ReminderMinutes []int
|
||||
IdempotencyKey string
|
||||
Version int
|
||||
CanceledAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Participants []MeetingParticipant
|
||||
}
|
||||
|
||||
type MeetingParticipant struct {
|
||||
ID int64
|
||||
MeetingID string
|
||||
UserID Uid
|
||||
Response string
|
||||
ResponseAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type MeetingPatch struct {
|
||||
Title *string
|
||||
StartAt *time.Time
|
||||
EndAt *time.Time
|
||||
Location *string
|
||||
ReminderMinutes []int
|
||||
Version int
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
type OrganizationDepartment struct {
|
||||
ID int64 `db:"id"`
|
||||
DepartmentID string `db:"department_id"`
|
||||
Name string `db:"name"`
|
||||
ParentID string `db:"parent_id"`
|
||||
SortOrder int `db:"sort_order"`
|
||||
ChildCount int `db:"child_count"`
|
||||
MemberCount int `db:"member_count"`
|
||||
Version int `db:"version"`
|
||||
State string `db:"state"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
type OrganizationMember struct {
|
||||
ID int64 `db:"id"`
|
||||
DepartmentID string `db:"department_id"`
|
||||
UserID Uid `db:"-"`
|
||||
DisplayName string `db:"display_name"`
|
||||
Position string `db:"position"`
|
||||
AvatarFileID string `db:"avatar_file_id"`
|
||||
Mobile string `db:"mobile"`
|
||||
Email string `db:"email"`
|
||||
Status string `db:"employment_state"`
|
||||
Contactable bool `db:"contactable"`
|
||||
SortOrder int `db:"sort_order"`
|
||||
Version int `db:"version"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
type OrganizationSearchItem struct {
|
||||
Type string
|
||||
Department *OrganizationDepartment
|
||||
Member *OrganizationMember
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
type UserProfile struct {
|
||||
UserID Uid
|
||||
AvatarFileID string
|
||||
Nickname string
|
||||
BirthDate string
|
||||
Signature string
|
||||
Gender string
|
||||
Region string
|
||||
PublicAccount string
|
||||
Version int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type UserProfilePatch struct {
|
||||
AvatarFileID *string
|
||||
Nickname *string
|
||||
BirthDate *string
|
||||
Signature *string
|
||||
Gender *string
|
||||
Region *string
|
||||
Version int
|
||||
}
|
||||
|
||||
type UserPrivacySettings struct {
|
||||
UserID Uid
|
||||
AddMeMethods map[string]bool
|
||||
ProfileVisibility string
|
||||
LastSeenVisibility string
|
||||
ReadReceipts bool
|
||||
Version int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type UserPrivacyPatch struct {
|
||||
AddMeMethods map[string]bool
|
||||
ProfileVisibility *string
|
||||
LastSeenVisibility *string
|
||||
ReadReceipts *bool
|
||||
Version int
|
||||
}
|
||||
|
||||
type AppRelease struct {
|
||||
ID int64
|
||||
Platform string
|
||||
Channel string
|
||||
LatestVersion string
|
||||
MinSupportedVersion string
|
||||
UpdateType string
|
||||
ReleaseNotes string
|
||||
StoreURL string
|
||||
PackageURL string
|
||||
Checksum string
|
||||
RemindAfterSeconds int
|
||||
PublishedAt time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type YouthModeSettings struct {
|
||||
UserID Uid
|
||||
Enabled bool
|
||||
PolicyVersion int
|
||||
Restrictions []string
|
||||
PasswordSet bool
|
||||
CanEnable bool
|
||||
EnabledAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type DevicePushToken struct {
|
||||
UserID Uid
|
||||
DeviceID string
|
||||
Provider string
|
||||
Token string
|
||||
AppVersion string
|
||||
Platform string
|
||||
UpdatedAt time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
DailyReportStatusDraft = "draft"
|
||||
DailyReportStatusSubmitted = "submitted"
|
||||
)
|
||||
|
||||
type DailyReport struct {
|
||||
ID int64
|
||||
ReportID string
|
||||
AuthorID Uid
|
||||
ReportDate time.Time
|
||||
Content string
|
||||
Attachments []string
|
||||
Status string
|
||||
ReadState string
|
||||
Version int
|
||||
SubmittedAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type DailyReportStats struct {
|
||||
ExpectedCount int
|
||||
SubmittedCount int
|
||||
MissingCount int
|
||||
}
|
||||
|
||||
type MonthlyReport struct {
|
||||
ID int64
|
||||
ReportID string
|
||||
AuthorID Uid
|
||||
Month string
|
||||
WorkSummary string
|
||||
NextPlan string
|
||||
Attachments []string
|
||||
Status string
|
||||
ReadState string
|
||||
Version int
|
||||
SubmittedAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type MonthlyReportStats struct {
|
||||
ExpectedCount int
|
||||
SubmittedCount int
|
||||
MissingCount int
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
type WorkbenchModule struct {
|
||||
ID int64 `db:"id"`
|
||||
Code string `db:"code"`
|
||||
Name string `db:"name"`
|
||||
IconFileID string `db:"icon_file_id"`
|
||||
Route string `db:"route"`
|
||||
SortOrder int `db:"sort_order"`
|
||||
UnreadKey string `db:"unread_key"`
|
||||
Enabled bool `db:"enabled"`
|
||||
Permission string `db:"permission"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
@@ -214,6 +214,7 @@ type testSecurityStore struct {
|
||||
realName *types.RealNameVerification
|
||||
actionToken *types.ActionToken
|
||||
independentPassword *types.IndependentPassword
|
||||
youthMode *types.YouthModeSettings
|
||||
attempts int
|
||||
lockedUntil *time.Time
|
||||
notifications []types.SystemNotification
|
||||
@@ -292,6 +293,46 @@ func (s *testSecurityStore) UpdateIndependentPasswordAttempts(_ types.TenantID,
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *testSecurityStore) GetYouthMode(_ types.TenantID, uid types.Uid) (*types.YouthModeSettings, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
if s.youthMode == nil {
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
s.youthMode = &types.YouthModeSettings{
|
||||
UserID: uid,
|
||||
Enabled: false,
|
||||
PolicyVersion: 1,
|
||||
Restrictions: []string{"night_quiet", "content_filter", "payment_guard"},
|
||||
PasswordSet: s.independentPassword != nil,
|
||||
CanEnable: s.independentPassword != nil,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
}
|
||||
return s.youthMode, nil
|
||||
}
|
||||
|
||||
func (s *testSecurityStore) SetYouthMode(_ types.TenantID, uid types.Uid, enabled bool, policyVersion int, now time.Time) (*types.YouthModeSettings, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
if s.youthMode == nil {
|
||||
s.youthMode = &types.YouthModeSettings{UserID: uid, Restrictions: []string{"night_quiet", "content_filter", "payment_guard"}}
|
||||
}
|
||||
s.youthMode.Enabled = enabled
|
||||
s.youthMode.PolicyVersion = policyVersion
|
||||
s.youthMode.PasswordSet = true
|
||||
s.youthMode.CanEnable = !enabled
|
||||
s.youthMode.UpdatedAt = now
|
||||
if enabled {
|
||||
s.youthMode.EnabledAt = &now
|
||||
} else {
|
||||
s.youthMode.EnabledAt = nil
|
||||
}
|
||||
return s.youthMode, nil
|
||||
}
|
||||
|
||||
func (s *testSecurityStore) CreateSystemNotification(_ types.TenantID, notification *types.SystemNotification) error {
|
||||
if notification != nil {
|
||||
s.notifications = append(s.notifications, *notification)
|
||||
|
||||
Reference in New Issue
Block a user