From 94d6777c4811f36f7e98f2d563a700cc9f127838 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Mon, 23 Mar 2026 05:24:29 -0700 Subject: [PATCH] more seed data --- db/data/009_question_types_seed.sql | 67 +++++++++++++++++++++++++++++ internal/ports/rbac.go | 3 ++ internal/repository/rbac.go | 11 +++++ internal/services/rbac/service.go | 22 ++++------ 4 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 db/data/009_question_types_seed.sql diff --git a/db/data/009_question_types_seed.sql b/db/data/009_question_types_seed.sql new file mode 100644 index 0000000..8bb928d --- /dev/null +++ b/db/data/009_question_types_seed.sql @@ -0,0 +1,67 @@ +-- Seed TRUE_FALSE and SHORT_ANSWER question types +-- Ensures question sets contain non-MCQ questions for end-to-end testing. + +-- ====================================================== +-- TRUE_FALSE questions (stored in questions + question_options) +-- ====================================================== +INSERT INTO questions ( + id, + question_text, + question_type, + difficulty_level, + points, + status, + created_at +) +VALUES + (27, 'The Python interpreter executes Python code top-to-bottom.', 'TRUE_FALSE', 'EASY', 1, 'PUBLISHED', CURRENT_TIMESTAMP) +ON CONFLICT (id) DO NOTHING; + +-- question_options for TRUE_FALSE: use two options with exactly one correct +INSERT INTO question_options (question_id, option_text, option_order, is_correct) +VALUES + (27, 'True', 1, TRUE), + (27, 'False', 2, FALSE) +ON CONFLICT DO NOTHING; + +-- ====================================================== +-- SHORT_ANSWER questions (stored in questions + question_short_answers) +-- ====================================================== +INSERT INTO questions ( + id, + question_text, + question_type, + difficulty_level, + points, + status, + created_at +) +VALUES + (29, 'What keyword is used in Python to define a function?', 'SHORT_ANSWER', 'EASY', 1, 'PUBLISHED', CURRENT_TIMESTAMP) +ON CONFLICT (id) DO NOTHING; + +INSERT INTO question_short_answers (question_id, acceptable_answer, match_type) +VALUES + (29, 'def', 'EXACT') +ON CONFLICT DO NOTHING; + +-- ====================================================== +-- Link new questions into existing question sets +-- Question Set 1: Initial Assessment (set_id = 1, PUBLISHED) +-- Question Set 2: Python Basics Assessment (set_id = 2, PUBLISHED) +-- ====================================================== +INSERT INTO question_set_items (set_id, question_id, display_order) +VALUES + (1, 27, 17), + (1, 29, 18), + (2, 27, 3), + (2, 29, 4) +ON CONFLICT (set_id, question_id) DO NOTHING; + +-- ====================================================== +-- Reset sequences to avoid ID collisions after seeding +-- ====================================================== +SELECT setval(pg_get_serial_sequence('questions', 'id'), COALESCE((SELECT MAX(id) FROM questions), 1), true); +SELECT setval(pg_get_serial_sequence('question_options', 'id'), COALESCE((SELECT MAX(id) FROM question_options), 1), true); +SELECT setval(pg_get_serial_sequence('question_short_answers', 'id'), COALESCE((SELECT MAX(id) FROM question_short_answers), 1), true); + diff --git a/internal/ports/rbac.go b/internal/ports/rbac.go index 8b6f945..e3747a4 100644 --- a/internal/ports/rbac.go +++ b/internal/ports/rbac.go @@ -19,6 +19,9 @@ type RBACStore interface { GetPermissionByKey(ctx context.Context, key string) (domain.Permission, error) SetRolePermissions(ctx context.Context, roleID int64, permissionIDs []int64) error + // AddRolePermissions inserts permissions into role without removing existing ones. + // It is safe to call repeatedly (idempotent) as it relies on ON CONFLICT DO NOTHING. + AddRolePermissions(ctx context.Context, roleID int64, permissionIDs []int64) error GetRolePermissions(ctx context.Context, roleID int64) ([]domain.Permission, error) GetAllRolesWithPermissions(ctx context.Context) (map[string]map[string]struct{}, error) diff --git a/internal/repository/rbac.go b/internal/repository/rbac.go index 3705080..81f9495 100644 --- a/internal/repository/rbac.go +++ b/internal/repository/rbac.go @@ -150,6 +150,17 @@ func (s *Store) SetRolePermissions(ctx context.Context, roleID int64, permission return nil } +func (s *Store) AddRolePermissions(ctx context.Context, roleID int64, permissionIDs []int64) error { + if len(permissionIDs) == 0 { + return nil + } + // Uses ON CONFLICT DO NOTHING at the SQL level (see rbac.sql). + return s.queries.BulkAssignPermissionsToRole(ctx, dbgen.BulkAssignPermissionsToRoleParams{ + RoleID: roleID, + Column2: permissionIDs, + }) +} + func (s *Store) GetRolePermissions(ctx context.Context, roleID int64) ([]domain.Permission, error) { rows, err := s.queries.GetRolePermissions(ctx, roleID) if err != nil { diff --git a/internal/services/rbac/service.go b/internal/services/rbac/service.go index b64e3d7..6eb3227 100644 --- a/internal/services/rbac/service.go +++ b/internal/services/rbac/service.go @@ -76,15 +76,7 @@ func (s *Service) SeedDefaultRolePermissions(ctx context.Context) error { continue } - existing, err := s.store.GetRolePermissions(ctx, role.ID) - if err != nil { - return fmt.Errorf("check existing permissions for %s: %w", roleName, err) - } - if len(existing) > 0 { - s.logger.Info("role already has permissions, skipping seed", "role", roleName, "count", len(existing)) - continue - } - + // Insert missing permissions without wiping existing role permissions. var permIDs []int64 for _, key := range permKeys { perm, err := s.store.GetPermissionByKey(ctx, key) @@ -95,12 +87,14 @@ func (s *Service) SeedDefaultRolePermissions(ctx context.Context) error { permIDs = append(permIDs, perm.ID) } - if len(permIDs) > 0 { - if err := s.store.SetRolePermissions(ctx, role.ID, permIDs); err != nil { - return fmt.Errorf("seed permissions for role %s: %w", roleName, err) - } - s.logger.Info("seeded default permissions for role", "role", roleName, "count", len(permIDs)) + if len(permIDs) == 0 { + continue } + + if err := s.store.AddRolePermissions(ctx, role.ID, permIDs); err != nil { + return fmt.Errorf("seed role permissions for %s: %w", roleName, err) + } + s.logger.Info("ensured default permissions for role", "role", roleName, "count", len(permIDs)) } return nil }