From adfb77525b166807b602c7c4615a39668eef149a Mon Sep 17 00:00:00 2001 From: Navaneethakrishnan Date: Tue, 13 Aug 2024 15:31:02 +0530 Subject: [PATCH] feat: introduced isFeatured option in member, team, project, pl-events - added new api to fetch featured member, team, project, events --- .forestadmin-schema.json | 64 +++++++++++++++++++ apps/web-api/prisma/fixtures/members.ts | 1 + apps/web-api/prisma/fixtures/project.ts | 1 + apps/web-api/prisma/fixtures/teams.ts | 1 + .../migration.sql | 11 ++++ apps/web-api/prisma/schema.prisma | 6 ++ apps/web-api/src/app.module.ts | 4 +- apps/web-api/src/home/home.controller.ts | 18 ++++++ apps/web-api/src/home/home.module.ts | 24 +++++++ apps/web-api/src/home/home.service.ts | 33 ++++++++++ .../src/members/__mocks__/members.mocks.ts | 1 + .../web-api/src/pl-events/pl-events.module.ts | 1 + apps/web-api/src/projects/projects.service.ts | 12 ++++ .../src/teams/__mocks__/teams.mocks.ts | 1 + libs/contracts/src/lib/contract-home.ts | 16 +++++ libs/contracts/src/schema/member.ts | 1 + libs/contracts/src/schema/pl-event.ts | 1 + libs/contracts/src/schema/project.ts | 1 + libs/contracts/src/schema/team.ts | 1 + 19 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 apps/web-api/prisma/migrations/20240813093750_team_project_event_featured_option/migration.sql create mode 100644 apps/web-api/src/home/home.controller.ts create mode 100644 apps/web-api/src/home/home.module.ts create mode 100644 apps/web-api/src/home/home.service.ts create mode 100644 libs/contracts/src/lib/contract-home.ts diff --git a/.forestadmin-schema.json b/.forestadmin-schema.json index 10094ba49..c10fb49f5 100644 --- a/.forestadmin-schema.json +++ b/.forestadmin-schema.json @@ -2006,6 +2006,22 @@ "type": "Number", "validations": [] }, + { + "defaultValue": false, + "enums": null, + "field": "isFeatured", + "integration": null, + "inverseOf": null, + "isFilterable": true, + "isPrimaryKey": false, + "isReadOnly": false, + "isRequired": false, + "isSortable": true, + "isVirtual": false, + "reference": null, + "type": "Boolean", + "validations": [] + }, { "defaultValue": null, "enums": null, @@ -3187,6 +3203,22 @@ "type": "Number", "validations": [] }, + { + "defaultValue": false, + "enums": null, + "field": "isFeatured", + "integration": null, + "inverseOf": null, + "isFilterable": true, + "isPrimaryKey": false, + "isReadOnly": false, + "isRequired": false, + "isSortable": true, + "isVirtual": false, + "reference": null, + "type": "Boolean", + "validations": [] + }, { "defaultValue": null, "enums": null, @@ -4010,6 +4042,22 @@ {"type": "is present", "message": "Failed validation rule: 'Present'"} ] }, + { + "defaultValue": false, + "enums": null, + "field": "isFeatured", + "integration": null, + "inverseOf": null, + "isFilterable": true, + "isPrimaryKey": false, + "isReadOnly": false, + "isRequired": false, + "isSortable": true, + "isVirtual": false, + "reference": null, + "type": "Boolean", + "validations": [] + }, { "defaultValue": null, "enums": null, @@ -4857,6 +4905,22 @@ "type": "Number", "validations": [] }, + { + "defaultValue": false, + "enums": null, + "field": "isFeatured", + "integration": null, + "inverseOf": null, + "isFilterable": true, + "isPrimaryKey": false, + "isReadOnly": false, + "isRequired": false, + "isSortable": true, + "isVirtual": false, + "reference": null, + "type": "Boolean", + "validations": [] + }, { "defaultValue": null, "enums": null, diff --git a/apps/web-api/prisma/fixtures/members.ts b/apps/web-api/prisma/fixtures/members.ts index 17485dee6..f8a997dca 100644 --- a/apps/web-api/prisma/fixtures/members.ts +++ b/apps/web-api/prisma/fixtures/members.ts @@ -46,6 +46,7 @@ const membersFactory = Factory.define>( plnFriend: faker.datatype.boolean(), airtableRecId: `airtable-rec-id-${sequence}`, externalId: null, + isFeatured: faker.datatype.boolean(), createdAt: faker.date.past(), approvedAt: faker.date.past(), plnStartDate: faker.date.past(), diff --git a/apps/web-api/prisma/fixtures/project.ts b/apps/web-api/prisma/fixtures/project.ts index 8f150b39e..f21c64982 100644 --- a/apps/web-api/prisma/fixtures/project.ts +++ b/apps/web-api/prisma/fixtures/project.ts @@ -45,6 +45,7 @@ const ProjectFactory = Factory.define>( readMe: faker.lorem.paragraph(), createdBy: '', maintainingTeamUid: '', + isFeatured: faker.datatype.boolean(), projectLinks: [{ name: faker.company.name(), url: faker.internet.url() diff --git a/apps/web-api/prisma/fixtures/teams.ts b/apps/web-api/prisma/fixtures/teams.ts index f76827d83..c077141e8 100644 --- a/apps/web-api/prisma/fixtures/teams.ts +++ b/apps/web-api/prisma/fixtures/teams.ts @@ -47,6 +47,7 @@ const teamsFactory = Factory.define>( officeHours: faker.name.firstName(), linkedinHandler: faker.name.firstName(), telegramHandler: faker.name.firstName(), + isFeatured: faker.datatype.boolean(), shortDescription: faker.helpers.arrayElement([ null, faker.lorem.sentence(), diff --git a/apps/web-api/prisma/migrations/20240813093750_team_project_event_featured_option/migration.sql b/apps/web-api/prisma/migrations/20240813093750_team_project_event_featured_option/migration.sql new file mode 100644 index 000000000..eeddb9e53 --- /dev/null +++ b/apps/web-api/prisma/migrations/20240813093750_team_project_event_featured_option/migration.sql @@ -0,0 +1,11 @@ +-- AlterTable +ALTER TABLE "Member" ADD COLUMN "isFeatured" BOOLEAN DEFAULT false; + +-- AlterTable +ALTER TABLE "PLEvent" ADD COLUMN "isFeatured" BOOLEAN DEFAULT false; + +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "isFeatured" BOOLEAN DEFAULT false; + +-- AlterTable +ALTER TABLE "Team" ADD COLUMN "isFeatured" BOOLEAN DEFAULT false; diff --git a/apps/web-api/prisma/schema.prisma b/apps/web-api/prisma/schema.prisma index 626aaaf08..7506d2ad4 100644 --- a/apps/web-api/prisma/schema.prisma +++ b/apps/web-api/prisma/schema.prisma @@ -30,6 +30,7 @@ model Team { shortDescription String? longDescription String? plnFriend Boolean @default(false) + isFeatured Boolean? @default(false) airtableRecId String? @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -68,6 +69,7 @@ model Member { airtableRecId String? @unique externalId String? @unique openToWork Boolean? @default(false) + isFeatured Boolean? @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt approvedAt DateTime @default(now()) @@ -330,6 +332,7 @@ model Project { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt isDeleted Boolean @default(false) + isFeatured Boolean? @default(false) projectFocusAreas ProjectFocusArea[] contributions ProjectContribution[] } @@ -348,6 +351,7 @@ model PLEvent { description String? shortDescription String? websiteURL String? + isFeatured Boolean? @default(false) location String slugURL String @unique resources Json[] @@ -495,3 +499,5 @@ model MemberFeedback { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + + diff --git a/apps/web-api/src/app.module.ts b/apps/web-api/src/app.module.ts index b6c54d3f0..26c638c21 100644 --- a/apps/web-api/src/app.module.ts +++ b/apps/web-api/src/app.module.ts @@ -38,6 +38,7 @@ import { EmptyStringToNullInterceptor } from './interceptors/empty-string-to-nul import { OfficeHoursModule } from './office-hours/office-hours.module'; import { MemberFollowUpsModule } from './member-follow-ups/member-follow-ups.module'; import { MemberFeedbacksModule } from './member-feedbacks/member-feedbacks.module'; +import { HomeModule } from './home/home.module'; @Module({ controllers: [AppController], @@ -90,7 +91,8 @@ import { MemberFeedbacksModule } from './member-feedbacks/member-feedbacks.modul PLEventsModule, OfficeHoursModule, MemberFollowUpsModule, - MemberFeedbacksModule + MemberFeedbacksModule, + HomeModule ], providers: [ { diff --git a/apps/web-api/src/home/home.controller.ts b/apps/web-api/src/home/home.controller.ts new file mode 100644 index 000000000..f0f4662e5 --- /dev/null +++ b/apps/web-api/src/home/home.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Req } from '@nestjs/common'; +import { Api, initNestServer } from '@ts-rest/nest'; +import { Request } from 'express'; +import { apiHome } from 'libs/contracts/src/lib/contract-home'; +import { HomeService } from './home.service'; + +const server = initNestServer(apiHome); +type RouteShape = typeof server.routeShapes; + +@Controller() +export class HomeController { + constructor(private homeService: HomeService) {} + + @Api(server.route.getAllFeaturedData) + async getAllFeaturedData(@Req() request: Request) { + return await this.homeService.fetchAllFeaturedData(); + } +} diff --git a/apps/web-api/src/home/home.module.ts b/apps/web-api/src/home/home.module.ts new file mode 100644 index 000000000..59146ee28 --- /dev/null +++ b/apps/web-api/src/home/home.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; +import { HomeController } from './home.controller'; +import { HomeService } from './home.service'; +import { MembersModule } from '../members/members.module'; +import { TeamsModule } from '../teams/teams.module'; +import { ProjectsModule} from '../projects/projects.module'; +import { PLEventsModule } from '../pl-events/pl-events.module'; + +@Module({ + controllers: [HomeController], + providers: [ + HomeService + ], + imports:[ + MembersModule, + TeamsModule, + ProjectsModule, + PLEventsModule + ], + exports: [ + HomeService + ] +}) +export class HomeModule {} diff --git a/apps/web-api/src/home/home.service.ts b/apps/web-api/src/home/home.service.ts new file mode 100644 index 000000000..5df1e5672 --- /dev/null +++ b/apps/web-api/src/home/home.service.ts @@ -0,0 +1,33 @@ +import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { LogService } from '../shared/log.service'; +import { MembersService } from '../members/members.service'; +import { TeamsService } from '../teams/teams.service'; +import { PLEventsService } from '../pl-events/pl-events.service'; +import { ProjectsService } from '../projects/projects.service'; + +@Injectable() +export class HomeService { + constructor( + private logger: LogService, + private memberService: MembersService, + private teamsService: TeamsService, + private plEventsService: PLEventsService, + private projectsService: ProjectsService + ) {} + + async fetchAllFeaturedData() { + try { + const filter = { where : { isFeatured: true }} + return { + members: await this.memberService.findAll(filter), + teams: await this.teamsService.findAll(filter), + events: await this.plEventsService.getPLEvents(filter), + projects: await this.projectsService.getProjects(filter) + }; + } + catch (error) { + this.logger.error(error); + throw new InternalServerErrorException('Failed to fetch featured data'); + } + } +} \ No newline at end of file diff --git a/apps/web-api/src/members/__mocks__/members.mocks.ts b/apps/web-api/src/members/__mocks__/members.mocks.ts index dc07dd145..198578b44 100644 --- a/apps/web-api/src/members/__mocks__/members.mocks.ts +++ b/apps/web-api/src/members/__mocks__/members.mocks.ts @@ -45,6 +45,7 @@ export async function createMember({ amount }: TestFactorySeederParams) { moreDetails: 'moreDetails', officeHours: 'officeHours', plnFriend: true, + isFeatured: false, airtableRecId: `airtable-rec-id-${sequence}`, externalId: `external-${sequence}`, plnStartDate: new Date(), diff --git a/apps/web-api/src/pl-events/pl-events.module.ts b/apps/web-api/src/pl-events/pl-events.module.ts index a88d3381d..a4d69f1b0 100644 --- a/apps/web-api/src/pl-events/pl-events.module.ts +++ b/apps/web-api/src/pl-events/pl-events.module.ts @@ -9,6 +9,7 @@ import { MembersModule } from '../members/members.module'; providers: [ PLEventsService, ], + exports: [PLEventsService], imports:[MembersModule] }) export class PLEventsModule {} diff --git a/apps/web-api/src/projects/projects.service.ts b/apps/web-api/src/projects/projects.service.ts index a99dc058d..9e07d0322 100644 --- a/apps/web-api/src/projects/projects.service.ts +++ b/apps/web-api/src/projects/projects.service.ts @@ -96,6 +96,18 @@ export class ProjectsService { isDeleted: false }; queryOptions.include = { + contributions: { + select: { + uid: true, + member: { + select: { + uid: true, + name: true, + image: true + } + } + } + }, maintainingTeam: { select: { uid: true, name: true, logo: true }}, creator: { select: { uid: true, name: true, image: true }}, logo: true diff --git a/apps/web-api/src/teams/__mocks__/teams.mocks.ts b/apps/web-api/src/teams/__mocks__/teams.mocks.ts index a5598c773..9477e28d8 100644 --- a/apps/web-api/src/teams/__mocks__/teams.mocks.ts +++ b/apps/web-api/src/teams/__mocks__/teams.mocks.ts @@ -39,6 +39,7 @@ export async function createTeam({ amount }: TestFactorySeederParams) { shortDescription: faker.lorem.sentence(), longDescription: faker.lorem.paragraph(), plnFriend: true, + isFeatured: true, airtableRecId: `airtable-rec-id-${sequence}`, createdAt: new Date(), updatedAt: new Date(), diff --git a/libs/contracts/src/lib/contract-home.ts b/libs/contracts/src/lib/contract-home.ts new file mode 100644 index 000000000..754706b09 --- /dev/null +++ b/libs/contracts/src/lib/contract-home.ts @@ -0,0 +1,16 @@ +import { initContract } from '@ts-rest/core'; +import { getAPIVersionAsPath } from '../utils/versioned-path'; + +const contract = initContract(); + +export const apiHome = contract.router({ + getAllFeaturedData: { + method: 'GET', + path: `${getAPIVersionAsPath('1')}/home/featured/all`, + query: contract.query, + responses: { + 200: contract.response() + }, + summary: 'Get all featured members, projects, teams and events', + } +}); \ No newline at end of file diff --git a/libs/contracts/src/schema/member.ts b/libs/contracts/src/schema/member.ts index 7961ae3fd..6ef09d794 100644 --- a/libs/contracts/src/schema/member.ts +++ b/libs/contracts/src/schema/member.ts @@ -39,6 +39,7 @@ export const MemberSchema = z.object({ officeHours: z.string().nullish(), airtableRecId: z.string().nullish(), plnFriend: z.boolean(), + isFeatured: z.boolean().nullish(), createdAt: z.string(), updatedAt: z.string(), locationUid: z.string(), diff --git a/libs/contracts/src/schema/pl-event.ts b/libs/contracts/src/schema/pl-event.ts index 277a26d53..387ac4fe1 100644 --- a/libs/contracts/src/schema/pl-event.ts +++ b/libs/contracts/src/schema/pl-event.ts @@ -14,6 +14,7 @@ export const PLEventSchema = z.object({ eventsCount: z.number().int(), logoUid: z.string().nullish(), bannerUid: z.string().nullish(), + isFeatured: z.boolean().nullish(), description: z.string().optional(), shortDescription: z.string().optional(), websiteURL: z.string().url().optional(), diff --git a/libs/contracts/src/schema/project.ts b/libs/contracts/src/schema/project.ts index 45d5aefa8..c31399961 100644 --- a/libs/contracts/src/schema/project.ts +++ b/libs/contracts/src/schema/project.ts @@ -17,6 +17,7 @@ const ProjectSchema = z.object({ tagline: z.string(), score: z.number().optional().nullable(), description: z.string(), + isFeatured: z.boolean().nullish(), contactEmail: z.string().email().nullish().transform((email)=> { return email && email.toLowerCase() }), diff --git a/libs/contracts/src/schema/team.ts b/libs/contracts/src/schema/team.ts index f5ee36687..552795c72 100644 --- a/libs/contracts/src/schema/team.ts +++ b/libs/contracts/src/schema/team.ts @@ -19,6 +19,7 @@ export const TeamSchema = z.object({ twitterHandler: z.string().nullish(), shortDescription: z.string().nullish(), longDescription: z.string().nullish(), + isFeatured: z.boolean().nullish(), plnFriend: z.boolean(), startDate: z.date().or(z.string()).nullish(), endDate: z.date().or(z.string()).nullish(),