From 92f183db69d031aeb6b733a50300c9b14fe631d5 Mon Sep 17 00:00:00 2001 From: slaveeks Date: Fri, 7 Nov 2025 00:13:56 +0300 Subject: [PATCH 1/4] feat(users): move membership data to users model --- convertors/set-user-workspaces-membership.js | 49 ++++++++++++ src/models/user.ts | 84 +++++++------------- 2 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 convertors/set-user-workspaces-membership.js diff --git a/convertors/set-user-workspaces-membership.js b/convertors/set-user-workspaces-membership.js new file mode 100644 index 00000000..f03fb562 --- /dev/null +++ b/convertors/set-user-workspaces-membership.js @@ -0,0 +1,49 @@ +require('dotenv').config(); +require('process'); +const { setup } = require('./setup'); + +/** + * Method that runs convertor script + */ +async function run() { + const { client, hawkDb } = await setup(); + + const collections = await hawkDb.listCollections({}, { + authorizedCollections: true, + nameOnly: true, + }).toArray(); + + let usersMembershipCollectionsToCheck = collections.filter(col => /^membership:/.test(col.name)).map(col => col.name); + + console.log(`Found ${usersMembershipCollectionsToCheck.length} users membership collections.`); + + const usersDocuments = await hawkDb.collection('users').find({}).toArray(); + + // Convert events + let i = 1; + + for (const collectionName of usersMembershipCollectionsToCheck) { + console.log(`[${i}/${usersMembershipCollectionsToCheck.length}] Processing ${collectionName}`); + + const userId = collectionName.split(':')[1]; + + const userDocument = usersDocuments.find(u => u._id.toString() === userId); + + const memberships = await hawkDb.collection(collectionName).find({}).toArray(); + + for (const membership of memberships) { + const workspaceId = membership.workspaceId.toString(); + const isPending = membership.isPending || false; + await hawkDb.collection('users').updateOne({ _id: userDocument._id }, { $set: { [`workspaces.${workspaceId}`]: { isPending } } }); + } + + i++; + } + + await client.close(); +} + +run().catch(err => { + console.error('❌ Script failed:', err); + process.exit(1); +}); \ No newline at end of file diff --git a/src/models/user.ts b/src/models/user.ts index 1913e3ce..5643e4ea 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -33,22 +33,9 @@ export interface TokensPair { /** * Membership collection DB implementation */ -export interface MembershipDBScheme { - /** - * Document id - */ - _id: ObjectId; - - /** - * User's workspace id - */ - workspaceId: ObjectId; - - /** - * Shows if member is pending - */ +export type MembershipDBScheme = Record; /** * This structure represents how user notifications are stored at the DB (in 'users' collection) @@ -124,6 +111,11 @@ export default class UserModel extends AbstractModel implements Us */ public githubId?: string; + /** + * User's workspaces + */ + public workspaces!: MembershipDBScheme; + /** * User's original password (this field appears only after registration). * Using to send password to user after registration @@ -155,11 +147,6 @@ export default class UserModel extends AbstractModel implements Us */ protected collection: Collection; - /** - * Collection of user's workspaces - */ - private membershipCollection: Collection; - /** * Model constructor * @param modelData - user data @@ -174,7 +161,6 @@ export default class UserModel extends AbstractModel implements Us super(modelData); - this.membershipCollection = this.dbConnection.collection('membership:' + this._id); this.collection = this.dbConnection.collection('users'); } @@ -339,19 +325,13 @@ export default class UserModel extends AbstractModel implements Us * @param workspaceId - user's id to add * @param isPending - if true, mark user's membership as pending */ - public async addWorkspace(workspaceId: string, isPending = false): Promise { - const doc: OptionalId = { - workspaceId: new ObjectId(workspaceId), - }; - - if (isPending) { - doc.isPending = isPending; - } - - const documentId = (await this.membershipCollection.insertOne(doc)).insertedId; + public async addWorkspace(workspaceId: string, isPending = false): Promise<{ workspaceId: string }> { + await this.update( + { _id: new ObjectId(this._id) }, + { [`workspaces.${workspaceId}`]: { isPending } } + ); return { - id: documentId, workspaceId, }; } @@ -361,9 +341,10 @@ export default class UserModel extends AbstractModel implements Us * @param workspaceId - id of workspace to remove */ public async removeWorkspace(workspaceId: string): Promise<{ workspaceId: string }> { - await this.membershipCollection.deleteOne({ - workspaceId: new ObjectId(workspaceId), - }); + await this.collection.updateOne( + { _id: new ObjectId(this._id) }, + { $unset: { [`workspaces.${workspaceId}`]: '' } } + ); return { workspaceId, @@ -375,11 +356,9 @@ export default class UserModel extends AbstractModel implements Us * @param workspaceId - workspace id to confirm */ public async confirmMembership(workspaceId: string): Promise { - await this.membershipCollection.updateOne( - { - workspaceId: new ObjectId(workspaceId), - }, - { $unset: { isPending: '' } } + await this.collection.updateOne( + { _id: new ObjectId(this._id) }, + { $unset: { [`workspaces.${workspaceId}.isPending`]: '' } } ); } @@ -389,23 +368,18 @@ export default class UserModel extends AbstractModel implements Us * @param ids - workspaces id to filter them if there are workspaces that doesn't belong to the user */ public async getWorkspacesIds(ids: (string | ObjectId)[] = []): Promise { - const idsAsObjectId = ids.map(id => new ObjectId(id)); - const searchQuery = ids.length ? { - workspaceId: { - $in: idsAsObjectId, - }, - isPending: { - $ne: true, - }, - } : { - isPending: { - $ne: true, - }, - }; + const res = []; - const membershipDocuments = await this.membershipCollection.find(searchQuery).toArray(); + for (const id of ids) { + const workspaceId = id.toString(); + const workspace = this.workspaces[workspaceId]; + + if (workspace && workspace.isPending !== true) { + res.push(workspaceId); + } + } - return membershipDocuments.map(doc => doc.workspaceId.toString()); + return res; } /** From aa6e6bae5f4f86143cd9878df94fc0481db7fba9 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 21:15:29 +0000 Subject: [PATCH 2/4] Bump version up to 1.2.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 124ecb6d..7c0362ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.2.16", + "version": "1.2.17", "main": "index.ts", "license": "BUSL-1.1", "scripts": { From 0c0701f642d5c6f438cbed9a61fa6d06b61656cb Mon Sep 17 00:00:00 2001 From: slaveeks Date: Fri, 7 Nov 2025 00:20:33 +0300 Subject: [PATCH 3/4] run ci --- convertors/set-user-workspaces-membership.js | 1 - 1 file changed, 1 deletion(-) diff --git a/convertors/set-user-workspaces-membership.js b/convertors/set-user-workspaces-membership.js index f03fb562..b15feb52 100644 --- a/convertors/set-user-workspaces-membership.js +++ b/convertors/set-user-workspaces-membership.js @@ -19,7 +19,6 @@ async function run() { const usersDocuments = await hawkDb.collection('users').find({}).toArray(); - // Convert events let i = 1; for (const collectionName of usersMembershipCollectionsToCheck) { From e4140b84f593229bea632552a6e05dcbb4600138 Mon Sep 17 00:00:00 2001 From: slaveeks Date: Fri, 7 Nov 2025 00:32:49 +0300 Subject: [PATCH 4/4] fix convertor --- convertors/set-user-workspaces-membership.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/convertors/set-user-workspaces-membership.js b/convertors/set-user-workspaces-membership.js index b15feb52..b216fd6e 100644 --- a/convertors/set-user-workspaces-membership.js +++ b/convertors/set-user-workspaces-membership.js @@ -28,6 +28,11 @@ async function run() { const userDocument = usersDocuments.find(u => u._id.toString() === userId); + if (!userDocument) { + i++; + continue; + } + const memberships = await hawkDb.collection(collectionName).find({}).toArray(); for (const membership of memberships) {