package api import ( "strings" "time" "gorm.io/gorm" "gorm.io/gorm/clause" "wucher/internal/domain/auth" "wucher/internal/domain/base" ) // SeedRoles inserts default roles if they don't exist. func SeedRoles(db *gorm.DB) error { roles := []auth.Role{ {Code: "admin", Name: "admin", Description: "Full access to all resources."}, {Code: auth.RoleCodeStaff, Name: auth.RoleCodeStaff, Description: "Operational access with limited admin rights."}, {Code: "user", Name: "user", Description: "Regular end-user access."}, {Code: auth.RoleCodePilot, Name: "pilot", Description: "HEMS pilot contact role."}, {Code: auth.RoleCodeDoctor, Name: "doctor", Description: "HEMS doctor contact role."}, {Code: auth.RoleCodeAirRescuer, Name: "air_rescuer", Description: "HEMS air rescuer contact role."}, {Code: auth.RoleCodeTechnician, Name: "technician", Description: "HEMS technician contact role."}, {Code: auth.RoleCodeFlightAssistant, Name: "flight_assistant", Description: "HEMS flight assistant contact role."}, } return db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "name"}}, DoUpdates: clause.AssignmentColumns([]string{"code", "description"}), }).Create(&roles).Error } // DefaultPermissions returns the canonical catalog of permissions seeded into the system. func DefaultPermissions() []auth.Permission { return []auth.Permission{ {Name: "Create User", Key: "user.create", Description: "Create users"}, {Name: "Read User", Key: "user.read", Description: "Read users"}, {Name: "Update User", Key: "user.update", Description: "Update users"}, {Name: "Delete User", Key: "user.delete", Description: "Delete users"}, {Name: "Create Role", Key: "role.create", Description: "Create roles"}, {Name: "Read Role", Key: "role.read", Description: "Read roles"}, {Name: "Update Role", Key: "role.update", Description: "Update roles"}, {Name: "Delete Role", Key: "role.delete", Description: "Delete roles"}, {Name: "Read Permission", Key: "role.permission.read", Description: "Read permissions"}, {Name: "Assign Permission", Key: "role.permission.assign", Description: "Assign permissions to role"}, {Name: "Remove Permission", Key: "role.permission.remove", Description: "Remove permissions from role"}, {Name: "Update Permission", Key: "role.permission.update", Description: "Update permission configuration"}, {Name: "Read Filesystem", Key: "filesystem.read", Description: "Read filesystem structure"}, {Name: "Read File Manager", Key: "file_manager.read", Description: "Read file manager"}, {Name: "Create File Manager", Key: "file_manager.create", Description: "Create in file manager"}, {Name: "Update File Manager", Key: "file_manager.update", Description: "Update in file manager"}, {Name: "Delete File Manager", Key: "file_manager.delete", Description: "Delete in file manager"}, {Name: "Read Folders", Key: "file_manager.folder.read", Description: "Read folders"}, {Name: "Create Folders", Key: "file_manager.folder.create", Description: "Create folders"}, {Name: "Update Folders", Key: "file_manager.folder.update", Description: "Update folders"}, {Name: "Delete Folders", Key: "file_manager.folder.delete", Description: "Delete folders"}, {Name: "Read Files", Key: "file_manager.file.read", Description: "Read files"}, {Name: "Create Files", Key: "file_manager.file.create", Description: "Create files"}, {Name: "Update Files", Key: "file_manager.file.update", Description: "Update files"}, {Name: "Delete Files", Key: "file_manager.file.delete", Description: "Delete files"}, {Name: "Create Helicopter", Key: "helicopter.create", Description: "Create helicopter"}, {Name: "Generate Helicopter Report Number", Key: "helicopter.report.generate", Description: "Generate next helicopter report number"}, {Name: "Read Helicopter", Key: "helicopter.read", Description: "Read helicopter"}, {Name: "Update Helicopter", Key: "helicopter.update", Description: "Update helicopter"}, {Name: "Delete Helicopter", Key: "helicopter.delete", Description: "Delete helicopter"}, {Name: "Create Helicopter Usage", Key: "helicopter_usage.create", Description: "Create helicopter usage"}, {Name: "Read Helicopter Usage", Key: "helicopter_usage.read", Description: "Read helicopter usage"}, {Name: "Update Helicopter Usage", Key: "helicopter_usage.update", Description: "Update helicopter usage"}, {Name: "Delete Helicopter Usage", Key: "helicopter_usage.delete", Description: "Delete helicopter usage"}, {Name: "Create Flight Inspection", Key: "flight_inspection.create", Description: "Create flight inspection"}, {Name: "Read Flight Inspection", Key: "flight_inspection.read", Description: "Read flight inspection"}, {Name: "Update Flight Inspection", Key: "flight_inspection.update", Description: "Update flight inspection"}, {Name: "Create After Flight Inspection", Key: "after_flight.create", Description: "Create after flight inspection"}, {Name: "Read After Flight Inspection", Key: "after_flight.read", Description: "Read after flight inspection"}, {Name: "Update After Flight Inspection", Key: "after_flight.update", Description: "Update after flight inspection"}, {Name: "Delete After Flight Inspection", Key: "after_flight.delete", Description: "Delete after flight inspection"}, {Name: "Create Flight", Key: "flight.create", Description: "Create flight"}, {Name: "Read Flight", Key: "flight.read", Description: "Read flight"}, {Name: "Update Flight", Key: "flight.update", Description: "Update flight"}, {Name: "Complete Flight Report", Key: "flight.complete", Description: "Complete (finalize) flight report"}, {Name: "Delete Flight", Key: "flight.delete", Description: "Delete flight"}, {Name: "Create Reserve AC", Key: "reserve_ac.create", Description: "Create reserve AC"}, {Name: "Read Reserve AC", Key: "reserve_ac.read", Description: "Read reserve AC"}, {Name: "Update Reserve AC", Key: "reserve_ac.update", Description: "Update reserve AC"}, {Name: "Delete Reserve AC", Key: "reserve_ac.delete", Description: "Delete reserve AC"}, {Name: "Create Facility", Key: "facility.create", Description: "Create facility"}, {Name: "Read Facility", Key: "facility.read", Description: "Read facility"}, {Name: "Update Facility", Key: "facility.update", Description: "Update facility"}, {Name: "Delete Facility", Key: "facility.delete", Description: "Delete facility"}, {Name: "Create Base", Key: "base.create", Description: "Create base"}, {Name: "Read Base", Key: "base.read", Description: "Read base"}, {Name: "Update Base", Key: "base.update", Description: "Update base"}, {Name: "Delete Base", Key: "base.delete", Description: "Delete base"}, {Name: "Create HEMS Base", Key: "hems_base.create", Description: "Create HEMS base"}, {Name: "Read HEMS Base", Key: "hems_base.read", Description: "Read HEMS base"}, {Name: "Update HEMS Base", Key: "hems_base.update", Description: "Update HEMS base"}, {Name: "Delete HEMS Base", Key: "hems_base.delete", Description: "Delete HEMS base"}, {Name: "Create Medicine", Key: "medicine.create", Description: "Create medicine"}, {Name: "Read Medicine", Key: "medicine.read", Description: "Read medicine"}, {Name: "Update Medicine", Key: "medicine.update", Description: "Update medicine"}, {Name: "Delete Medicine", Key: "medicine.delete", Description: "Delete medicine"}, {Name: "Create Vocation", Key: "vocation.create", Description: "Create vocation"}, {Name: "Read Vocation", Key: "vocation.read", Description: "Read vocation"}, {Name: "Update Vocation", Key: "vocation.update", Description: "Update vocation"}, {Name: "Delete Vocation", Key: "vocation.delete", Description: "Delete vocation"}, {Name: "Create OPC", Key: "opc.create", Description: "Create opc"}, {Name: "Read OPC", Key: "opc.read", Description: "Read opc"}, {Name: "Update OPC", Key: "opc.update", Description: "Update opc"}, {Name: "Delete OPC", Key: "opc.delete", Description: "Delete opc"}, {Name: "Create Forces Present", Key: "forces_present.create", Description: "Create forces present"}, {Name: "Read Forces Present", Key: "forces_present.read", Description: "Read forces present"}, {Name: "Update Forces Present", Key: "forces_present.update", Description: "Update forces present"}, {Name: "Delete Forces Present", Key: "forces_present.delete", Description: "Delete forces present"}, {Name: "Create No ICAO Code", Key: "no_icao_code.create", Description: "Create no icao code"}, {Name: "Read No ICAO Code", Key: "no_icao_code.read", Description: "Read no icao code"}, {Name: "Update No ICAO Code", Key: "no_icao_code.update", Description: "Update no icao code"}, {Name: "Delete No ICAO Code", Key: "no_icao_code.delete", Description: "Delete no icao code"}, {Name: "Create Health Insurance Companies", Key: "health_insurance_companies.create", Description: "Create health insurance companies"}, {Name: "Read Health Insurance Companies", Key: "health_insurance_companies.read", Description: "Read health insurance companies"}, {Name: "Update Health Insurance Companies", Key: "health_insurance_companies.update", Description: "Update health insurance companies"}, {Name: "Delete Health Insurance Companies", Key: "health_insurance_companies.delete", Description: "Delete health insurance companies"}, {Name: "Create Federal State", Key: "federal_state.create", Description: "Create federal state"}, {Name: "Read Federal State", Key: "federal_state.read", Description: "Read federal state"}, {Name: "Update Federal State", Key: "federal_state.update", Description: "Update federal state"}, {Name: "Delete Federal State", Key: "federal_state.delete", Description: "Delete federal state"}, {Name: "Create ICAO", Key: "icao.create", Description: "Create ICAO"}, {Name: "Read ICAO", Key: "icao.read", Description: "Read ICAO"}, {Name: "Update ICAO", Key: "icao.update", Description: "Update ICAO"}, {Name: "Delete ICAO", Key: "icao.delete", Description: "Delete ICAO"}, {Name: "Create Country", Key: "country.create", Description: "Create country"}, {Name: "Read Country", Key: "country.read", Description: "Read country"}, {Name: "Update Country", Key: "country.update", Description: "Update country"}, {Name: "Delete Country", Key: "country.delete", Description: "Delete country"}, {Name: "Create Master Settings", Key: "master_settings.create", Description: "Create master settings"}, {Name: "Read Master Settings", Key: "master_settings.read", Description: "Read master settings"}, {Name: "Update Master Settings", Key: "master_settings.update", Description: "Update master settings"}, {Name: "Delete Master Settings", Key: "master_settings.delete", Description: "Delete master settings"}, {Name: "Create Duty Roster", Key: "duty_roster.create", Description: "Create duty roster"}, {Name: "Read Duty Roster", Key: "duty_roster.read", Description: "Read duty roster"}, {Name: "Update Duty Roster", Key: "duty_roster.update", Description: "Update duty roster"}, {Name: "Delete Duty Roster", Key: "duty_roster.delete", Description: "Delete duty roster"}, {Name: "Create Air Rescuer Checklist", Key: "air_rescuer_checklist.create", Description: "Create air rescuer checklist"}, {Name: "Read Air Rescuer Checklist", Key: "air_rescuer_checklist.read", Description: "Read air rescuer checklist"}, {Name: "Update Air Rescuer Checklist", Key: "air_rescuer_checklist.update", Description: "Update air rescuer checklist"}, {Name: "Delete Air Rescuer Checklist", Key: "air_rescuer_checklist.delete", Description: "Delete air rescuer checklist"}, {Name: "Create Flight Prep Check", Key: "flight_prep_check.create", Description: "Create flight prep check"}, {Name: "Read Flight Prep Check", Key: "flight_prep_check.read", Description: "Read flight prep check"}, {Name: "Update Flight Prep Check", Key: "flight_prep_check.update", Description: "Update flight prep check"}, {Name: "Read Audit Log", Key: "audit.read", Description: "Read audit logs"}, {Name: "Create Insurance Patient Data", Key: "insurance_patient_data.create", Description: "Create insurance patient data"}, {Name: "Read Insurance Patient Data", Key: "insurance_patient_data.read", Description: "Read insurance patient data"}, {Name: "Update Insurance Patient Data", Key: "insurance_patient_data.update", Description: "Update insurance patient data"}, {Name: "Delete Insurance Patient Data", Key: "insurance_patient_data.delete", Description: "Delete insurance patient data"}, {Name: "Create Patient Data", Key: "patient_data.create", Description: "Create patient data"}, {Name: "Read Patient Data", Key: "patient_data.read", Description: "Read patient data"}, {Name: "Update Patient Data", Key: "patient_data.update", Description: "Update patient data"}, {Name: "Delete Patient Data", Key: "patient_data.delete", Description: "Delete patient data"}, {Name: "Create Operation", Key: "operation.create", Description: "Create HEMS operational data"}, {Name: "Read Operation", Key: "operation.read", Description: "Read HEMS operational data"}, {Name: "Update Operation", Key: "operation.update", Description: "Update HEMS operational data"}, {Name: "Delete Operation", Key: "operation.delete", Description: "Delete HEMS operational data"}, {Name: "Create Operation Category", Key: "operation_category.create", Description: "Create HEMS operation category"}, {Name: "Read Operation Category", Key: "operation_category.read", Description: "Read HEMS operation category"}, {Name: "Update Operation Category", Key: "operation_category.update", Description: "Update HEMS operation category"}, {Name: "Delete Operation Category", Key: "operation_category.delete", Description: "Delete HEMS operation category"}, {Name: "Create HEMS Operation", Key: "hems_operation.create", Description: "Create HEMS operation"}, {Name: "Read HEMS Operation", Key: "hems_operation.read", Description: "Read HEMS operation"}, {Name: "Update HEMS Operation", Key: "hems_operation.update", Description: "Update HEMS operation"}, {Name: "Delete HEMS Operation", Key: "hems_operation.delete", Description: "Delete HEMS operation"}, {Name: "Create DUL", Key: "dul.create", Description: "Create DUL"}, {Name: "Read DUL", Key: "dul.read", Description: "Read DUL"}, {Name: "Update DUL", Key: "dul.update", Description: "Update DUL"}, {Name: "Delete DUL", Key: "dul.delete", Description: "Delete DUL"}, {Name: "Create Complaint", Key: "complaint.create", Description: "Create complaint"}, {Name: "Read Complaint", Key: "complaint.read", Description: "Read complaint"}, {Name: "Update Complaint", Key: "complaint.update", Description: "Update complaint"}, {Name: "Delete Complaint", Key: "complaint.delete", Description: "Delete complaint"}, {Name: "Decide Complaint NSR", Key: "complaint.nsr", Description: "Set or cancel complaint NSR (non-safety release)"}, {Name: "Create EASA Release", Key: "easa_release.create", Description: "Create EASA maintenance release"}, {Name: "Read EASA Release", Key: "easa_release.read", Description: "Read EASA maintenance release"}, {Name: "Update EASA Release", Key: "easa_release.update", Description: "Update EASA maintenance release"}, {Name: "Delete EASA Release", Key: "easa_release.delete", Description: "Delete EASA maintenance release"}, {Name: "Sign EASA Release", Key: "easa_release.sign", Description: "Sign EASA maintenance release (return to service)"}, {Name: "Read Action Sign-off", Key: "action_signoff.read", Description: "Read corrective-action sign-off"}, {Name: "Sign Action Sign-off", Key: "action_signoff.sign", Description: "Toggle (sign/unsign) corrective-action sign-off, per complaint or fleet-wide"}, {Name: "Create MCF", Key: "mcf.create", Description: "Create maintenance check flight"}, {Name: "Read MCF", Key: "mcf.read", Description: "Read maintenance check flight"}, {Name: "Complete MCF", Key: "mcf.complete", Description: "Complete (release) a helicopter's open maintenance check flight"}, } } // SeedPermissions inserts default permissions and keeps existing rows in sync. func SeedPermissions(db *gorm.DB) error { perms := DefaultPermissions() for i := range perms { perms[i].Module = auth.ModuleForKey(perms[i].Key) perms[i].RequiresPIN = auth.DefaultPermissionRequiresPIN(perms[i].Key) } return db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "key"}}, DoUpdates: clause.AssignmentColumns([]string{"name", "description", "module"}), }).Create(&perms).Error } // SeedRolePermissions inserts default role-permission mappings and ensures admin has all permissions. func SeedRolePermissions(db *gorm.DB) error { type rolePerm struct { RoleName string PermKey string } mappings := []rolePerm{ // staff: read-only for users/roles/permissions {RoleName: "staff", PermKey: "user.read"}, {RoleName: "staff", PermKey: "role.read"}, {RoleName: "staff", PermKey: "role.permission.read"}, {RoleName: "staff", PermKey: "helicopter.read"}, {RoleName: "staff", PermKey: "flight_inspection.read"}, {RoleName: "staff", PermKey: "flight.read"}, {RoleName: "staff", PermKey: "reserve_ac.read"}, {RoleName: "staff", PermKey: "facility.read"}, {RoleName: "staff", PermKey: "base.read"}, {RoleName: "staff", PermKey: "hems_base.read"}, {RoleName: "staff", PermKey: "medicine.read"}, {RoleName: "staff", PermKey: "vocation.read"}, {RoleName: "staff", PermKey: "opc.read"}, {RoleName: "staff", PermKey: "forces_present.read"}, {RoleName: "staff", PermKey: "no_icao_code.read"}, {RoleName: "staff", PermKey: "health_insurance_companies.read"}, {RoleName: "staff", PermKey: "federal_state.read"}, {RoleName: "staff", PermKey: "icao.read"}, {RoleName: "staff", PermKey: "country.read"}, {RoleName: "staff", PermKey: "master_settings.read"}, {RoleName: "staff", PermKey: "file_manager.read"}, {RoleName: "staff", PermKey: "duty_roster.read"}, {RoleName: "staff", PermKey: "air_rescuer_checklist.read"}, {RoleName: "staff", PermKey: "flight_prep_check.create"}, {RoleName: "staff", PermKey: "flight_prep_check.read"}, {RoleName: "staff", PermKey: "flight_prep_check.update"}, {RoleName: "staff", PermKey: "helicopter_usage.read"}, {RoleName: "staff", PermKey: "after_flight.read"}, {RoleName: "staff", PermKey: "audit.read"}, {RoleName: "staff", PermKey: "insurance_patient_data.read"}, {RoleName: "staff", PermKey: "patient_data.read"}, {RoleName: "staff", PermKey: "operation.read"}, {RoleName: "staff", PermKey: "operation_category.read"}, {RoleName: "staff", PermKey: "hems_operation.read"}, {RoleName: "pilot", PermKey: "flight.read"}, {RoleName: "pilot", PermKey: "duty_roster.read"}, } if err := assignAllPermissionsToRole(db, "admin"); err != nil { return err } for _, m := range mappings { if err := assignPermissionToRole(db, m.RoleName, m.PermKey); err != nil { return err } } return nil } func assignAllPermissionsToRole(db *gorm.DB, roleName string) error { var role auth.Role if err := db.Where("name = ?", roleName).First(&role).Error; err != nil { return err } var perms []auth.Permission if err := db.Find(&perms).Error; err != nil { return err } for _, perm := range perms { rp := auth.RolePermission{RoleID: role.ID, PermissionID: perm.ID} if err := db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "role_id"}, {Name: "permission_id"}}, DoNothing: true, }).Create(&rp).Error; err != nil { return err } } return nil } func assignPermissionToRole(db *gorm.DB, roleName, permKey string) error { var role auth.Role if err := db.Where("name = ?", roleName).First(&role).Error; err != nil { return err } var perm auth.Permission if err := db.Where("`key` = ?", permKey).First(&perm).Error; err != nil { return err } rp := auth.RolePermission{RoleID: role.ID, PermissionID: perm.ID} return db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "role_id"}, {Name: "permission_id"}}, DoNothing: true, }).Create(&rp).Error } // SeedBaseCategories inserts default base categories if they don't exist. func SeedBaseCategories(db *gorm.DB) error { categories := []base.BaseCategory{ {Key: base.CategoryKeyRegular, Name: "Regular Base"}, {Key: base.CategoryKeyHEMS, Name: "HEMS Base"}, } return db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "key"}}, DoUpdates: clause.AssignmentColumns([]string{"name"}), }).Create(&categories).Error } // SeedRosterUsers inserts sample users for duty roster assignment in development. // This seed is idempotent by email and keeps role assignment synced via user_roles. func SeedRosterUsers(db *gorm.DB) error { type rosterSeed struct { Email string FirstName string LastName string MobilePhone string RoleCode string } seeds := []rosterSeed{ {Email: "pilot.main@wucher.local", FirstName: "Pilot", LastName: "Main", MobilePhone: "+620001", RoleCode: auth.RoleCodePilot}, {Email: "pilot.additional@wucher.local", FirstName: "Pilot", LastName: "Additional", MobilePhone: "+620002", RoleCode: auth.RoleCodePilot}, {Email: "doctor.main@wucher.local", FirstName: "Doctor", LastName: "Main", MobilePhone: "+620003", RoleCode: auth.RoleCodeDoctor}, {Email: "rescuer.main@wucher.local", FirstName: "Rescuer", LastName: "Main", MobilePhone: "+620004", RoleCode: auth.RoleCodeAirRescuer}, {Email: "helper.main@wucher.local", FirstName: "Helper", LastName: "Main", MobilePhone: "+620005", RoleCode: auth.RoleCodeFlightAssistant}, } return db.Transaction(func(tx *gorm.DB) error { roleKeys := make([]string, 0, len(seeds)) seenRole := make(map[string]struct{}, len(seeds)) for i := range seeds { k := strings.ToLower(strings.TrimSpace(seeds[i].RoleCode)) if k == "" { continue } if _, ok := seenRole[k]; ok { continue } seenRole[k] = struct{}{} roleKeys = append(roleKeys, k) } var roles []auth.Role if err := tx.Where("LOWER(code) IN ? OR LOWER(name) IN ?", roleKeys, roleKeys).Find(&roles).Error; err != nil { return err } roleIDByCode := make(map[string][]byte, len(roles)) for i := range roles { code := strings.ToLower(strings.TrimSpace(roles[i].Code)) if code == "" { code = strings.ToLower(strings.TrimSpace(roles[i].Name)) } if code == "" { continue } roleIDByCode[code] = append([]byte(nil), roles[i].ID...) } now := time.Now().UTC() for i := range seeds { roleCode := strings.ToLower(strings.TrimSpace(seeds[i].RoleCode)) roleID := roleIDByCode[roleCode] if len(roleID) != 16 { continue } row := auth.User{ Email: strings.ToLower(strings.TrimSpace(seeds[i].Email)), Username: nil, FirstName: strings.TrimSpace(seeds[i].FirstName), LastName: strings.TrimSpace(seeds[i].LastName), MobilePhone: strings.TrimSpace(seeds[i].MobilePhone), Timezone: "UTC", RoleID: roleID, IsActive: true, EmailVerifiedAt: &now, } if err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "email"}}, DoUpdates: clause.Assignments(map[string]any{ "first_name": row.FirstName, "last_name": row.LastName, "mobile_phone": row.MobilePhone, "timezone": row.Timezone, "role_id": row.RoleID, "is_active": row.IsActive, "email_verified_at": row.EmailVerifiedAt, "updated_at": now, }), }).Create(&row).Error; err != nil { return err } var created auth.User if err := tx.Where("email = ?", row.Email).Take(&created).Error; err != nil { return err } link := auth.UserRole{ UserID: created.ID, RoleID: roleID, } if err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "user_id"}, {Name: "role_id"}}, DoNothing: true, }).Create(&link).Error; err != nil { return err } } return nil }) }