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.db
 arp
 arp
-arp_spec.md
+arp_spec.md
+arp_cli/arp_cli

+ 0 - 10
README.md

@@ -231,13 +231,3 @@ type Mutation {
 | Variable | Default | Description |
 | Variable | Default | Description |
 |----------|---------|-------------|
 |----------|---------|-------------|
 | `JWT_SECRET` | `your-secret-key-change-in-production` | Secret for JWT signing |
 | `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 == "" {
 	if serverURL == "" {
 		prompt := &survey.Input{
 		prompt := &survey.Input{
 			Message: "Server URL",
 			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 {
 		if err := survey.AskOne(prompt, &serverURL, survey.WithValidator(survey.Required)); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
+	// Ensure URL has the /query endpoint
+	serverURL = ensureQueryEndpoint(serverURL)
+
 	// Get email
 	// Get email
 	email := cmd.String("email")
 	email := cmd.String("email")
 	if email == "" {
 	if email == "" {
@@ -170,3 +174,18 @@ func doLogin(ctx context.Context, cmd *cli.Command) error {
 
 
 	return nil
 	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 := tablewriter.NewWriter(os.Stdout)
 	table.Header([]string{"ID", "Title", "Priority", "Status", "Assignee", "Due Date"})
 	table.Header([]string{"ID", "Title", "Priority", "Status", "Assignee", "Due Date"})
-	
 
 
 	for _, t := range result.Tasks {
 	for _, t := range result.Tasks {
 		status := ""
 		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.Printf("Watching for task events (type: %s)...\n", eventType)
 	fmt.Println("Press Ctrl+C to stop.")
 	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 {
 	switch eventType {
 	case "created":
 	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":
 	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":
 	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:
 	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 {
 	for {

+ 1 - 0
graph/converters.go

@@ -133,6 +133,7 @@ func convertService(s models.Service) *model.Service {
 		Name:         s.Name,
 		Name:         s.Name,
 		Description:  desc,
 		Description:  desc,
 		CreatedByID:  createdByID,
 		CreatedByID:  createdByID,
+		CreatedBy:    convertUser(s.CreatedBy),
 		Participants: participants,
 		Participants: participants,
 		Tasks:        tasks,
 		Tasks:        tasks,
 		CreatedAt:    s.CreatedAt.String(),
 		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
 	// 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)
 	logging.LogMutation(ctx, "CREATE", "SERVICE", service.Name)
 	return convertService(service), nil
 	return convertService(service), nil
@@ -542,7 +542,7 @@ func (r *mutationResolver) UpdateService(ctx context.Context, id string, input m
 	}
 	}
 
 
 	// Reload with associations for response
 	// 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)
 	logging.LogMutation(ctx, "UPDATE", "SERVICE", existing.Name)
 	return convertService(existing), nil
 	return convertService(existing), nil

+ 184 - 50
init_prod.sql

@@ -1,59 +1,193 @@
 -- ARP Initial Data Bootstrap Script
 -- ARP Initial Data Bootstrap Script
 -- Run this script to set up initial permissions, roles, and an admin user
 -- 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.
 -- 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
 -- 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
 -- 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)
 -- Role-Permission associations (admin gets all permissions)
 INSERT INTO role_permissions (role_id, permission_id) 
 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)
 -- Admin user (password: secret123)
 -- bcrypt hash generated with cost 10
 -- bcrypt hash generated with cost 10
 INSERT INTO users (id, email, password, created_at, updated_at) VALUES 
 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
 -- Associate admin user with admin role
 INSERT INTO user_roles (user_id, role_id) VALUES (1, 1);
 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')),
   (3, 'blocked', 'Blocked', datetime('now'), datetime('now')),
   (4, 'review', 'In Review', datetime('now'), datetime('now')),
   (4, 'review', 'In Review', datetime('now'), datetime('now')),
   (5, 'done', 'Done', 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"`
 	ID        uint   `gorm:"primaryKey"`
 	Email     string `gorm:"size:255;not null;uniqueIndex"`
 	Email     string `gorm:"size:255;not null;uniqueIndex"`
 	Password  string `gorm:"size:255;not null"` // hashed password or credential reference
 	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
 	CreatedAt time.Time
 	UpdatedAt time.Time
 	UpdatedAt time.Time
 }
 }
@@ -36,7 +36,7 @@ type Role struct {
 	ID          uint         `gorm:"primaryKey"`
 	ID          uint         `gorm:"primaryKey"`
 	Name        string       `gorm:"size:100;not null;uniqueIndex"`
 	Name        string       `gorm:"size:100;not null;uniqueIndex"`
 	Description string       `gorm:"size:255"`
 	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.
 // Permission is a fine‑grained action that can be allowed/denied.