package admin import ( "fmt" "time" "github.com/knowfoolery/backend/shared/errors" "github.com/knowfoolery/backend/shared/types" ) type AdminSessionStatus string const ( AdminSessionStatusActive AdminSessionStatus = "active" AdminSessionStatusExpired AdminSessionStatus = "expired" AdminSessionStatusRevoked AdminSessionStatus = "revoked" AdminSessionStatusSuspended AdminSessionStatus = "suspended" ) type AdminSession struct { id types.AdminSessionID adminID types.UserID adminName string adminEmail string roles []AdminRole permissions []Permission sessionToken string refreshToken string ipAddress string userAgent string status AdminSessionStatus createdAt time.Time lastActiveAt time.Time expiresAt time.Time metadata map[string]interface{} events []AdminSessionEvent } type AdminRole string const ( AdminRoleSuperAdmin AdminRole = "super_admin" AdminRoleModerator AdminRole = "moderator" AdminRoleQuestionEditor AdminRole = "question_editor" AdminRoleUserManager AdminRole = "user_manager" AdminRoleReporter AdminRole = "reporter" ) type Permission string const ( PermissionManageUsers Permission = "manage_users" PermissionManageQuestions Permission = "manage_questions" PermissionManageThemes Permission = "manage_themes" PermissionViewReports Permission = "view_reports" PermissionManageSettings Permission = "manage_settings" PermissionManageAdmins Permission = "manage_admins" PermissionViewLogs Permission = "view_logs" PermissionManageLeaderboards Permission = "manage_leaderboards" PermissionModerateContent Permission = "moderate_content" PermissionManageSystem Permission = "manage_system" ) type AdminSessionEvent struct { ID string Type AdminSessionEventType Data map[string]interface{} Timestamp time.Time } type AdminSessionEventType string const ( AdminSessionEventCreated AdminSessionEventType = "session_created" AdminSessionEventRefreshed AdminSessionEventType = "session_refreshed" AdminSessionEventExpired AdminSessionEventType = "session_expired" AdminSessionEventRevoked AdminSessionEventType = "session_revoked" AdminSessionEventSuspended AdminSessionEventType = "session_suspended" AdminSessionEventPermissionAdded AdminSessionEventType = "permission_added" AdminSessionEventPermissionRemoved AdminSessionEventType = "permission_removed" AdminSessionEventRoleAssigned AdminSessionEventType = "role_assigned" AdminSessionEventRoleRevoked AdminSessionEventType = "role_revoked" ) func NewAdminSession( id types.AdminSessionID, adminID types.UserID, adminName, adminEmail, sessionToken string, roles []AdminRole, expirationDuration time.Duration, ) (*AdminSession, error) { if id == "" { return nil, errors.ErrInvalidSessionID } if adminID == "" { return nil, errors.ErrInvalidPlayerID } if adminName == "" { return nil, errors.ErrInvalidPlayerName("", "admin name cannot be empty") } if adminEmail == "" { return nil, errors.ErrValidationFailed("email", "admin email cannot be empty") } if sessionToken == "" { return nil, errors.ErrValidationFailed("session_token", "session token cannot be empty") } if expirationDuration <= 0 { expirationDuration = time.Hour * 8 } now := time.Now() permissions := calculatePermissionsFromRoles(roles) session := &AdminSession{ id: id, adminID: adminID, adminName: adminName, adminEmail: adminEmail, roles: roles, permissions: permissions, sessionToken: sessionToken, status: AdminSessionStatusActive, createdAt: now, lastActiveAt: now, expiresAt: now.Add(expirationDuration), metadata: make(map[string]interface{}), events: make([]AdminSessionEvent, 0), } session.addEvent(AdminSessionEventCreated, map[string]interface{}{ "admin_id": adminID, "admin_name": adminName, "admin_email": adminEmail, "roles": roles, "expires_at": session.expiresAt, }) return session, nil } func calculatePermissionsFromRoles(roles []AdminRole) []Permission { permissionSet := make(map[Permission]bool) for _, role := range roles { rolePermissions := getRolePermissions(role) for _, perm := range rolePermissions { permissionSet[perm] = true } } permissions := make([]Permission, 0, len(permissionSet)) for perm := range permissionSet { permissions = append(permissions, perm) } return permissions } func getRolePermissions(role AdminRole) []Permission { switch role { case AdminRoleSuperAdmin: return []Permission{ PermissionManageUsers, PermissionManageQuestions, PermissionManageThemes, PermissionViewReports, PermissionManageSettings, PermissionManageAdmins, PermissionViewLogs, PermissionManageLeaderboards, PermissionModerateContent, PermissionManageSystem, } case AdminRoleModerator: return []Permission{ PermissionManageUsers, PermissionViewReports, PermissionModerateContent, PermissionViewLogs, } case AdminRoleQuestionEditor: return []Permission{ PermissionManageQuestions, PermissionManageThemes, PermissionViewReports, } case AdminRoleUserManager: return []Permission{ PermissionManageUsers, PermissionViewReports, PermissionViewLogs, } case AdminRoleReporter: return []Permission{ PermissionViewReports, PermissionViewLogs, } default: return []Permission{} } } func (as *AdminSession) ID() types.AdminSessionID { return as.id } func (as *AdminSession) AdminID() types.UserID { return as.adminID } func (as *AdminSession) AdminName() string { return as.adminName } func (as *AdminSession) AdminEmail() string { return as.adminEmail } func (as *AdminSession) Roles() []AdminRole { result := make([]AdminRole, len(as.roles)) copy(result, as.roles) return result } func (as *AdminSession) Permissions() []Permission { result := make([]Permission, len(as.permissions)) copy(result, as.permissions) return result } func (as *AdminSession) SessionToken() string { return as.sessionToken } func (as *AdminSession) RefreshToken() string { return as.refreshToken } func (as *AdminSession) IPAddress() string { return as.ipAddress } func (as *AdminSession) UserAgent() string { return as.userAgent } func (as *AdminSession) Status() AdminSessionStatus { return as.status } func (as *AdminSession) CreatedAt() time.Time { return as.createdAt } func (as *AdminSession) LastActiveAt() time.Time { return as.lastActiveAt } func (as *AdminSession) ExpiresAt() time.Time { return as.expiresAt } func (as *AdminSession) Metadata() map[string]interface{} { result := make(map[string]interface{}) for k, v := range as.metadata { result[k] = v } return result } func (as *AdminSession) Events() []AdminSessionEvent { result := make([]AdminSessionEvent, len(as.events)) copy(result, as.events) return result } func (as *AdminSession) IsActive() bool { if as.status != AdminSessionStatusActive { return false } return time.Now().Before(as.expiresAt) } func (as *AdminSession) IsExpired() bool { return time.Now().After(as.expiresAt) || as.status == AdminSessionStatusExpired } func (as *AdminSession) HasPermission(permission Permission) bool { for _, perm := range as.permissions { if perm == permission { return true } } return false } func (as *AdminSession) HasRole(role AdminRole) bool { for _, r := range as.roles { if r == role { return true } } return false } func (as *AdminSession) HasAnyRole(roles ...AdminRole) bool { for _, role := range roles { if as.HasRole(role) { return true } } return false } func (as *AdminSession) RefreshSession(newToken string, extensionDuration time.Duration) error { if as.status != AdminSessionStatusActive { return errors.ErrOperationNotAllowed("refresh_session", "session is not active") } if as.IsExpired() { return errors.ErrSessionExpired(types.GameSessionID(as.id)) } now := time.Now() as.sessionToken = newToken as.lastActiveAt = now as.expiresAt = now.Add(extensionDuration) as.addEvent(AdminSessionEventRefreshed, map[string]interface{}{ "new_expires_at": as.expiresAt, "extended_by": extensionDuration.String(), }) return nil } func (as *AdminSession) UpdateActivity(ipAddress, userAgent string) error { if as.status != AdminSessionStatusActive { return errors.ErrOperationNotAllowed("update_activity", "session is not active") } as.lastActiveAt = time.Now() if ipAddress != "" { as.ipAddress = ipAddress } if userAgent != "" { as.userAgent = userAgent } return nil } func (as *AdminSession) RevokeSession(reason string) error { if as.status != AdminSessionStatusActive { return errors.ErrOperationNotAllowed("revoke_session", "session is not active") } as.status = AdminSessionStatusRevoked as.addEvent(AdminSessionEventRevoked, map[string]interface{}{ "reason": reason, "revoked_at": time.Now(), }) return nil } func (as *AdminSession) SuspendSession(reason string, until time.Time) error { if as.status != AdminSessionStatusActive { return errors.ErrOperationNotAllowed("suspend_session", "session is not active") } as.status = AdminSessionStatusSuspended as.metadata["suspended_until"] = until as.metadata["suspension_reason"] = reason as.addEvent(AdminSessionEventSuspended, map[string]interface{}{ "reason": reason, "suspended_until": until, "suspended_at": time.Now(), }) return nil } func (as *AdminSession) AddRole(role AdminRole) error { if as.HasRole(role) { return errors.ErrValidationFailed("role", "role already assigned") } as.roles = append(as.roles, role) as.permissions = calculatePermissionsFromRoles(as.roles) as.addEvent(AdminSessionEventRoleAssigned, map[string]interface{}{ "role": role, "assigned_at": time.Now(), "new_permissions": as.permissions, }) return nil } func (as *AdminSession) RemoveRole(role AdminRole) error { if !as.HasRole(role) { return errors.ErrValidationFailed("role", "role not assigned") } newRoles := make([]AdminRole, 0, len(as.roles)) for _, r := range as.roles { if r != role { newRoles = append(newRoles, r) } } as.roles = newRoles as.permissions = calculatePermissionsFromRoles(as.roles) as.addEvent(AdminSessionEventRoleRevoked, map[string]interface{}{ "role": role, "revoked_at": time.Now(), "new_permissions": as.permissions, }) return nil } func (as *AdminSession) SetRefreshToken(refreshToken string) error { if refreshToken == "" { return errors.ErrValidationFailed("refresh_token", "refresh token cannot be empty") } as.refreshToken = refreshToken return nil } func (as *AdminSession) SetMetadata(key string, value interface{}) error { if key == "" { return errors.ErrInvalidMetadata } as.metadata[key] = value return nil } func (as *AdminSession) RemoveMetadata(key string) { delete(as.metadata, key) } func (as *AdminSession) GetTimeUntilExpiration() time.Duration { return time.Until(as.expiresAt) } func (as *AdminSession) GetSessionDuration() time.Duration { return time.Since(as.createdAt) } func (as *AdminSession) GetIdleDuration() time.Duration { return time.Since(as.lastActiveAt) } func (as *AdminSession) addEvent(eventType AdminSessionEventType, data map[string]interface{}) { event := AdminSessionEvent{ ID: fmt.Sprintf("%s_%d", eventType, time.Now().UnixNano()), Type: eventType, Data: data, Timestamp: time.Now(), } as.events = append(as.events, event) } func (as *AdminSession) RequirePermission(permission Permission) error { if !as.HasPermission(permission) { return errors.ErrOperationNotAllowed("access_resource", fmt.Sprintf("missing permission: %s", permission)) } return nil } func (as *AdminSession) RequireRole(role AdminRole) error { if !as.HasRole(role) { return errors.ErrOperationNotAllowed("access_resource", fmt.Sprintf("missing role: %s", role)) } return nil } func (as *AdminSession) ValidateAccess() error { if as.status != AdminSessionStatusActive { return errors.ErrOperationNotAllowed("access", "session is not active") } if as.IsExpired() { as.status = AdminSessionStatusExpired as.addEvent(AdminSessionEventExpired, map[string]interface{}{ "expired_at": time.Now(), }) return errors.ErrSessionExpired(types.GameSessionID(as.id)) } return nil } func (as *AdminSession) String() string { return fmt.Sprintf("AdminSession{ID: %s, AdminID: %s, Status: %s, ExpiresAt: %v}", as.id, as.adminID, as.status, as.expiresAt) } type AdminSessionSnapshot struct { ID types.AdminSessionID `json:"id"` AdminID types.UserID `json:"admin_id"` AdminName string `json:"admin_name"` AdminEmail string `json:"admin_email"` Roles []AdminRole `json:"roles"` Permissions []Permission `json:"permissions"` SessionToken string `json:"session_token"` RefreshToken string `json:"refresh_token"` IPAddress string `json:"ip_address"` UserAgent string `json:"user_agent"` Status AdminSessionStatus `json:"status"` CreatedAt time.Time `json:"created_at"` LastActiveAt time.Time `json:"last_active_at"` ExpiresAt time.Time `json:"expires_at"` Metadata map[string]interface{} `json:"metadata"` Events []AdminSessionEvent `json:"events"` } func (as *AdminSession) ToSnapshot() AdminSessionSnapshot { return AdminSessionSnapshot{ ID: as.id, AdminID: as.adminID, AdminName: as.adminName, AdminEmail: as.adminEmail, Roles: as.Roles(), Permissions: as.Permissions(), SessionToken: as.sessionToken, RefreshToken: as.refreshToken, IPAddress: as.ipAddress, UserAgent: as.userAgent, Status: as.status, CreatedAt: as.createdAt, LastActiveAt: as.lastActiveAt, ExpiresAt: as.expiresAt, Metadata: as.Metadata(), Events: as.Events(), } } func (as *AdminSession) FromSnapshot(snapshot AdminSessionSnapshot) error { if snapshot.ID == "" { return errors.ErrInvalidSessionID } if snapshot.AdminID == "" { return errors.ErrInvalidPlayerID } as.id = snapshot.ID as.adminID = snapshot.AdminID as.adminName = snapshot.AdminName as.adminEmail = snapshot.AdminEmail as.roles = make([]AdminRole, len(snapshot.Roles)) copy(as.roles, snapshot.Roles) as.permissions = make([]Permission, len(snapshot.Permissions)) copy(as.permissions, snapshot.Permissions) as.sessionToken = snapshot.SessionToken as.refreshToken = snapshot.RefreshToken as.ipAddress = snapshot.IPAddress as.userAgent = snapshot.UserAgent as.status = snapshot.Status as.createdAt = snapshot.CreatedAt as.lastActiveAt = snapshot.LastActiveAt as.expiresAt = snapshot.ExpiresAt as.metadata = make(map[string]interface{}) for k, v := range snapshot.Metadata { as.metadata[k] = v } as.events = make([]AdminSessionEvent, len(snapshot.Events)) copy(as.events, snapshot.Events) return nil }