Przeglądaj źródła

fix task subscription

david 6 dni temu
rodzic
commit
7dafdbb61b
9 zmienionych plików z 239 dodań i 164 usunięć
  1. 2 1
      .gitignore
  2. 0 10
      README.md
  3. 20 1
      arp_cli/cmd/login.go
  4. 28 10
      arp_cli/cmd/task.go
  5. 1 0
      graph/converters.go
  6. 2 2
      graph/schema.resolvers.go
  7. 184 50
      init_prod.sql
  8. 0 88
      init_tests.sql
  9. 2 2
      models/models.go

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
 arp.db
 arp
-arp_spec.md
+arp_spec.md
+arp_cli/arp_cli

+ 0 - 10
README.md

@@ -231,13 +231,3 @@ type Mutation {
 | Variable | Default | Description |
 |----------|---------|-------------|
 | `JWT_SECRET` | `your-secret-key-change-in-production` | Secret for JWT signing |
-
-
-## Todo
-@todo: all operations should only be allowed for logged in users
-@todo: Change permissions such that users can
-  * only update, delete the messages they sent
-  * only update, delete notes they created
-@todo: add a UpdatedBy User field to Tasks
-@todo: add cli logging for operations: <timestamp> <user> <action>
-*/

+ 20 - 1
arp_cli/cmd/login.go

@@ -65,13 +65,17 @@ func doLogin(ctx context.Context, cmd *cli.Command) error {
 	if serverURL == "" {
 		prompt := &survey.Input{
 			Message: "Server URL",
-			Help:    "The GraphQL endpoint URL (e.g., http://localhost:8080/query)",
+			Default: "http://localhost:8080",
+			Help:    "The ARP server URL (e.g., http://localhost:8080)",
 		}
 		if err := survey.AskOne(prompt, &serverURL, survey.WithValidator(survey.Required)); err != nil {
 			return err
 		}
 	}
 
+	// Ensure URL has the /query endpoint
+	serverURL = ensureQueryEndpoint(serverURL)
+
 	// Get email
 	email := cmd.String("email")
 	if email == "" {
@@ -170,3 +174,18 @@ func doLogin(ctx context.Context, cmd *cli.Command) error {
 
 	return nil
 }
+
+// ensureQueryEndpoint ensures the URL ends with /query
+func ensureQueryEndpoint(url string) string {
+	// Remove trailing slash if present
+	for len(url) > 0 && url[len(url)-1] == '/' {
+		url = url[:len(url)-1]
+	}
+
+	// Add /query if not already present
+	if len(url) < 6 || url[len(url)-6:] != "/query" {
+		url = url + "/query"
+	}
+
+	return url
+}

+ 28 - 10
arp_cli/cmd/task.go

@@ -240,7 +240,6 @@ func taskList(ctx context.Context, cmd *cli.Command) error {
 
 	table := tablewriter.NewWriter(os.Stdout)
 	table.Header([]string{"ID", "Title", "Priority", "Status", "Assignee", "Due Date"})
-	
 
 	for _, t := range result.Tasks {
 		status := ""
@@ -559,20 +558,39 @@ func taskWatch(ctx context.Context, cmd *cli.Command) error {
 	fmt.Printf("Watching for task events (type: %s)...\n", eventType)
 	fmt.Println("Press Ctrl+C to stop.")
 
-	var subscription string
+	// GraphQL subscriptions only allow one top-level field per subscription
+	// When watching "all" events, we need to create separate subscriptions
 	switch eventType {
 	case "created":
-		subscription = "subscription { taskCreated { id title priority status { id code label } createdAt } }"
+		subscription := "subscription { taskCreated { id title priority status { id code label } createdAt } }"
+		if err := wsClient.Subscribe("1", subscription, nil); err != nil {
+			return fmt.Errorf("failed to subscribe: %w", err)
+		}
 	case "updated":
-		subscription = "subscription { taskUpdated { id title priority status { id code label } updatedAt } }"
+		subscription := "subscription { taskUpdated { id title priority status { id code label } updatedAt } }"
+		if err := wsClient.Subscribe("1", subscription, nil); err != nil {
+			return fmt.Errorf("failed to subscribe: %w", err)
+		}
 	case "deleted":
-		subscription = "subscription { taskDeleted { id title } }"
+		subscription := "subscription { taskDeleted { id title } }"
+		if err := wsClient.Subscribe("1", subscription, nil); err != nil {
+			return fmt.Errorf("failed to subscribe: %w", err)
+		}
 	default:
-		subscription = "subscription { taskCreated { id title priority status { id code label } createdAt } taskUpdated { id title priority status { id code label } updatedAt } taskDeleted { id title } }"
-	}
-
-	if err := wsClient.Subscribe("1", subscription, nil); err != nil {
-		return fmt.Errorf("failed to subscribe: %w", err)
+		// Subscribe to all three event types with separate subscriptions
+		subscriptions := []struct {
+			id    string
+			query string
+		}{
+			{"1", "subscription { taskCreated { id title priority status { id code label } createdAt } }"},
+			{"2", "subscription { taskUpdated { id title priority status { id code label } updatedAt } }"},
+			{"3", "subscription { taskDeleted { id title } }"},
+		}
+		for _, sub := range subscriptions {
+			if err := wsClient.Subscribe(sub.id, sub.query, nil); err != nil {
+				return fmt.Errorf("failed to subscribe to %s: %w", sub.id, err)
+			}
+		}
 	}
 
 	for {

+ 1 - 0
graph/converters.go

@@ -133,6 +133,7 @@ func convertService(s models.Service) *model.Service {
 		Name:         s.Name,
 		Description:  desc,
 		CreatedByID:  createdByID,
+		CreatedBy:    convertUser(s.CreatedBy),
 		Participants: participants,
 		Tasks:        tasks,
 		CreatedAt:    s.CreatedAt.String(),

+ 2 - 2
graph/schema.resolvers.go

@@ -489,7 +489,7 @@ func (r *mutationResolver) CreateService(ctx context.Context, input model.NewSer
 	}
 
 	// Reload with associations
-	r.DB.Preload("Participants").Preload("Tasks").First(&service, service.ID)
+	r.DB.Preload("CreatedBy").Preload("Participants").Preload("Tasks").First(&service, service.ID)
 
 	logging.LogMutation(ctx, "CREATE", "SERVICE", service.Name)
 	return convertService(service), nil
@@ -542,7 +542,7 @@ func (r *mutationResolver) UpdateService(ctx context.Context, id string, input m
 	}
 
 	// Reload with associations for response
-	r.DB.Preload("Participants").Preload("Tasks").First(&existing, existing.ID)
+	r.DB.Preload("CreatedBy").Preload("Participants").Preload("Tasks").First(&existing, existing.ID)
 
 	logging.LogMutation(ctx, "UPDATE", "SERVICE", existing.Name)
 	return convertService(existing), nil

+ 184 - 50
init_prod.sql

@@ -1,59 +1,193 @@
 -- ARP Initial Data Bootstrap Script
 -- Run this script to set up initial permissions, roles, and an admin user
--- 
--- Usage:
---   sqlite3 arp.db < init.sql
 --
 -- Note: The password hash below is for "secret123" using bcrypt.
--- You can generate a new hash with: 
--- go run -e 'package main; import ("fmt"; "golang.org/x/crypto/bcrypt"); func main() { h, _ := bcrypt.GenerateFromPassword([]byte("your-password"), 10); fmt.Println(string(h)) }'
--- or
--- python3 -c "import bcrypt; print(bcrypt.hashpw(b'your_password', bcrypt.gensalt()).decode())"
+
+-- Enable foreign keys
+PRAGMA foreign_keys = ON;
+
+-- Permissions table (no created_at/updated_at in model)
+CREATE TABLE IF NOT EXISTS permissions (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    code TEXT NOT NULL UNIQUE,
+    description TEXT
+);
+
+-- Roles table (no created_at/updated_at in model)
+CREATE TABLE IF NOT EXISTS roles (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    name TEXT NOT NULL UNIQUE,
+    description TEXT
+);
+
+-- Role-Permission join table
+CREATE TABLE IF NOT EXISTS role_permissions (
+    role_id INTEGER NOT NULL,
+    permission_id INTEGER NOT NULL,
+    PRIMARY KEY (role_id, permission_id),
+    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
+    FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
+);
+
+-- Users table
+CREATE TABLE IF NOT EXISTS users (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    email TEXT NOT NULL UNIQUE,
+    password TEXT NOT NULL,
+    created_at DATETIME,
+    updated_at DATETIME
+);
+
+-- User-Role join table
+CREATE TABLE IF NOT EXISTS user_roles (
+    user_id INTEGER NOT NULL,
+    role_id INTEGER NOT NULL,
+    PRIMARY KEY (user_id, role_id),
+    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
+    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
+);
+
+-- Task Statuses table
+CREATE TABLE IF NOT EXISTS task_statuses (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    code TEXT NOT NULL UNIQUE,
+    label TEXT,
+    created_at DATETIME,
+    updated_at DATETIME
+);
+
+-- Services table
+CREATE TABLE IF NOT EXISTS services (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    name TEXT NOT NULL,
+    description TEXT,
+    created_by_id INTEGER,
+    created_at DATETIME,
+    updated_at DATETIME,
+    FOREIGN KEY (created_by_id) REFERENCES users(id)
+);
+
+-- Service Participants join table
+CREATE TABLE IF NOT EXISTS service_participants (
+    service_id INTEGER NOT NULL,
+    user_id INTEGER NOT NULL,
+    PRIMARY KEY (service_id, user_id),
+    FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,
+    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+);
+
+-- Tasks table
+CREATE TABLE IF NOT EXISTS tasks (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    title TEXT NOT NULL,
+    content TEXT NOT NULL,
+    service_id INTEGER,
+    created_by_id INTEGER NOT NULL,
+    updated_by_id INTEGER NOT NULL,
+    assignee_id INTEGER,
+    status_id INTEGER,
+    due_date DATETIME,
+    priority TEXT,
+    created_at DATETIME,
+    updated_at DATETIME,
+    FOREIGN KEY (service_id) REFERENCES services(id),
+    FOREIGN KEY (created_by_id) REFERENCES users(id),
+    FOREIGN KEY (updated_by_id) REFERENCES users(id),
+    FOREIGN KEY (assignee_id) REFERENCES users(id),
+    FOREIGN KEY (status_id) REFERENCES task_statuses(id) ON UPDATE CASCADE ON DELETE SET NULL
+);
+
+-- Notes table
+CREATE TABLE IF NOT EXISTS notes (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    title TEXT NOT NULL,
+    content TEXT NOT NULL,
+    user_id INTEGER,
+    service_id INTEGER,
+    created_at DATETIME,
+    updated_at DATETIME,
+    FOREIGN KEY (user_id) REFERENCES users(id),
+    FOREIGN KEY (service_id) REFERENCES services(id)
+);
+
+-- Channels table
+CREATE TABLE IF NOT EXISTS channels (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    title TEXT NOT NULL,
+    created_at DATETIME,
+    updated_at DATETIME
+);
+
+-- Conversation Participants join table
+CREATE TABLE IF NOT EXISTS conversation_participants (
+    channel_id INTEGER NOT NULL,
+    user_id INTEGER NOT NULL,
+    PRIMARY KEY (channel_id, user_id),
+    FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE,
+    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+);
+
+-- Messages table
+CREATE TABLE IF NOT EXISTS messages (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    conversation_id INTEGER NOT NULL,
+    sender_id INTEGER NOT NULL,
+    content TEXT NOT NULL,
+    sent_at DATETIME,
+    created_at DATETIME,
+    updated_at DATETIME,
+    FOREIGN KEY (conversation_id) REFERENCES channels(id),
+    FOREIGN KEY (sender_id) REFERENCES users(id)
+);
+
+-- ============================================
+-- INSERT STATEMENTS
+-- ============================================
 
 -- Permissions
-INSERT INTO permissions (id, code, description, created_at, updated_at) VALUES 
-  (1, 'user:create', 'Create users', datetime('now'), datetime('now')),
-  (2, 'user:read', 'Read users', datetime('now'), datetime('now')),
-  (3, 'user:update', 'Update users', datetime('now'), datetime('now')),
-  (4, 'user:delete', 'Delete users', datetime('now'), datetime('now')),
-  (5, 'role:create', 'Create roles', datetime('now'), datetime('now')),
-  (6, 'role:read', 'Read roles', datetime('now'), datetime('now')),
-  (7, 'role:update', 'Update roles', datetime('now'), datetime('now')),
-  (8, 'role:delete', 'Delete roles', datetime('now'), datetime('now')),
-  (9, 'permission:create', 'Create permissions', datetime('now'), datetime('now')),
-  (10, 'permission:read', 'Read permissions', datetime('now'), datetime('now')),
-  (11, 'permission:update', 'Update permissions', datetime('now'), datetime('now')),
-  (12, 'permission:delete', 'Delete permissions', datetime('now'), datetime('now')),
-  (13, 'service:create', 'Create services', datetime('now'), datetime('now')),
-  (14, 'service:read', 'Read services', datetime('now'), datetime('now')),
-  (15, 'service:update', 'Update services', datetime('now'), datetime('now')),
-  (16, 'service:delete', 'Delete services', datetime('now'), datetime('now')),
-  (17, 'task:create', 'Create tasks', datetime('now'), datetime('now')),
-  (18, 'task:read', 'Read tasks', datetime('now'), datetime('now')),
-  (19, 'task:update', 'Update tasks', datetime('now'), datetime('now')),
-  (20, 'task:delete', 'Delete tasks', datetime('now'), datetime('now')),
-  (21, 'note:create', 'Create notes', datetime('now'), datetime('now')),
-  (22, 'note:read', 'Read notes', datetime('now'), datetime('now')),
-  (23, 'note:update', 'Update notes', datetime('now'), datetime('now')),
-  (24, 'note:delete', 'Delete notes', datetime('now'), datetime('now')),
-  (25, 'channel:create', 'Create channels', datetime('now'), datetime('now')),
-  (26, 'channel:read', 'Read channels', datetime('now'), datetime('now')),
-  (27, 'channel:update', 'Update channels', datetime('now'), datetime('now')),
-  (28, 'channel:delete', 'Delete channels', datetime('now'), datetime('now')),
-  (29, 'message:create', 'Create messages', datetime('now'), datetime('now')),
-  (30, 'message:read', 'Read messages', datetime('now'), datetime('now')),
-  (31, 'message:update', 'Update messages', datetime('now'), datetime('now')),
-  (32, 'message:delete', 'Delete messages', datetime('now'), datetime('now')),
-  (33, 'taskstatus:create', 'Create task statuses', datetime('now'), datetime('now')),
-  (34, 'taskstatus:read', 'Read task statuses', datetime('now'), datetime('now')),
-  (35, 'taskstatus:update', 'Update task statuses', datetime('now'), datetime('now')),
-  (36, 'taskstatus:delete', 'Delete task statuses', datetime('now'), datetime('now'));
+INSERT INTO permissions (id, code, description) VALUES 
+  (1, 'user:create', 'Create users'),
+  (2, 'user:read', 'Read users'),
+  (3, 'user:update', 'Update users'),
+  (4, 'user:delete', 'Delete users'),
+  (5, 'role:create', 'Create roles'),
+  (6, 'role:read', 'Read roles'),
+  (7, 'role:update', 'Update roles'),
+  (8, 'role:delete', 'Delete roles'),
+  (9, 'permission:create', 'Create permissions'),
+  (10, 'permission:read', 'Read permissions'),
+  (11, 'permission:update', 'Update permissions'),
+  (12, 'permission:delete', 'Delete permissions'),
+  (13, 'service:create', 'Create services'),
+  (14, 'service:read', 'Read services'),
+  (15, 'service:update', 'Update services'),
+  (16, 'service:delete', 'Delete services'),
+  (17, 'task:create', 'Create tasks'),
+  (18, 'task:read', 'Read tasks'),
+  (19, 'task:update', 'Update tasks'),
+  (20, 'task:delete', 'Delete tasks'),
+  (21, 'note:create', 'Create notes'),
+  (22, 'note:read', 'Read notes'),
+  (23, 'note:update', 'Update notes'),
+  (24, 'note:delete', 'Delete notes'),
+  (25, 'channel:create', 'Create channels'),
+  (26, 'channel:read', 'Read channels'),
+  (27, 'channel:update', 'Update channels'),
+  (28, 'channel:delete', 'Delete channels'),
+  (29, 'message:create', 'Create messages'),
+  (30, 'message:read', 'Read messages'),
+  (31, 'message:update', 'Update messages'),
+  (32, 'message:delete', 'Delete messages'),
+  (33, 'taskstatus:create', 'Create task statuses'),
+  (34, 'taskstatus:read', 'Read task statuses'),
+  (35, 'taskstatus:update', 'Update task statuses'),
+  (36, 'taskstatus:delete', 'Delete task statuses');
 
 -- Roles
-INSERT INTO roles (id, name, description, created_at, updated_at) VALUES 
-  (1, 'admin', 'Administrator with full access', datetime('now'), datetime('now')),
-  (2, 'manager', 'Service manager with task management', datetime('now'), datetime('now')),
-  (3, 'user', 'Regular user with limited access', datetime('now'), datetime('now'));
+INSERT INTO roles (id, name, description) VALUES 
+  (1, 'admin', 'Administrator with full access'),
+  (2, 'manager', 'Service manager with task management'),
+  (3, 'user', 'Regular user with limited access');
 
 -- Role-Permission associations (admin gets all permissions)
 INSERT INTO role_permissions (role_id, permission_id) 
@@ -76,7 +210,7 @@ INSERT INTO role_permissions (role_id, permission_id) VALUES
 -- Admin user (password: secret123)
 -- bcrypt hash generated with cost 10
 INSERT INTO users (id, email, password, created_at, updated_at) VALUES 
-  (1, 'admin@example.com', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', datetime('now'), datetime('now'));
+  (1, 'admin@example.com', '$2a$10$9CNePaChncemsl8ZgMFDfeFm.Rl1K1l8rurgZxVx7C6sbv5tojUDC', datetime('now'), datetime('now'));
 
 -- Associate admin user with admin role
 INSERT INTO user_roles (user_id, role_id) VALUES (1, 1);
@@ -88,4 +222,4 @@ INSERT INTO task_statuses (id, code, label, created_at, updated_at) VALUES
   (3, 'blocked', 'Blocked', datetime('now'), datetime('now')),
   (4, 'review', 'In Review', datetime('now'), datetime('now')),
   (5, 'done', 'Done', datetime('now'), datetime('now')),
-  (6, 'cancelled', 'Cancelled', datetime('now'), datetime('now'));
+  (6, 'cancelled', 'Cancelled', datetime('now'), datetime('now'));

+ 0 - 88
init_tests.sql

@@ -1,88 +0,0 @@
--- ARP Initial Data Bootstrap Script
--- Run this script to set up initial permissions, roles, and an admin user
--- 
--- Usage:
---   sqlite3 arp.db < init.sql
---
--- Note: The password hash below is for "secret123" using bcrypt.
--- You can generate a new hash with: go run -e 'package main; import ("fmt"; "golang.org/x/crypto/bcrypt"); func main() { h, _ := bcrypt.GenerateFromPassword([]byte("your-password"), 10); fmt.Println(string(h)) }'
-
--- Permissions
-INSERT INTO permissions (id, code, description, created_at, updated_at) VALUES 
-  (1, 'user:create', 'Create users', datetime('now'), datetime('now')),
-  (2, 'user:read', 'Read users', datetime('now'), datetime('now')),
-  (3, 'user:update', 'Update users', datetime('now'), datetime('now')),
-  (4, 'user:delete', 'Delete users', datetime('now'), datetime('now')),
-  (5, 'role:create', 'Create roles', datetime('now'), datetime('now')),
-  (6, 'role:read', 'Read roles', datetime('now'), datetime('now')),
-  (7, 'role:update', 'Update roles', datetime('now'), datetime('now')),
-  (8, 'role:delete', 'Delete roles', datetime('now'), datetime('now')),
-  (9, 'permission:create', 'Create permissions', datetime('now'), datetime('now')),
-  (10, 'permission:read', 'Read permissions', datetime('now'), datetime('now')),
-  (11, 'permission:update', 'Update permissions', datetime('now'), datetime('now')),
-  (12, 'permission:delete', 'Delete permissions', datetime('now'), datetime('now')),
-  (13, 'service:create', 'Create services', datetime('now'), datetime('now')),
-  (14, 'service:read', 'Read services', datetime('now'), datetime('now')),
-  (15, 'service:update', 'Update services', datetime('now'), datetime('now')),
-  (16, 'service:delete', 'Delete services', datetime('now'), datetime('now')),
-  (17, 'task:create', 'Create tasks', datetime('now'), datetime('now')),
-  (18, 'task:read', 'Read tasks', datetime('now'), datetime('now')),
-  (19, 'task:update', 'Update tasks', datetime('now'), datetime('now')),
-  (20, 'task:delete', 'Delete tasks', datetime('now'), datetime('now')),
-  (21, 'note:create', 'Create notes', datetime('now'), datetime('now')),
-  (22, 'note:read', 'Read notes', datetime('now'), datetime('now')),
-  (23, 'note:update', 'Update notes', datetime('now'), datetime('now')),
-  (24, 'note:delete', 'Delete notes', datetime('now'), datetime('now')),
-  (25, 'channel:create', 'Create channels', datetime('now'), datetime('now')),
-  (26, 'channel:read', 'Read channels', datetime('now'), datetime('now')),
-  (27, 'channel:update', 'Update channels', datetime('now'), datetime('now')),
-  (28, 'channel:delete', 'Delete channels', datetime('now'), datetime('now')),
-  (29, 'message:create', 'Create messages', datetime('now'), datetime('now')),
-  (30, 'message:read', 'Read messages', datetime('now'), datetime('now')),
-  (31, 'message:update', 'Update messages', datetime('now'), datetime('now')),
-  (32, 'message:delete', 'Delete messages', datetime('now'), datetime('now')),
-  (33, 'taskstatus:create', 'Create task statuses', datetime('now'), datetime('now')),
-  (34, 'taskstatus:read', 'Read task statuses', datetime('now'), datetime('now')),
-  (35, 'taskstatus:update', 'Update task statuses', datetime('now'), datetime('now')),
-  (36, 'taskstatus:delete', 'Delete task statuses', datetime('now'), datetime('now'));
-
--- Roles
-INSERT INTO roles (id, name, description, created_at, updated_at) VALUES 
-  (1, 'admin', 'Administrator with full access', datetime('now'), datetime('now')),
-  (2, 'manager', 'Service manager with task management', datetime('now'), datetime('now')),
-  (3, 'user', 'Regular user with limited access', datetime('now'), datetime('now'));
-
--- Role-Permission associations (admin gets all permissions)
-INSERT INTO role_permissions (role_id, permission_id) 
-SELECT 1, id FROM permissions;
-
--- Manager role permissions (service, task, note operations)
-INSERT INTO role_permissions (role_id, permission_id) VALUES
-  (2, 13), (2, 14), (2, 15), (2, 16), -- service:*
-  (2, 17), (2, 18), (2, 19), (2, 20), -- task:*
-  (2, 21), (2, 22), (2, 23), (2, 24), -- note:*
-  (2, 25), (2, 26), (2, 27), (2, 28), -- channel:*
-  (2, 29), (2, 30), (2, 31), (2, 32), -- message:*
-  (2, 33), (2, 34), (2, 35), (2, 36); -- taskstatus:*
-
--- User role permissions (read-only + create notes/messages)
-INSERT INTO role_permissions (role_id, permission_id) VALUES
-  (3, 2), (3, 6), (3, 10), (3, 14), (3, 18), (3, 22), (3, 26), (3, 30), (3, 34), -- read permissions
-  (3, 21), (3, 29); -- create notes and messages
-
--- Admin user (password: secret123)
--- bcrypt hash generated with cost 10
-INSERT INTO users (id, email, password, created_at, updated_at) VALUES 
-  (1, 'admin@example.com', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', datetime('now'), datetime('now'));
-
--- Associate admin user with admin role
-INSERT INTO user_roles (user_id, role_id) VALUES (1, 1);
-
--- Task Statuses (common workflow states)
-INSERT INTO task_statuses (id, code, label, created_at, updated_at) VALUES 
-  (1, 'open', 'Open', datetime('now'), datetime('now')),
-  (2, 'in_progress', 'In Progress', datetime('now'), datetime('now')),
-  (3, 'blocked', 'Blocked', datetime('now'), datetime('now')),
-  (4, 'review', 'In Review', datetime('now'), datetime('now')),
-  (5, 'done', 'Done', datetime('now'), datetime('now')),
-  (6, 'cancelled', 'Cancelled', datetime('now'), datetime('now'));

+ 2 - 2
models/models.go

@@ -9,7 +9,7 @@ type User struct {
 	ID        uint   `gorm:"primaryKey"`
 	Email     string `gorm:"size:255;not null;uniqueIndex"`
 	Password  string `gorm:"size:255;not null"` // hashed password or credential reference
-	Roles     []Role `gorm:"many2many:user_roles;constraint:OnDelete:CASCADE;"`
+	Roles     []Role `gorm:"many2many:user_roles;"`
 	CreatedAt time.Time
 	UpdatedAt time.Time
 }
@@ -36,7 +36,7 @@ type Role struct {
 	ID          uint         `gorm:"primaryKey"`
 	Name        string       `gorm:"size:100;not null;uniqueIndex"`
 	Description string       `gorm:"size:255"`
-	Permissions []Permission `gorm:"many2many:role_permissions;constraint:OnDelete:CASCADE;"`
+	Permissions []Permission `gorm:"many2many:role_permissions;"`
 }
 
 // Permission is a fine‑grained action that can be allowed/denied.