@intranefr/superbackend 1.6.7 → 1.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl +0 -0
  2. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl.meta.json +1 -0
  3. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl +1 -0
  4. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl.meta.json +1 -0
  5. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl +2 -0
  6. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl.meta.json +1 -0
  7. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl +3 -0
  8. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl.meta.json +1 -0
  9. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl +4 -0
  10. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl.meta.json +1 -0
  11. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl +5 -0
  12. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl.meta.json +1 -0
  13. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl +6 -0
  14. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl.meta.json +1 -0
  15. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl +7 -0
  16. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl.meta.json +1 -0
  17. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl +7 -0
  18. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl.meta.json +1 -0
  19. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl +7 -0
  20. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl.meta.json +1 -0
  21. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl +7 -0
  22. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl.meta.json +1 -0
  23. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl +7 -0
  24. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl.meta.json +1 -0
  25. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl +7 -0
  26. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl.meta.json +1 -0
  27. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl +7 -0
  28. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl.meta.json +1 -0
  29. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl +7 -0
  30. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl.meta.json +1 -0
  31. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl +8 -0
  32. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl.meta.json +1 -0
  33. package/.beads/config.yaml +4 -0
  34. package/.beads/issues.jsonl +8 -0
  35. package/.beads/metadata.json +4 -0
  36. package/.env.example +8 -0
  37. package/autochangelog/.env.example +36 -0
  38. package/autochangelog/README.md +412 -0
  39. package/autochangelog/config/database.js +27 -0
  40. package/autochangelog/package.json +47 -0
  41. package/autochangelog/public/landing.html +581 -0
  42. package/autochangelog/server.js +104 -0
  43. package/autochangelog/src/app.js +181 -0
  44. package/autochangelog/src/config/database.js +26 -0
  45. package/autochangelog/src/controllers/auth.js +488 -0
  46. package/autochangelog/src/controllers/changelog.js +682 -0
  47. package/autochangelog/src/controllers/project.js +580 -0
  48. package/autochangelog/src/controllers/repository.js +780 -0
  49. package/autochangelog/src/middleware/auth.js +386 -0
  50. package/autochangelog/src/models/Changelog.js +443 -0
  51. package/autochangelog/src/models/Project.js +226 -0
  52. package/autochangelog/src/models/Repository.js +366 -0
  53. package/autochangelog/src/models/User.js +223 -0
  54. package/autochangelog/src/routes/auth.routes.js +32 -0
  55. package/autochangelog/src/routes/changelog.routes.js +42 -0
  56. package/autochangelog/src/routes/github-auth.routes.js +102 -0
  57. package/autochangelog/src/routes/project.routes.js +50 -0
  58. package/autochangelog/src/routes/repository.routes.js +54 -0
  59. package/autochangelog/src/services/changelog.js +722 -0
  60. package/autochangelog/src/services/github.js +243 -0
  61. package/autochangelog/utils/logger.js +77 -0
  62. package/autochangelog/views/404.ejs +18 -0
  63. package/autochangelog/views/dashboard.ejs +596 -0
  64. package/autochangelog/views/index.ejs +231 -0
  65. package/autochangelog/views/layouts/main.ejs +44 -0
  66. package/autochangelog/views/login.ejs +104 -0
  67. package/autochangelog/views/partials/footer.ejs +20 -0
  68. package/autochangelog/views/partials/navbar.ejs +51 -0
  69. package/autochangelog/views/register.ejs +109 -0
  70. package/autochangelog-cli/README.md +266 -0
  71. package/autochangelog-cli/bin/autochangelog +120 -0
  72. package/autochangelog-cli/package.json +46 -0
  73. package/autochangelog-cli/src/cli/commands/auth.js +291 -0
  74. package/autochangelog-cli/src/cli/commands/changelog.js +619 -0
  75. package/autochangelog-cli/src/cli/commands/project.js +427 -0
  76. package/autochangelog-cli/src/cli/commands/repo.js +557 -0
  77. package/autochangelog-cli/src/cli/commands/stats.js +706 -0
  78. package/autochangelog-cli/src/cli/utils/config.js +277 -0
  79. package/autochangelog-cli/src/cli/utils/errors.js +307 -0
  80. package/autochangelog-cli/src/cli/utils/logger.js +75 -0
  81. package/autochangelog-cli/src/cli/utils/output.js +357 -0
  82. package/package.json +9 -3
  83. package/plugins/supercli/README.md +108 -0
  84. package/plugins/supercli/plugin.json +123 -0
  85. package/server.js +1 -1
  86. package/src/cli/api.js +380 -0
  87. package/src/cli/direct/agent-utils.js +61 -0
  88. package/src/cli/direct/cli-utils.js +112 -0
  89. package/src/cli/direct/data-seeding.js +307 -0
  90. package/src/cli/direct/db-admin.js +84 -0
  91. package/src/cli/direct/db-advanced.js +372 -0
  92. package/src/cli/direct/db-utils.js +558 -0
  93. package/src/cli/direct/help.js +195 -0
  94. package/src/cli/direct/migration.js +107 -0
  95. package/src/cli/direct/rbac-advanced.js +132 -0
  96. package/src/cli/direct/resources-additional.js +400 -0
  97. package/src/cli/direct/resources-cms-advanced.js +173 -0
  98. package/src/cli/direct/resources-cms.js +247 -0
  99. package/src/cli/direct/resources-core.js +253 -0
  100. package/src/cli/direct/resources-execution.js +367 -0
  101. package/src/cli/direct/resources-health.js +152 -0
  102. package/src/cli/direct/resources-integrations.js +182 -0
  103. package/src/cli/direct/resources-logs.js +204 -0
  104. package/src/cli/direct/resources-org-rbac.js +187 -0
  105. package/src/cli/direct/resources-system.js +236 -0
  106. package/src/cli/direct.js +556 -0
  107. package/src/controllers/admin.controller.js +4 -0
  108. package/src/controllers/auth.controller.js +148 -1
  109. package/src/controllers/waitingList.controller.js +130 -1
  110. package/src/models/RbacRole.js +1 -1
  111. package/src/models/User.js +39 -5
  112. package/src/routes/auth.routes.js +6 -0
  113. package/src/routes/waitingList.routes.js +12 -2
  114. package/src/routes/waitingListAdmin.routes.js +3 -0
  115. package/src/services/email.service.js +1 -0
  116. package/src/services/github.service.js +255 -0
  117. package/src/services/rateLimiter.service.js +29 -1
  118. package/src/services/waitingListJson.service.js +32 -3
  119. package/views/admin-waiting-list.ejs +386 -3
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Help text for the direct CLI
5
+ */
6
+
7
+ const { colorize } = require('./cli-utils');
8
+
9
+ function printHelp() {
10
+ console.log(`
11
+ ${colorize("bold", "SuperBackend Direct CLI")}
12
+
13
+ ${colorize("bold", "Usage:")}
14
+ node src/cli/direct.js <resource> <command> [options]
15
+
16
+ ${colorize("bold", "Resources & Commands:")}
17
+
18
+ ${colorize("cyan", "agents")} - Manage AI agents
19
+ list, get, create, update, delete
20
+
21
+ ${colorize("cyan", "settings")} - Manage global settings
22
+ list, get, create, update, delete
23
+
24
+ ${colorize("cyan", "users")} - Manage users
25
+ list, get, create, update, delete, disable, enable
26
+
27
+ ${colorize("cyan", "json-configs")} - Manage JSON configs
28
+ list, get, create, update, delete
29
+
30
+ ${colorize("cyan", "blog-posts")} - Manage blog posts
31
+ list, get, create, update, delete, publish, unpublish
32
+
33
+ ${colorize("cyan", "orgs")} - Manage organizations
34
+ list, get, create, update, delete
35
+
36
+ ${colorize("cyan", "crons")} - Manage cron jobs
37
+ list, get, create, delete, enable, disable
38
+
39
+ ${colorize("cyan", "errors")} - Manage error logs
40
+ list, get, delete, clear
41
+
42
+ ${colorize("cyan", "scripts")} - Manage script definitions
43
+ list, get, create, delete
44
+
45
+ ${colorize("cyan", "workflows")} - Manage workflows
46
+ list, get, create, delete, enable, disable
47
+
48
+ ${colorize("cyan", "health-checks")} - Manage health checks
49
+ list, get, create, delete
50
+
51
+ ${colorize("cyan", "pages")} - Manage pages
52
+ list, get, create, update, delete
53
+
54
+ ${colorize("cyan", "assets")} - Manage assets
55
+ list, get, delete, clear
56
+
57
+ ${colorize("cyan", "forms")} - Manage form submissions
58
+ list, get, delete, clear
59
+
60
+ ${colorize("cyan", "i18n")} - Manage i18n entries
61
+ list, get, create, delete
62
+
63
+ ${colorize("cyan", "notifications")} - Manage notifications
64
+ list, get, delete, clear
65
+
66
+ ${colorize("cyan", "rbac-roles")} - Manage RBAC roles
67
+ list, get, create, delete
68
+
69
+ ${colorize("cyan", "rbac-groups")} - Manage RBAC groups
70
+ list, get, create, delete
71
+
72
+ ${colorize("cyan", "invites")} - Manage invites
73
+ list, get, create, delete, clear
74
+
75
+ ${colorize("cyan", "waiting-list")} - Manage waiting list
76
+ list, delete, clear
77
+
78
+ ${colorize("cyan", "cache")} - Manage cache entries
79
+ list, get, delete, clear
80
+
81
+ ${colorize("cyan", "audit-logs")} - Manage audit logs
82
+ list, get, clear
83
+
84
+ ${colorize("cyan", "db-stats")} - Database statistics
85
+ ${colorize("cyan", "db-indexes")} - Database indexes
86
+ ${colorize("cyan", "db-cleanup")} - Database cleanup
87
+
88
+ ${colorize("cyan", "experiments")} - Manage experiments
89
+ ${colorize("cyan", "experiment-assignments")} - Experiment assignments
90
+
91
+ ${colorize("cyan", "telegram")} - Manage Telegram bots
92
+ ${colorize("cyan", "rate-limits")} - Manage rate limits
93
+ ${colorize("cyan", "console-logs")} - Manage console logs
94
+ ${colorize("cyan", "activity-logs")} - Manage activity logs
95
+ ${colorize("cyan", "email-logs")} - Manage email logs
96
+ ${colorize("cyan", "webhooks")} - Manage webhooks
97
+
98
+ ${colorize("cyan", "stripe-items")} - Stripe catalog items
99
+ ${colorize("cyan", "stripe-events")} - Stripe webhook events
100
+
101
+ ${colorize("cyan", "demo-projects")} - Demo projects
102
+ ${colorize("cyan", "demo-steps")} - Demo steps
103
+
104
+ ${colorize("cyan", "external-dbs")} - External DB connections
105
+ ${colorize("cyan", "org-members")} - Organization members
106
+
107
+ ${colorize("cyan", "page-collections")} - Page collections
108
+ ${colorize("cyan", "block-definitions")} - Block definitions
109
+ ${colorize("cyan", "context-blocks")} - Context blocks
110
+ ${colorize("cyan", "ui-components")} - UI components
111
+ ${colorize("cyan", "headless-models")} - Headless models
112
+ ${colorize("cyan", "headless-tokens")} - Headless API tokens
113
+
114
+ ${colorize("cyan", "blog-automation-locks")} - Blog automation locks
115
+ ${colorize("cyan", "blog-automation-runs")} - Blog automation runs
116
+
117
+ ${colorize("cyan", "cron-executions")} - Cron execution history
118
+ ${colorize("cyan", "workflow-executions")} - Workflow executions
119
+ ${colorize("cyan", "script-runs")} - Script execution history
120
+
121
+ ${colorize("cyan", "health-incidents")} - Health incidents
122
+ ${colorize("cyan", "health-attempts")} - Health auto-heal attempts
123
+
124
+ ${colorize("cyan", "error-aggregates")} - Error aggregates
125
+ ${colorize("cyan", "metric-buckets")} - Metric buckets
126
+
127
+ ${colorize("cyan", "virtual-ejs-files")} - Virtual EJS files
128
+ ${colorize("cyan", "virtual-ejs-groups")} - Virtual EJS groups
129
+ ${colorize("cyan", "markdowns")} - Manage markdowns
130
+
131
+ ${colorize("cyan", "batch-delete")} - Batch delete documents
132
+ ${colorize("cyan", "batch-update")} - Batch update documents
133
+ ${colorize("cyan", "collection-count")} - Count documents
134
+ ${colorize("cyan", "collection-schema")} - Show collection schema
135
+ ${colorize("cyan", "export-collection")} - Export collection to JSON
136
+
137
+ ${colorize("cyan", "find-duplicates")} - Find duplicate documents
138
+ ${colorize("cyan", "remove-duplicates")} - Remove duplicate documents
139
+ ${colorize("cyan", "validate-refs")} - Validate references
140
+ ${colorize("cyan", "repair-refs")} - Repair broken references
141
+
142
+ ${colorize("cyan", "add-index")} - Add index to collection
143
+ ${colorize("cyan", "drop-index")} - Drop index from collection
144
+ ${colorize("cyan", "reindex")} - Rebuild collection indexes
145
+ ${colorize("cyan", "compact")} - Compact collection
146
+ ${colorize("cyan", "validate-collection")} - Validate collection integrity
147
+ ${colorize("cyan", "rename-collection")} - Rename collection
148
+ ${colorize("cyan", "list-collections")} - List all collections
149
+ ${colorize("cyan", "create-collection")} - Create new collection
150
+ ${colorize("cyan", "drop-collection")} - Drop collection
151
+
152
+ ${colorize("cyan", "db-info")} - Database server info
153
+ ${colorize("cyan", "db-users")} - Database users
154
+ ${colorize("cyan", "slow-queries")} - Find slow queries
155
+ ${colorize("cyan", "enable-profiling")} - Enable query profiling
156
+ ${colorize("cyan", "disable-profiling")} - Disable query profiling
157
+
158
+ ${colorize("cyan", "user-permissions")} - Show user permissions
159
+ ${colorize("cyan", "grant-role")} - Grant role to user
160
+ ${colorize("cyan", "revoke-role")} - Revoke role from user
161
+ ${colorize("cyan", "group-members")} - Show group members
162
+ ${colorize("cyan", "add-to-group")} - Add user to group
163
+ ${colorize("cyan", "remove-from-group")} - Remove user from group
164
+
165
+ ${colorize("cyan", "agent-stats")} - Agent statistics
166
+ ${colorize("cyan", "agent-sessions")} - List agent sessions
167
+ ${colorize("cyan", "clear-agent-sessions")} - Clear old sessions
168
+
169
+ ${colorize("cyan", "migration-status")} - Migration status check
170
+ ${colorize("cyan", "add-timestamps")} - Add timestamps to docs
171
+ ${colorize("cyan", "data-digest")} - Database digest report
172
+
173
+ ${colorize("bold", "Options:")}
174
+ --name NAME Resource name
175
+ --model MODEL AI model name (for agents)
176
+ --key KEY Setting key / Collection name / User ID
177
+ --value VALUE Value (context-dependent)
178
+ --description DESC Description / Additional parameter
179
+ --email EMAIL User email
180
+ --password PASSWORD User password
181
+ --role ROLE User role
182
+ --alias ALIAS JSON config alias
183
+ --json JSON JSON config data
184
+ --output FORMAT Output: json, text, table (default: json)
185
+ --quiet Only output data
186
+ --verbose Show additional details
187
+ -h, --help Show this help
188
+
189
+ ${colorize("bold", "Environment Variables:")}
190
+ MONGODB_URI MongoDB connection string
191
+ MODE Environment mode (loads .env.MODE)
192
+ `);
193
+ }
194
+
195
+ module.exports = { printHelp };
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Migration utilities: migration-status, add-timestamps, data-digest
5
+ */
6
+
7
+ const mongoose = require("mongoose");
8
+
9
+ const migrationStatus = {
10
+ async execute(options, context) {
11
+ if (options.command && options.command !== "execute") return;
12
+ const db = context.db;
13
+ const collections = await db.listCollections().toArray();
14
+ const status = {};
15
+
16
+ for (const coll of collections) {
17
+ const count = await db.collection(coll.name).countDocuments();
18
+ const sample = await db.collection(coll.name).findOne();
19
+ status[coll.name] = {
20
+ count,
21
+ hasCreatedAt: sample?.createdAt ? true : false,
22
+ hasUpdatedAt: sample?.updatedAt ? true : false,
23
+ };
24
+ }
25
+
26
+ return { totalCollections: collections.length, status };
27
+ },
28
+ };
29
+
30
+ const addTimestamps = {
31
+ async execute(options, context) {
32
+ const db = context.db;
33
+ const collectionName = options.key;
34
+ const dryRun = options.value === "dry";
35
+
36
+ if (!collectionName) throw new Error("--key (collection name) is required");
37
+
38
+ const collection = db.collection(collectionName);
39
+ const docs = await collection
40
+ .find({
41
+ $or: [
42
+ { createdAt: { $exists: false } },
43
+ { updatedAt: { $exists: false } },
44
+ ],
45
+ })
46
+ .toArray();
47
+
48
+ if (dryRun) {
49
+ return {
50
+ collection: collectionName,
51
+ dryRun: true,
52
+ docsToUpdate: docs.length,
53
+ samples: docs.slice(0, 5).map((d) => ({ _id: d._id })),
54
+ };
55
+ }
56
+
57
+ let updatedCount = 0;
58
+ const now = new Date();
59
+
60
+ for (const doc of docs) {
61
+ await collection.updateOne(
62
+ { _id: doc._id },
63
+ {
64
+ $set: {
65
+ createdAt: doc.createdAt || now,
66
+ updatedAt: doc.updatedAt || now,
67
+ },
68
+ },
69
+ );
70
+ updatedCount++;
71
+ }
72
+
73
+ return { collection: collectionName, updatedCount, totalDocs: docs.length };
74
+ },
75
+ };
76
+
77
+ const dataDigest = {
78
+ async execute(options, context) {
79
+ if (options.command && options.command !== "execute") return;
80
+ const db = context.db;
81
+ const collections = await db.listCollections().toArray();
82
+
83
+ const digest = {
84
+ timestamp: new Date().toISOString(),
85
+ totalCollections: collections.length,
86
+ totalDocuments: 0,
87
+ totalSize: 0,
88
+ collections: [],
89
+ };
90
+
91
+ for (const coll of collections) {
92
+ const stats = await db.collection(coll.name).stats();
93
+ digest.totalDocuments += stats.count;
94
+ digest.totalSize += stats.size;
95
+ digest.collections.push({
96
+ name: coll.name,
97
+ count: stats.count,
98
+ size: stats.size,
99
+ storageSize: stats.storageSize,
100
+ });
101
+ }
102
+
103
+ return digest;
104
+ },
105
+ };
106
+
107
+ module.exports = { migrationStatus, addTimestamps, dataDigest };
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * RBAC Advanced: user-permissions, grant-role, revoke-role, group-members, add-to-group, remove-from-group
5
+ */
6
+
7
+ const mongoose = require('mongoose');
8
+
9
+ const userPermissions = {
10
+ async execute(options) {
11
+ const User = mongoose.model('User');
12
+ const RbacUserRole = mongoose.model('RbacUserRole');
13
+ const RbacRole = mongoose.model('RbacRole');
14
+
15
+ const userId = options.key;
16
+ if (!userId) throw new Error('--key (user ID) is required');
17
+
18
+ const user = await User.findById(userId).lean();
19
+ if (!user) return { error: 'User not found' };
20
+
21
+ const userRoles = await RbacUserRole.find({ userId }).populate('roleId').lean();
22
+ const roles = userRoles.map(ur => ur.roleId);
23
+
24
+ return { userId, email: user.email, role: user.role, rbacRoles: roles.map(r => ({ name: r.name, description: r.description })) };
25
+ },
26
+ };
27
+
28
+ const grantRole = {
29
+ async execute(options) {
30
+ const RbacUserRole = mongoose.model('RbacUserRole');
31
+ const RbacRole = mongoose.model('RbacRole');
32
+
33
+ const userId = options.key;
34
+ const roleName = options.value;
35
+
36
+ if (!userId) throw new Error('--key (user ID) is required');
37
+ if (!roleName) throw new Error('--value (role name) is required');
38
+
39
+ const role = await RbacRole.findOne({ name: roleName });
40
+ if (!role) throw new Error(`Role '${roleName}' not found`);
41
+
42
+ const existing = await RbacUserRole.findOne({ userId, roleId: role._id });
43
+ if (existing) return { message: 'User already has this role', userId, roleId: role._id };
44
+
45
+ await RbacUserRole.create({ userId, roleId: role._id });
46
+ return { success: true, userId, roleId: role._id, roleName };
47
+ },
48
+ };
49
+
50
+ const revokeRole = {
51
+ async execute(options) {
52
+ const RbacUserRole = mongoose.model('RbacUserRole');
53
+ const RbacRole = mongoose.model('RbacRole');
54
+
55
+ const userId = options.key;
56
+ const roleName = options.value;
57
+
58
+ if (!userId) throw new Error('--key (user ID) is required');
59
+ if (!roleName) throw new Error('--value (role name) is required');
60
+
61
+ const role = await RbacRole.findOne({ name: roleName });
62
+ if (!role) throw new Error(`Role '${roleName}' not found`);
63
+
64
+ const result = await RbacUserRole.deleteOne({ userId, roleId: role._id });
65
+ return { success: result.deletedCount > 0, userId, roleId: role._id, roleName };
66
+ },
67
+ };
68
+
69
+ const groupMembers = {
70
+ async execute(options) {
71
+ const RbacGroup = mongoose.model('RbacGroup');
72
+ const RbacGroupMember = mongoose.model('RbacGroupMember');
73
+
74
+ const groupId = options.key;
75
+
76
+ if (!groupId) {
77
+ const groups = await RbacGroup.find().lean();
78
+ const result = [];
79
+ for (const group of groups) {
80
+ const members = await RbacGroupMember.find({ groupId }).populate('userId').lean();
81
+ result.push({
82
+ groupId: group._id,
83
+ name: group.name,
84
+ memberCount: members.length,
85
+ members: members.map(m => ({ userId: m.userId?._id, email: m.userId?.email })),
86
+ });
87
+ }
88
+ return { groups: result };
89
+ }
90
+
91
+ const group = await RbacGroup.findById(groupId).lean();
92
+ if (!group) throw new Error('Group not found');
93
+
94
+ const members = await RbacGroupMember.find({ groupId }).populate('userId').lean();
95
+ return { groupId, name: group.name, members: members.map(m => ({ userId: m.userId?._id, email: m.userId?.email })) };
96
+ },
97
+ };
98
+
99
+ const addToGroup = {
100
+ async execute(options) {
101
+ const RbacGroupMember = mongoose.model('RbacGroupMember');
102
+
103
+ const groupId = options.key;
104
+ const userId = options.value;
105
+
106
+ if (!groupId) throw new Error('--key (group ID) is required');
107
+ if (!userId) throw new Error('--value (user ID) is required');
108
+
109
+ const existing = await RbacGroupMember.findOne({ groupId, userId });
110
+ if (existing) return { message: 'User already in group', groupId, userId };
111
+
112
+ await RbacGroupMember.create({ groupId, userId });
113
+ return { success: true, groupId, userId };
114
+ },
115
+ };
116
+
117
+ const removeFromGroup = {
118
+ async execute(options) {
119
+ const RbacGroupMember = mongoose.model('RbacGroupMember');
120
+
121
+ const groupId = options.key;
122
+ const userId = options.value;
123
+
124
+ if (!groupId) throw new Error('--key (group ID) is required');
125
+ if (!userId) throw new Error('--value (user ID) is required');
126
+
127
+ const result = await RbacGroupMember.deleteOne({ groupId, userId });
128
+ return { success: result.deletedCount > 0, groupId, userId };
129
+ },
130
+ };
131
+
132
+ module.exports = { userPermissions, grantRole, revokeRole, groupMembers, addToGroup, removeFromGroup };