diff --git a/apps/orchestrator/src/activities/email.activity.ts b/apps/orchestrator/src/activities/email.activity.ts index c591c8d8..8ecfc236 100644 --- a/apps/orchestrator/src/activities/email.activity.ts +++ b/apps/orchestrator/src/activities/email.activity.ts @@ -25,4 +25,9 @@ export class EmailActivity { async getUserOrgs(id: string) { return this._organizationService.getTeam(id); } + + @ActivityMethod() + async setStreak(organizationId: string, type: 'start' | 'end') { + return this._organizationService.setStreak(organizationId, type); + } } diff --git a/apps/orchestrator/src/activities/post.activity.ts b/apps/orchestrator/src/activities/post.activity.ts index 71200476..fcb9997e 100644 --- a/apps/orchestrator/src/activities/post.activity.ts +++ b/apps/orchestrator/src/activities/post.activity.ts @@ -83,7 +83,11 @@ export class PostActivity { @ActivityMethod() async getPostsList(orgId: string, postId: string) { - const getPosts = await this._postService.getPostsRecursively(postId, true, orgId); + const getPosts = await this._postService.getPostsRecursively( + postId, + true, + orgId + ); if (!getPosts || getPosts.length === 0 || getPosts[0].parentPostId) { return []; } @@ -155,7 +159,7 @@ export class PostActivity { posts ); - return getIntegration.post( + const postNow = await getIntegration.post( integration.internalId, integration.token, await Promise.all( @@ -179,6 +183,23 @@ export class PostActivity { ), integration ); + + await this._temporalService.client + .getRawClient() + .workflow.start('streakWorkflow', { + args: [{ organizationId: integration.organizationId }], + workflowId: `streak_${integration.organizationId}`, + taskQueue: 'main', + workflowIdConflictPolicy: 'TERMINATE_EXISTING', + typedSearchAttributes: new TypedSearchAttributes([ + { + key: organizationId, + value: integration.organizationId, + }, + ]), + }); + + return postNow; } @ActivityMethod() diff --git a/apps/orchestrator/src/workflows/index.ts b/apps/orchestrator/src/workflows/index.ts index 72f10d68..76b0d16f 100644 --- a/apps/orchestrator/src/workflows/index.ts +++ b/apps/orchestrator/src/workflows/index.ts @@ -4,3 +4,4 @@ export * from './digest.email.workflow'; export * from './missing.post.workflow'; export * from './send.email.workflow'; export * from './refresh.token.workflow'; +export * from './streak.workflow'; diff --git a/apps/orchestrator/src/workflows/streak.workflow.ts b/apps/orchestrator/src/workflows/streak.workflow.ts new file mode 100644 index 00000000..02fc7fc8 --- /dev/null +++ b/apps/orchestrator/src/workflows/streak.workflow.ts @@ -0,0 +1,28 @@ +import { proxyActivities, sleep } from '@temporalio/workflow'; +import { EmailActivity } from '@gitroom/orchestrator/activities/email.activity'; + +const { sendEmailAsync, getUserOrgs, setStreak } = proxyActivities({ + startToCloseTimeout: '10 minute', + taskQueue: 'main', + cancellationType: 'ABANDON', +}); + +export async function streakWorkflow({ + organizationId, +}: { + organizationId: string; +}) { + await setStreak(organizationId, 'start'); + await sleep(79200000); + const userOrgs = await getUserOrgs(organizationId); + for (const user of userOrgs.users) { + await sendEmailAsync( + user.user.email, + 'Streak Reminder', + '

You are about to lose your streak in two hours! schedule a post now to keep it!

', + 'bottom' + ); + } + await sleep(7200000); + await setStreak(organizationId, 'end'); +} diff --git a/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts b/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts index 57bae74e..32c81d5a 100644 --- a/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts @@ -260,6 +260,25 @@ export class OrganizationRepository { }); } + async setStreak(organizationId: string, type: 'start' | 'end') { + try { + await this._organization.model.organization.update({ + where: { + id: organizationId, + ...(type === 'start' + ? { + streakSince: null, + } + : {}), + }, + data: { + ...(type === 'end' ? { streakSince: null } : {}), + ...(type === 'start' ? { streakSince: new Date() } : {}), + }, + }); + } catch (err) {} + } + async getTeam(orgId: string) { return this._organization.model.organization.findUnique({ where: { diff --git a/libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts b/libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts index c9fca065..e914e534 100644 --- a/libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts @@ -65,6 +65,10 @@ export class OrganizationService { return this._organizationRepository.getTeam(orgId); } + async setStreak(organizationId: string, type: 'start' | 'end') { + return this._organizationRepository.setStreak(organizationId, type); + } + getOrgByCustomerId(customerId: string) { return this._organizationRepository.getOrgByCustomerId(customerId); } diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 32310bb9..5cda9afe 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -14,6 +14,7 @@ model Organization { description String? apiKey String? paymentId String? + streakSince DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt allowTrial Boolean @default(false) @@ -30,8 +31,8 @@ model Organization { buyerOrganization MessagesGroup[] notifications Notifications[] plugs Plugs[] - post Post[] @relation("organization") - submittedPost Post[] @relation("submittedForOrg") + post Post[] @relation("organization") + submittedPost Post[] @relation("submittedForOrg") sets Sets[] signatures Signatures[] subscription Subscription? @@ -40,6 +41,9 @@ model Organization { usedCodes UsedCodes[] users UserOrganization[] webhooks Webhooks[] + + @@index([streakSince]) + @@index([paymentId]) } model Tags {