Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7a9a3fb
fix(converter): rewrite lastVisit => add new key
slaveeks Nov 5, 2025
194716c
Bump version up to 1.2.15
github-actions[bot] Nov 5, 2025
51f6f52
Merge branch 'master' of github.com:codex-team/hawk.api.nodejs into f…
slaveeks Nov 5, 2025
8fe3c15
Merge branch 'fix/conv' of github.com:codex-team/hawk.api.nodejs into…
slaveeks Nov 5, 2025
415c19f
Merge pull request #572 from codex-team/fix/conv
slaveeks Nov 5, 2025
2403596
feat(opt): added index for dailyEvent for groupingTimestamp, lastRepe…
slaveeks Nov 5, 2025
e112dff
Bump version up to 1.2.16
github-actions[bot] Nov 5, 2025
9d33299
run ci
slaveeks Nov 5, 2025
dee36aa
Merge pull request #575 from codex-team/feat/add-daily-events-index
slaveeks Nov 5, 2025
92f183d
feat(users): move membership data to users model
slaveeks Nov 6, 2025
aa6e6ba
Bump version up to 1.2.17
github-actions[bot] Nov 6, 2025
0c0701f
run ci
slaveeks Nov 6, 2025
56b2ae2
Merge branch 'feat/memberships-to-user' of github.com:codex-team/hawk…
slaveeks Nov 6, 2025
e4140b8
fix convertor
slaveeks Nov 6, 2025
c83c288
Merge pull request #579 from codex-team/feat/memberships-to-user
slaveeks Nov 6, 2025
caf14a1
fix(users): return all users workspaces if no ids
slaveeks Nov 6, 2025
5a7f32c
Bump version up to 1.2.18
github-actions[bot] Nov 6, 2025
3a0f56f
rm log
slaveeks Nov 6, 2025
b48a26e
Merge branch 'feat/user-workspaces' of github.com:codex-team/hawk.api…
slaveeks Nov 6, 2025
17be2b0
Merge pull request #580 from codex-team/feat/user-workspaces
slaveeks Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion convertors/set-user-project-last-visit.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function run() {
const userDocument = usersDocuments.find(u => u._id.toString() === userInProject.userId.toString());
if (userDocument) {
const projectId = collectionName.split(':')[1];
await hawkDb.collection('users').updateOne({ _id: userDocument._id }, { $set: { projectsLastVisit: { [projectId]: userInProject.timestamp } } });
await hawkDb.collection('users').updateOne({ _id: userDocument._id }, { $set: { [`projectsLastVisit.${projectId}`]: userInProject.timestamp } });
usersUpdatedCount++;
console.log(`Updated ${usersUpdatedCount}/${usersInProject.length} users in project ${collectionName}.`);
}
Expand Down
53 changes: 53 additions & 0 deletions convertors/set-user-workspaces-membership.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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();

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);

if (!userDocument) {
i++;
continue;
}

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);
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.2.14",
"version": "1.2.18",
"main": "index.ts",
"license": "BUSL-1.1",
"scripts": {
Expand Down
88 changes: 33 additions & 55 deletions src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, {
isPending?: boolean;
}
}>;

/**
* This structure represents how user notifications are stored at the DB (in 'users' collection)
Expand Down Expand Up @@ -124,6 +111,11 @@ export default class UserModel extends AbstractModel<UserDBScheme> 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
Expand Down Expand Up @@ -155,11 +147,6 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
*/
protected collection: Collection<UserDBScheme>;

/**
* Collection of user's workspaces
*/
private membershipCollection: Collection<MembershipDBScheme>;

/**
* Model constructor
* @param modelData - user data
Expand All @@ -174,7 +161,6 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us

super(modelData);

this.membershipCollection = this.dbConnection.collection('membership:' + this._id);
this.collection = this.dbConnection.collection<UserDBScheme>('users');
}

Expand Down Expand Up @@ -339,19 +325,13 @@ export default class UserModel extends AbstractModel<UserDBScheme> 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<object> {
const doc: OptionalId<MembershipDBScheme> = {
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,
};
}
Expand All @@ -361,9 +341,10 @@ export default class UserModel extends AbstractModel<UserDBScheme> 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,
Expand All @@ -375,11 +356,9 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
* @param workspaceId - workspace id to confirm
*/
public async confirmMembership(workspaceId: string): Promise<void> {
await this.membershipCollection.updateOne(
{
workspaceId: new ObjectId(workspaceId),
},
{ $unset: { isPending: '' } }
await this.collection.updateOne(
{ _id: new ObjectId(this._id) },
{ $unset: { [`workspaces.${workspaceId}.isPending`]: '' } }
);
}

Expand All @@ -389,23 +368,22 @@ export default class UserModel extends AbstractModel<UserDBScheme> 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<string[]> {
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();
if (ids.length === 0) {
return Object.keys(this.workspaces);
}

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;
}

/**
Expand Down
9 changes: 9 additions & 0 deletions src/resolvers/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const REPETITIONS_GROUP_HASH_INDEX_NAME = 'groupHash_hashed';
const REPETITIONS_USER_ID_INDEX_NAME = 'userId';
const EVENTS_TIMESTAMP_INDEX_NAME = 'timestamp';
const GROUPING_TIMESTAMP_INDEX_NAME = 'groupingTimestamp';
const GROUPING_TIMESTAMP_AND_LAST_REPETITION_TIME_AND_ID_INDEX_NAME = 'groupingTimestampAndLastRepetitionTimeAndId';
const GROUPING_TIMESTAMP_AND_GROUP_HASH_INDEX_NAME = 'groupingTimestampAndGroupHash';
const MAX_SEARCH_QUERY_LENGTH = 50;

Expand Down Expand Up @@ -117,6 +118,14 @@ module.exports = {
name: GROUPING_TIMESTAMP_AND_GROUP_HASH_INDEX_NAME,
});

await projectDailyEventsCollection.createIndex({
groupingTimestamp: -1,
lastRepetitionTime: -1,
_id: -1,
}, {
name: GROUPING_TIMESTAMP_AND_LAST_REPETITION_TIME_AND_ID_INDEX_NAME,
});

await projectEventsCollection.createIndex({
groupHash: 1,
},
Expand Down
Loading