Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4195243
fix: added user metrics [SPRW-3110]
mayankjha-eng Mar 23, 2026
440fd9f
fix: integrate user metrics with event-driven updates [SPRW-3110]
mayankjha-eng Mar 23, 2026
1eb1bad
feat: user metrics tracking [SPRW-3110]
mayankjha-eng Mar 24, 2026
aced60f
fix: replace weekly digest aggregation with precomputed user metrics …
mayankjha-eng Mar 24, 2026
2dc20e7
fix: minimal fixes [SPRW-3110]
mayankjha-eng Mar 24, 2026
7a89f08
fix: new workspaces count [SPRW-3110]
mayankjha-eng Mar 24, 2026
046ec3b
fix: testflow execution and pending invite fixed [SPRW-3110]
mayankjha-eng Mar 24, 2026
5450aad
feat: add in-memory buffering with concurrency control[SPRW-3110]
mayankjha-eng Mar 25, 2026
22d88ff
feat: daily update for user metrics[SPRW-3110]
mayankjha-eng Mar 25, 2026
fb2d8b9
fix: invite flow[SPRW-3110]
mayankjha-eng Mar 25, 2026
d546403
fix: active workspace[SPRW-3110]
mayankjha-eng Mar 25, 2026
400bb3e
fix: cta link[SPRW-3110]
mayankjha-eng Mar 26, 2026
9136244
fix: set for dev testing[SPRW-3110]
mayankjha-eng Mar 26, 2026
3cd62a4
fix: cron set for 10min[SPRW-3110]
mayankjha-eng Mar 26, 2026
507a90b
Merge pull request #965 from mayankjha-eng/fix/SPRW-3110/weekly-digest
LordNayan Mar 26, 2026
1df5246
fix: email changes for testing[SPRW-3110]
mayankjha-eng Mar 26, 2026
0fed7c7
Merge pull request #966 from mayankjha-eng/fix/SPRW-3110/weekly-diges…
LordNayan Mar 26, 2026
16231b4
fix: dev email send logic fixed[SPRW-3110]
mayankjha-eng Mar 26, 2026
c13529c
Merge pull request #967 from mayankjha-eng/fix/SPRW-3110/weekly-diges…
LordNayan Mar 26, 2026
e373b21
fix: pending action for admin user[SPRW-3110]
mayankjha-eng Mar 27, 2026
b964d37
Merge pull request #968 from mayankjha-eng/fix/SPRW-3110/weekly-diges…
LordNayan Mar 27, 2026
95f82f5
fix: modified for prod[SPRW-3110]
mayankjha-eng Mar 27, 2026
cba143c
fix: comments removed[SPRW-3110]
mayankjha-eng Mar 30, 2026
64993bc
fix: version bump [SPRW-1234]
mayankjha-eng Mar 30, 2026
bf04f58
Merge pull request #970 from mayankjha-eng/fix/version-bump
LordNayan Mar 30, 2026
c927e2c
Merge pull request #969 from mayankjha-eng/fix/SPRW-3110/weekly-diges…
LordNayan Mar 30, 2026
bf07673
feat: enable cors for dev
LordNayan Mar 31, 2026
604e69c
Merge pull request #971 from LordNayan/feat/enable-cors
jatinlodhi2002 Mar 31, 2026
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 .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Staging
on:
push:
branches:
- release/2.37.0
- release/2.39.0

jobs:
build:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "project-sparrow-api",
"version": "2.37.0",
"version": "2.39.0",
"description": "Backend APIs for Project Sparrow.",
"author": "techdome",
"license": "",
Expand Down
8 changes: 7 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ const { PORT } = process.env;
SwaggerModule.setup(SWAGGER_API_ROOT, app, document);

// Enable Cross-Origin Resource Sharing (CORS)
app.enableCors();
if (process.env.APP_ENV === "DEV") {
app.enableCors({
origin: "*",
});
} else {
app.enableCors();
}

// Register additional Fastify plugins
app.register(headers);
Expand Down
1 change: 1 addition & 0 deletions src/modules/common/enum/database.collection.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export enum Collections {
PROMOCODES = "promocodes",
SUPERADMINS = "superadmins",
NOTIFICATIONS = "notifications",
USER_METRICS = "user_metrics",
}
105 changes: 105 additions & 0 deletions src/modules/common/models/user-metrics.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
IsDate,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
} from "class-validator";

/**
* UserMetrics model for precomputed weekly digest metrics.
* Designed for efficient upserts and bulk reads without aggregation.
*/
export class UserMetrics {
/**
* The user ID this metric belongs to. Indexed for fast lookups.
*/
@IsString()
@IsNotEmpty()
userId: string;

/**
* The start of the week (Monday 00:00:00 UTC) this metric covers.
* Combined with userId for compound index.
*/
@IsDate()
@IsNotEmpty()
weekStart: Date;

/**
* Total number of executions (updates/activities) by the user this week.
*/
@IsNumber()
@IsOptional()
totalExecutions?: number;

/**
* Number of APIs created by the user this week.
*/
@IsNumber()
@IsOptional()
apisCreated?: number;

/**
* Number of collections the user has access to.
*/
@IsNumber()
@IsOptional()
collectionsCount?: number;

/**
* Number of active workspaces the user participated in this week.
*/
@IsNumber()
@IsOptional()
activeWorkspaces?: number;

/**
* Number of new workspaces created by the user this week.
*/
@IsNumber()
@IsOptional()
newWorkspaces?: number;

/**
* Number of testflows executed by the user this week.
*/
@IsNumber()
@IsOptional()
testflowsExecuted?: number;

/**
* Timestamp when this metric was last updated.
*/
@IsDate()
@IsOptional()
updatedAt?: Date;
}

/**
* Payload for incrementing metrics. All fields are optional
* since we use $inc for partial updates.
*/
export interface IncrementMetricsPayload {
totalExecutions?: number;
apisCreated?: number;
collectionsCount?: number;
activeWorkspaces?: number;
newWorkspaces?: number;
testflowsExecuted?: number;
}

/**
* Metrics data returned from the repository.
*/
export interface UserMetricsData {
userId: string;
weekStart: Date;
totalExecutions: number;
apisCreated: number;
collectionsCount: number;
activeWorkspaces: number;
newWorkspaces: number;
testflowsExecuted: number;
updatedAt: Date;
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export class UserInvitesRepository {
.aggregate([
{
$match: {
createdAt: { $gte: start, $lte: end },
email: { $in: emails },
},
},
Expand Down
82 changes: 82 additions & 0 deletions src/modules/notifications/repositories/notification.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,86 @@ export class NotificationRepository {
"data.inviteStatus": "pending",
});
}

/**
* Fetch pending workspace invite notifications for a list of users within a time range.
* Returns a Map keyed by userId (string) with an array of formatted messages.
*/
async getPendingInvitesForUsers(
userIds: string[],
start: Date,
end: Date,
): Promise<Map<string, string[]>> {
if (!userIds || userIds.length === 0) return new Map();

const objectIds = userIds.map((id) => new ObjectId(id));

const pipeline = [
{
$match: {
recipientId: { $in: objectIds },
type: "WORKSPACE_INVITE",
"data.inviteStatus": "pending",
createdAt: { $gte: start, $lte: end },
isArchived: false,
},
},
{ $sort: { createdAt: -1 } },
{
$group: {
_id: "$recipientId",
invites: {
$push: {
inviterName: "$data.inviterName",
workspaceNames: "$data.workspaceNames",
role: "$data.role",
teamName: "$data.teamName",
},
},
},
},
// Keep only the latest 5 invites per recipient for compactness
{
$project: {
invites: { $slice: ["$invites", 5] },
},
},
];

const results = await this.db
.collection(Collections.NOTIFICATIONS)
.aggregate(pipeline)
.toArray();

const map = new Map<string, string[]>();

for (const row of results) {
const key = row._id.toString();
const messages: string[] = [];
for (const inv of row.invites || []) {
const inviter = inv?.inviterName || "Someone";

if (inv?.role === "admin") {
const teamName = inv?.teamName || "team";
messages.push(`${inviter} invited you as admin to ${teamName}`);
} else {
const workspaceNames = Array.isArray(inv?.workspaceNames)
? inv.workspaceNames
: inv?.workspaceNames
? [inv.workspaceNames]
: [];

const workspaceText =
workspaceNames.length > 0
? workspaceNames.join(", ")
: "a workspace";

messages.push(`${inviter} invited you to ${workspaceText}`);
}
}
map.set(key, messages);
}

return map;
}
}
Loading
Loading