From 83c7f756219dc9929b8b5accbf51dfb64b5a6baa Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 11 Feb 2025 23:32:14 +0700 Subject: [PATCH] feat: repeated posts --- .../components/launches/add.edit.model.tsx | 6 ++ .../src/components/launches/calendar.tsx | 7 +- .../components/launches/repeat.component.tsx | 35 ++++++++++ .../database/prisma/posts/posts.repository.ts | 64 ++++++++++++++++--- .../database/prisma/posts/posts.service.ts | 22 ++++++- .../src/database/prisma/schema.prisma | 2 + .../src/dtos/posts/create.post.dto.ts | 16 ++--- 7 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 apps/frontend/src/components/launches/repeat.component.tsx diff --git a/apps/frontend/src/components/launches/add.edit.model.tsx b/apps/frontend/src/components/launches/add.edit.model.tsx index a986bb43..0b9b7992 100644 --- a/apps/frontend/src/components/launches/add.edit.model.tsx +++ b/apps/frontend/src/components/launches/add.edit.model.tsx @@ -58,6 +58,7 @@ import { LoadingComponent } from '@gitroom/frontend/components/layout/loading'; import { DropFiles } from '@gitroom/frontend/components/layout/drop.files'; import { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer'; import { TagsComponent } from './tags.component'; +import { RepeatComponent } from '@gitroom/frontend/components/launches/repeat.component'; function countCharacters(text: string, type: string): number { if (type !== 'x') { @@ -140,6 +141,8 @@ export const AddEditModal: FC<{ // are we in edit mode? const existingData = useExistingData(); + const [inter, setInter] = useState(existingData?.posts?.[0]?.intervalInDays); + const [tags, setTags] = useState( // @ts-ignore existingData?.posts?.[0]?.tags?.map((p: any) => ({ @@ -394,6 +397,7 @@ export const AddEditModal: FC<{ body: JSON.stringify({ ...(postFor ? { order: postFor.id } : {}), type, + inter, tags, shortLink, date: dateState.utc().format('YYYY-MM-DDTHH:mm:ss'), @@ -418,6 +422,7 @@ export const AddEditModal: FC<{ modal.closeAll(); }, [ + inter, postFor, dateState, value, @@ -566,6 +571,7 @@ export const AddEditModal: FC<{ setSelectedIntegrations([]); }} /> + {!selectedIntegrations.length && ( + (loadPost: Post & { integration: Integration }, isDuplicate?: boolean) => async () => { + const post = { + ...loadPost, + // @ts-ignore + publishDate: loadPost.actualDate || loadPost.publishDate, + }; if (user?.orgId === post.submittedForOrganizationId) { return previewPublication(post); } diff --git a/apps/frontend/src/components/launches/repeat.component.tsx b/apps/frontend/src/components/launches/repeat.component.tsx new file mode 100644 index 00000000..1a470366 --- /dev/null +++ b/apps/frontend/src/components/launches/repeat.component.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react'; +import { Select } from '@gitroom/react/form/select'; + +const list = [ + { value: 1, label: 'Every Day' }, + { value: 2, label: 'Every Two Days' }, + { value: 3, label: 'Every Three Days' }, + { value: 4, label: 'Every Four Days' }, + { value: 5, label: 'Every Five Days' }, + { value: 6, label: 'Every Six Days' }, + { value: 7, label: 'Every Week' }, + { value: 14, label: 'Every Two Weeks' }, + { value: 30, label: 'Every Month' }, +]; + +export const RepeatComponent: FC<{ repeat: number|null, onChange: (newVal: number) => void }> = (props) => { + const { repeat } = props; + return ( + + ); +}; diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts index 28e6b9e1..3ef81139 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts @@ -6,11 +6,15 @@ import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto' import dayjs from 'dayjs'; import isoWeek from 'dayjs/plugin/isoWeek'; import weekOfYear from 'dayjs/plugin/weekOfYear'; +import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; +import utc from 'dayjs/plugin/utc'; import { v4 as uuidv4 } from 'uuid'; import { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto'; dayjs.extend(isoWeek); dayjs.extend(weekOfYear); +dayjs.extend(isSameOrAfter); +dayjs.extend(utc); @Injectable() export class PostsRepository { @@ -80,7 +84,7 @@ export class PostsRepository { }); } - getPosts(orgId: string, query: GetPostsDto) { + async getPosts(orgId: string, query: GetPostsDto) { const dateYear = dayjs().year(query.year); const date = query.display === 'day' @@ -108,20 +112,35 @@ export class PostsRepository { .add(2, 'hours') .toDate(); - return this._post.model.post.findMany({ + const list = await this._post.model.post.findMany({ where: { - OR: [ + AND: [ { - organizationId: orgId, + OR: [ + { + organizationId: orgId, + }, + { + submittedForOrganizationId: orgId, + }, + ], }, { - submittedForOrganizationId: orgId, + OR: [ + { + publishDate: { + gte: startDate, + lte: endDate, + }, + }, + { + intervalInDays: { + not: null, + }, + }, + ], }, ], - publishDate: { - gte: startDate, - lte: endDate, - }, deletedAt: null, parentPostId: null, ...(query.customer @@ -140,6 +159,7 @@ export class PostsRepository { submittedForOrganizationId: true, submittedForOrderId: true, state: true, + intervalInDays: true, tags: { select: { tag: true, @@ -155,6 +175,28 @@ export class PostsRepository { }, }, }); + + return list.reduce((all, post) => { + if (!post.intervalInDays) { + return [...all, post]; + } + + const addMorePosts = []; + let startingDate = dayjs.utc(post.publishDate); + while (dayjs.utc(endDate).isSameOrAfter(startingDate)) { + if (dayjs(startingDate).isSameOrAfter(dayjs.utc(post.publishDate))) { + addMorePosts.push({ + ...post, + publishDate: startingDate.toDate(), + actualDate: post.publishDate, + }); + } + + startingDate = startingDate.add(post.intervalInDays, 'days'); + } + + return [...all, ...addMorePosts]; + }, [] as any[]); } async deletePost(orgId: string, group: string) { @@ -272,7 +314,8 @@ export class PostsRepository { orgId: string, date: string, body: PostBody, - tags: { value: string; label: string }[] + tags: { value: string; label: string }[], + inter?: number, ) { const posts: Post[] = []; const uuid = uuidv4(); @@ -303,6 +346,7 @@ export class PostsRepository { : {}), content: value.content, group: uuid, + intervalInDays: inter ? +inter : null, approvedSubmitForOrder: APPROVED_SUBMIT_FOR_ORDER.NO, state: state === 'draft' ? ('DRAFT' as const) : ('QUEUE' as const), image: JSON.stringify(value.image), diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts index 0964994b..019c308c 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts @@ -88,7 +88,7 @@ export class PostsService { ]; } - getPosts(orgId: string, query: GetPostsDto) { + async getPosts(orgId: string, query: GetPostsDto) { return this._postRepository.getPosts(orgId, query); } @@ -205,6 +205,18 @@ export class PostsService { return; } + if (firstPost?.intervalInDays) { + this._workerServiceProducer.emit('post', { + id, + options: { + delay: firstPost.intervalInDays * 86400000, + }, + payload: { + id: id, + }, + }); + } + if (firstPost.submittedForOrderId) { this._workerServiceProducer.emit('submit', { payload: { @@ -597,7 +609,8 @@ export class PostsService { ? dayjs().format('YYYY-MM-DDTHH:mm:00') : body.date, post, - body.tags + body.tags, + body.inter, ); if (!posts?.length) { @@ -633,6 +646,10 @@ export class PostsService { }, payload: { id: posts[0].id, + delay: + body.type === 'now' + ? 0 + : dayjs(posts[0].publishDate).diff(dayjs(), 'millisecond'), }, }); } @@ -666,6 +683,7 @@ export class PostsService { }, payload: { id: id, + delay: dayjs(date).diff(dayjs(), 'millisecond'), }, }); } diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index d9724fbe..9d294d0b 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -374,6 +374,7 @@ model Post { approvedSubmitForOrder APPROVED_SUBMIT_FOR_ORDER @default(NO) lastMessageId String? lastMessage Messages? @relation(fields: [lastMessageId], references: [id]) + intervalInDays Int? payoutProblems PayoutProblems[] comments Comments[] tags TagsPosts[] @@ -389,6 +390,7 @@ model Post { @@index([organizationId]) @@index([parentPostId]) @@index([submittedForOrderId]) + @@index([intervalInDays]) @@index([approvedSubmitForOrder]) @@index([lastMessageId]) @@index([createdAt]) diff --git a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts index 008958b5..a9100f85 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts @@ -1,15 +1,5 @@ import { - ArrayMinSize, - IsArray, - IsBoolean, - IsDateString, - IsDefined, - IsIn, - IsOptional, - IsString, - MinLength, - ValidateIf, - ValidateNested, + ArrayMinSize, IsArray, IsBoolean, IsDateString, IsDefined, IsIn, IsNumber, IsOptional, IsString, MinLength, ValidateIf, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto'; @@ -113,6 +103,10 @@ export class CreatePostDto { @IsBoolean() shortLink: boolean; + @IsOptional() + @IsNumber() + inter?: number; + @IsDefined() @IsDateString() date: string;