Files
gochat/backend/internal/model/notification_setting.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

243 lines
9.8 KiB
Go

package model
// NotificationSetting represents per-user per-account notification preferences.
// Reference: Chatwoot app/models/notification_setting.rb
//
// Uses bitmask flags for email_flags and push_flags columns.
// Chatwoot uses FlagShihTzu gem for single-column multi-flag storage.
// FlagShihTzu bit positions: bit_value = 2^(enum_value - 1)
//
// Table name: notification_settings
//
// id uint primaryKey autoIncrement
// account_id uint not null; unique index with user_id
// user_id uint not null; unique index with account_id
// email_flags int default:0 not null; bitmask
// push_flags int default:0 not null; bitmask
// created_at time
// updated_at time
type NotificationSetting struct {
Base `gorm:"embedded"`
AccountID uint `gorm:"not null;uniqueIndex:by_account_user"`
UserID uint `gorm:"not null;uniqueIndex:by_account_user"`
EmailFlags int `gorm:"default:0;not null"`
PushFlags int `gorm:"default:0;not null"`
}
// TableName returns the database table name.
func (NotificationSetting) TableName() string { return "notification_settings" }
// Chatwoot NOTIFICATION_TYPES enum values and FlagShihTzu bit positions.
// Reference: Chatwoot Notification::NOTIFICATION_TYPES
// FlagShihTzu: has_flags key = "email_{type}" / "push_{type}", bit_value = 2^(enum_value - 1)
//
// Enum value → Flag name → Bit position → Bit value:
// 1 → conversation_creation → bit 0 → 1
// 2 → conversation_assignment → bit 1 → 2
// 3 → assigned_conversation_new_message → bit 2 → 4
// 4 → conversation_mention → bit 3 → 8
// 5 → participating_conversation_new_message → bit 4 → 16
// 6 → sla_missed_first_response → bit 5 → 32
// 7 → sla_missed_next_response → bit 6 → 64
// 8 → sla_missed_resolution → bit 7 → 128
const (
// Email notification flags (bit values match Chatwoot FlagShihTzu)
EmailFlagConversationCreation = 1 // 2^0
EmailFlagConversationAssignment = 2 // 2^1
EmailFlagAssignedConversationNewMessage = 4 // 2^2
EmailFlagConversationMention = 8 // 2^3
EmailFlagParticipatingConversationNewMessage = 16 // 2^4
EmailFlagSLAMissedFirstResponse = 32 // 2^5
EmailFlagSLAMissedNextResponse = 64 // 2^6
EmailFlagSLAMissedResolution = 128 // 2^7
// Push notification flags (same bit values, separate column)
PushFlagConversationCreation = 1
PushFlagConversationAssignment = 2
PushFlagAssignedConversationNewMessage = 4
PushFlagConversationMention = 8
PushFlagParticipatingConversationNewMessage = 16
PushFlagSLAMissedFirstResponse = 32
PushFlagSLAMissedNextResponse = 64
PushFlagSLAMissedResolution = 128
)
// AllEmailFlags returns the bitmask of all email notification types.
func AllEmailFlags() int {
return EmailFlagConversationCreation |
EmailFlagConversationAssignment |
EmailFlagAssignedConversationNewMessage |
EmailFlagConversationMention |
EmailFlagParticipatingConversationNewMessage |
EmailFlagSLAMissedFirstResponse |
EmailFlagSLAMissedNextResponse |
EmailFlagSLAMissedResolution
}
// AllPushFlags returns the bitmask of all push notification types.
func AllPushFlags() int {
return PushFlagConversationCreation |
PushFlagConversationAssignment |
PushFlagAssignedConversationNewMessage |
PushFlagConversationMention |
PushFlagParticipatingConversationNewMessage |
PushFlagSLAMissedFirstResponse |
PushFlagSLAMissedNextResponse |
PushFlagSLAMissedResolution
}
// AllEmailFlagNames returns all available email notification flag names.
// Matches Chatwoot FlagShihTzu generated flag names: "email_{type}".
func AllEmailFlagNames() []string {
return []string{
"email_conversation_creation",
"email_conversation_assignment",
"email_assigned_conversation_new_message",
"email_conversation_mention",
"email_participating_conversation_new_message",
"email_sla_missed_first_response",
"email_sla_missed_next_response",
"email_sla_missed_resolution",
}
}
// AllPushFlagNames returns all available push notification flag names.
// Matches Chatwoot FlagShihTzu generated flag names: "push_{type}".
func AllPushFlagNames() []string {
return []string{
"push_conversation_creation",
"push_conversation_assignment",
"push_assigned_conversation_new_message",
"push_conversation_mention",
"push_participating_conversation_new_message",
"push_sla_missed_first_response",
"push_sla_missed_next_response",
"push_sla_missed_resolution",
}
}
// Email flag name → bit value mapping for selected_email_flags parsing.
var emailFlagNameToValue = map[string]int{
"email_conversation_creation": EmailFlagConversationCreation,
"email_conversation_assignment": EmailFlagConversationAssignment,
"email_assigned_conversation_new_message": EmailFlagAssignedConversationNewMessage,
"email_conversation_mention": EmailFlagConversationMention,
"email_participating_conversation_new_message": EmailFlagParticipatingConversationNewMessage,
"email_sla_missed_first_response": EmailFlagSLAMissedFirstResponse,
"email_sla_missed_next_response": EmailFlagSLAMissedNextResponse,
"email_sla_missed_resolution": EmailFlagSLAMissedResolution,
}
// Push flag name → bit value mapping for selected_push_flags parsing.
var pushFlagNameToValue = map[string]int{
"push_conversation_creation": PushFlagConversationCreation,
"push_conversation_assignment": PushFlagConversationAssignment,
"push_assigned_conversation_new_message": PushFlagAssignedConversationNewMessage,
"push_conversation_mention": PushFlagConversationMention,
"push_participating_conversation_new_message": PushFlagParticipatingConversationNewMessage,
"push_sla_missed_first_response": PushFlagSLAMissedFirstResponse,
"push_sla_missed_next_response": PushFlagSLAMissedNextResponse,
"push_sla_missed_resolution": PushFlagSLAMissedResolution,
}
// Email bit value → flag name mapping.
var emailValueToFlagName = map[int]string{
EmailFlagConversationCreation: "email_conversation_creation",
EmailFlagConversationAssignment: "email_conversation_assignment",
EmailFlagAssignedConversationNewMessage: "email_assigned_conversation_new_message",
EmailFlagConversationMention: "email_conversation_mention",
EmailFlagParticipatingConversationNewMessage: "email_participating_conversation_new_message",
EmailFlagSLAMissedFirstResponse: "email_sla_missed_first_response",
EmailFlagSLAMissedNextResponse: "email_sla_missed_next_response",
EmailFlagSLAMissedResolution: "email_sla_missed_resolution",
}
// Push bit value → flag name mapping.
var pushValueToFlagName = map[int]string{
PushFlagConversationCreation: "push_conversation_creation",
PushFlagConversationAssignment: "push_conversation_assignment",
PushFlagAssignedConversationNewMessage: "push_assigned_conversation_new_message",
PushFlagConversationMention: "push_conversation_mention",
PushFlagParticipatingConversationNewMessage: "push_participating_conversation_new_message",
PushFlagSLAMissedFirstResponse: "push_sla_missed_first_response",
PushFlagSLAMissedNextResponse: "push_sla_missed_next_response",
PushFlagSLAMissedResolution: "push_sla_missed_resolution",
}
// HasEmailFlag checks if a specific email flag is enabled.
func (ns *NotificationSetting) HasEmailFlag(flag int) bool {
return ns.EmailFlags&flag != 0
}
// HasPushFlag checks if a specific push flag is enabled.
func (ns *NotificationSetting) HasPushFlag(flag int) bool {
return ns.PushFlags&flag != 0
}
// SetEmailFlagsFromNames sets email flags from a list of flag names.
// Reference: Chatwoot selected_email_flags (FlagShihTzu flag names like "email_conversation_creation")
func (ns *NotificationSetting) SetEmailFlagsFromNames(names []string) {
ns.EmailFlags = 0
for _, name := range names {
if v, ok := emailFlagNameToValue[name]; ok {
ns.EmailFlags |= v
}
}
}
// SetPushFlagsFromNames sets push flags from a list of flag names.
// Reference: Chatwoot selected_push_flags
func (ns *NotificationSetting) SetPushFlagsFromNames(names []string) {
ns.PushFlags = 0
for _, name := range names {
if v, ok := pushFlagNameToValue[name]; ok {
ns.PushFlags |= v
}
}
}
// SelectedEmailFlagNames returns the names of enabled email flags.
// Matches Chatwoot FlagShihTzu: returns "email_{type}" format names.
func (ns *NotificationSetting) SelectedEmailFlagNames() []string {
names := []string{}
for _, bitValue := range []int{
EmailFlagConversationCreation,
EmailFlagConversationAssignment,
EmailFlagAssignedConversationNewMessage,
EmailFlagConversationMention,
EmailFlagParticipatingConversationNewMessage,
EmailFlagSLAMissedFirstResponse,
EmailFlagSLAMissedNextResponse,
EmailFlagSLAMissedResolution,
} {
if ns.EmailFlags&bitValue != 0 {
names = append(names, emailValueToFlagName[bitValue])
}
}
return names
}
// SelectedPushFlagNames returns the names of enabled push flags.
// Matches Chatwoot FlagShihTzu: returns "push_{type}" format names.
func (ns *NotificationSetting) SelectedPushFlagNames() []string {
names := []string{}
for _, bitValue := range []int{
PushFlagConversationCreation,
PushFlagConversationAssignment,
PushFlagAssignedConversationNewMessage,
PushFlagConversationMention,
PushFlagParticipatingConversationNewMessage,
PushFlagSLAMissedFirstResponse,
PushFlagSLAMissedNextResponse,
PushFlagSLAMissedResolution,
} {
if ns.PushFlags&bitValue != 0 {
names = append(names, pushValueToFlagName[bitValue])
}
}
return names
}