Skip to content

Commit f56e8cc

Browse files
authored
Merge pull request #570 from codex-team/feat/rm-user-in-project
feat(opt): rm user-in-project:projectId collections usage
2 parents 366a7e7 + 85e056e commit f56e8cc

File tree

6 files changed

+152
-99
lines changed

6 files changed

+152
-99
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
require('dotenv').config();
2+
require('process');
3+
const { setup } = require('./setup');
4+
5+
/**
6+
* Method that runs convertor script
7+
*/
8+
async function run() {
9+
const { client, hawkDb } = await setup();
10+
11+
const collections = await hawkDb.listCollections({}, {
12+
authorizedCollections: true,
13+
nameOnly: true,
14+
}).toArray();
15+
16+
let usersInProjectCollectionsToCheck = collections.filter(col => /^users-in-project:/.test(col.name)).map(col => col.name);
17+
18+
console.log(`Found ${usersInProjectCollectionsToCheck.length} users in project collections.`);
19+
20+
const usersDocuments = await hawkDb.collection('users').find({}).toArray();
21+
22+
// Convert events
23+
let i = 1;
24+
25+
for (const collectionName of usersInProjectCollectionsToCheck) {
26+
console.log(`[${i}/${usersInProjectCollectionsToCheck.length}] Processing ${collectionName}`);
27+
28+
const usersInProject = await hawkDb.collection(collectionName).find({}).toArray();
29+
30+
console.log(`Found ${usersInProject.length} users in project ${collectionName}.`);
31+
32+
let usersUpdatedCount = 0;
33+
34+
for (const userInProject of usersInProject) {
35+
const userDocument = usersDocuments.find(u => u._id.toString() === userInProject.userId.toString());
36+
if (userDocument) {
37+
const projectId = collectionName.split(':')[1];
38+
await hawkDb.collection('users').updateOne({ _id: userDocument._id }, { $set: { projectsLastVisit: { [projectId]: userInProject.timestamp } } });
39+
usersUpdatedCount++;
40+
console.log(`Updated ${usersUpdatedCount}/${usersInProject.length} users in project ${collectionName}.`);
41+
}
42+
}
43+
44+
i++;
45+
}
46+
47+
await client.close();
48+
}
49+
50+
run().catch(err => {
51+
console.error('❌ Script failed:', err);
52+
process.exit(1);
53+
});

convertors/setup.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const { MongoClient } = require('mongodb');
2+
3+
async function setup() {
4+
const fullUri = process.env.MONGO_HAWK_DB_URL;
5+
6+
// Parse the Mongo URL manually
7+
const mongoUrl = new URL(fullUri);
8+
const hawkDatabaseName = 'hawk';
9+
10+
// Extract query parameters
11+
const queryParams = Object.fromEntries(mongoUrl.searchParams.entries());
12+
13+
// Compose connection options manually
14+
const options = {
15+
useNewUrlParser: true,
16+
useUnifiedTopology: true,
17+
authSource: queryParams.authSource || 'admin',
18+
replicaSet: queryParams.replicaSet || undefined,
19+
tls: queryParams.tls === 'true',
20+
tlsInsecure: queryParams.tlsInsecure === 'true',
21+
// connectTimeoutMS: 3600000,
22+
// socketTimeoutMS: 3600000,
23+
};
24+
25+
// Remove query string from URI
26+
mongoUrl.search = '';
27+
const cleanUri = mongoUrl.toString();
28+
29+
console.log('Connecting to:', cleanUri);
30+
console.log('With options:', options);
31+
32+
const client = new MongoClient(cleanUri, options);
33+
34+
await client.connect();
35+
const hawkDb = client.db(hawkDatabaseName);
36+
37+
console.log(`Connected to database: ${hawkDatabaseName}`);
38+
39+
return { client, hawkDb };
40+
}
41+
42+
module.exports = { setup };
43+
44+

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.2.13",
3+
"version": "1.2.14",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {
@@ -11,6 +11,7 @@
1111
"dev:up": "docker-compose -f docker-compose.dev.yml up -d",
1212
"dev:down": "docker-compose -f docker-compose.dev.yml down",
1313
"build": "tsc",
14+
"convert": "node ./convertors/set-user-project-last-visit.js",
1415
"migrations:create": "docker-compose exec api yarn migrate-mongo create",
1516
"migrations:up": "docker-compose exec api yarn migrate-mongo up",
1617
"migrations:down": "docker-compose exec api yarn migrate-mongo down",

src/models/user.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export interface UserNotificationsDBScheme {
6262
/**
6363
* Types of notifications to receive
6464
*/
65-
whatToReceive: {[key in UserNotificationType]: boolean};
65+
whatToReceive: { [key in UserNotificationType]: boolean };
6666
}
6767

6868
/**
@@ -85,6 +85,11 @@ export enum UserNotificationType {
8585
SystemMessages = 'SystemMessages',
8686
}
8787

88+
/**
89+
* This structure represents how user projects last visit is stored at the DB (in 'users' collection)
90+
*/
91+
type UserProjectsLastVisitDBScheme = Record<string, number>;
92+
8893
/**
8994
* User model
9095
*/
@@ -130,6 +135,11 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
130135
*/
131136
public notifications!: UserNotificationsDBScheme;
132137

138+
/**
139+
* User projects last visit
140+
*/
141+
public projectsLastVisit!: UserProjectsLastVisitDBScheme;
142+
133143
/**
134144
* Saved bank cards for one-click payments
135145
*/
@@ -233,6 +243,33 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
233243
}
234244
}
235245

246+
/**
247+
* Update user's last project visit
248+
*
249+
* @param projectId - project id
250+
* @returns {Promise<number>} - last project visit timestamp
251+
*/
252+
public async updateLastProjectVisit(projectId: string): Promise<number> {
253+
const time = Date.now() / 1000;
254+
255+
await this.update(
256+
{ _id: new ObjectId(this._id) },
257+
{ [`projectsLastVisit.${projectId}`]: time }
258+
);
259+
260+
return time;
261+
}
262+
263+
/**
264+
* Get user's last project visit
265+
*
266+
* @param projectId - project id
267+
* @returns {Promise<number>} - last project visit timestamp
268+
*/
269+
public async getLastProjectVisit(projectId: string): Promise<number> {
270+
return this.projectsLastVisit?.[projectId] || 0;
271+
}
272+
236273
/**
237274
* Update user profile data
238275
* @param user – user object
@@ -323,7 +360,7 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
323360
* Remove workspace from membership collection
324361
* @param workspaceId - id of workspace to remove
325362
*/
326-
public async removeWorkspace(workspaceId: string): Promise<{workspaceId: string}> {
363+
public async removeWorkspace(workspaceId: string): Promise<{ workspaceId: string }> {
327364
await this.membershipCollection.deleteOne({
328365
workspaceId: new ObjectId(workspaceId),
329366
});

src/models/userInProject.js

Lines changed: 0 additions & 89 deletions
This file was deleted.

src/resolvers/project.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as telegram from '../utils/telegram';
33
const mongo = require('../mongo');
44
const { ApolloError, UserInputError } = require('apollo-server-express');
55
const Validator = require('../utils/validator');
6-
const UserInProject = require('../models/userInProject');
76
const EventsFactory = require('../models/eventsFactory');
87
const getEventsFactory = require('./helpers/eventsFactory').default;
98
const ProjectToWorkspace = require('../models/projectToWorkspace');
@@ -358,10 +357,14 @@ module.exports = {
358357
* @param {Context.user} user - current authorized user {@see ../index.js}
359358
* @return {Promise<Number>}
360359
*/
361-
async updateLastProjectVisit(_obj, { projectId }, { user }) {
362-
const userInProject = new UserInProject(user.id, projectId);
360+
async updateLastProjectVisit(_obj, { projectId }, { user, factories }) {
361+
const userModel = await factories.usersFactory.findById(user.id);
363362

364-
return userInProject.updateLastVisit();
363+
if (!userModel) {
364+
throw new ApolloError('User not found');
365+
}
366+
367+
return userModel.updateLastProjectVisit(projectId);
365368
},
366369
},
367370
Project: {
@@ -422,10 +425,14 @@ module.exports = {
422425
*
423426
* @return {Promise<number>}
424427
*/
425-
async unreadCount(project, data, { user, ...context }) {
428+
async unreadCount(project, _args, { factories, user, ...context }) {
426429
const eventsFactory = getEventsFactory(context, project._id);
427-
const userInProject = new UserInProject(user.id, project._id);
428-
const lastVisit = await userInProject.getLastVisit();
430+
const userModel = await factories.usersFactory.findById(user.id);
431+
432+
if (!userModel) {
433+
throw new ApolloError('User not found');
434+
}
435+
const lastVisit = await userModel.getLastProjectVisit(project._id);
429436

430437
return eventsFactory.getUnreadCount(lastVisit);
431438
},

0 commit comments

Comments
 (0)