From d704925d8965f92880debc1208befffd409f19ae Mon Sep 17 00:00:00 2001 From: Neo-Ryo Date: Thu, 28 Mar 2024 15:56:28 +0100 Subject: [PATCH 01/10] add new col + fix dummy import --- server/controllers/activity.ts | 5 +++-- server/entities/activity.ts | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/server/controllers/activity.ts b/server/controllers/activity.ts index 9053366a5..8093b76fd 100644 --- a/server/controllers/activity.ts +++ b/server/controllers/activity.ts @@ -2,11 +2,12 @@ import type { JSONSchemaType } from 'ajv'; import type { NextFunction, Request, Response } from 'express'; import { IsNull } from 'typeorm'; +import type { ActivityContent, AnyData } from '../../types/activity.type'; +import { ActivityStatus, ActivityType } from '../../types/activity.type'; import type { GameData, GamesData } from '../../types/game.type'; import type { StoriesData, StoryElement } from '../../types/story.type'; import { ImageType } from '../../types/story.type'; -import type { AnyData, ActivityContent } from '../entities/activity'; -import { Activity, ActivityType, ActivityStatus } from '../entities/activity'; +import { Activity } from '../entities/activity'; import { Comment } from '../entities/comment'; import { Game } from '../entities/game'; import { Image } from '../entities/image'; diff --git a/server/entities/activity.ts b/server/entities/activity.ts index 309c2de81..83b251d9f 100644 --- a/server/entities/activity.ts +++ b/server/entities/activity.ts @@ -19,9 +19,6 @@ import { Image } from './image'; import { User } from './user'; import { Village } from './village'; -export type { AnyData, ActivityContent }; -export { ActivityType, ActivityStatus }; - @Entity() export class Activity implements ActivityInterface { @PrimaryGeneratedColumn() @@ -40,6 +37,9 @@ export class Activity implements ActivityInterface { @Column({ type: 'tinyint', nullable: false, default: VillagePhase.DISCOVER }) public phase: number; + @Column({ type: 'string', nullable: true }) + public phaseStep: string | null; + @Column({ type: 'tinyint', nullable: false, From 9882a1b57294f5c53df3888897ec5ea2df448365 Mon Sep 17 00:00:00 2001 From: Neo-Ryo Date: Thu, 28 Mar 2024 17:34:43 +0100 Subject: [PATCH 02/10] WIP update db --- package.json | 1 + server/controllers/activity.ts | 36 ++++----- server/entities/activity.ts | 6 +- .../1711637965713-PhaseStep-activity.ts | 21 +++++ src/pages/admin/newportal/publish/index.tsx | 9 +-- src/utils/phases.ts | 76 ------------------- types/activity.type.ts | 17 +++++ 7 files changed, 61 insertions(+), 105 deletions(-) create mode 100644 server/migrations/1711637965713-PhaseStep-activity.ts delete mode 100644 src/utils/phases.ts diff --git a/package.json b/package.json index 2d49069ce..626b1be03 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "migration:create": "cd server && mkdir -p migrations && cd migrations && yarn typeorm migration:create", "migration:run": "yarn typeorm migration:run -d dist/server/utils/data-source.js", "migration:run-dev": "rm -rf dist && yarn build:server && yarn typeorm migration:run -d dist/server/utils/data-source.js || true", + "migration:revert-dev": "rm -rf dist && yarn build:server && npx typeorm migration:revert -d dist/server/utils/data-source.js || true", "typeorm": "typeorm-ts-node-commonjs" }, "jest": { diff --git a/server/controllers/activity.ts b/server/controllers/activity.ts index 8093b76fd..1e0976fb3 100644 --- a/server/controllers/activity.ts +++ b/server/controllers/activity.ts @@ -22,24 +22,6 @@ import { Controller } from './controller'; const activityController = new Controller('/activities'); -type ActivityGetter = { - limit?: number; - page?: number; - phase?: number | null; - villageId?: number; - type?: string[]; - subType?: number | null; - countries?: string[]; - pelico?: boolean; - userId?: number; - status?: number; - responseActivityId?: number; - delayedDays?: number; - hasVisibilitySetToClass?: boolean; - teacherId?: number; - visibleToParent: boolean; -}; - const getActivitiesCommentCount = async (ids: number[]): Promise<{ [key: number]: number }> => { if (ids.length === 0) { return {}; @@ -76,6 +58,24 @@ const getActivitiesCommentCount = async (ids: number[]): Promise<{ [key: number] }, {}); }; +type ActivityGetter = { + limit?: number; + page?: number; + phase?: number | null; + villageId?: number; + type?: string[]; + subType?: number | null; + countries?: string[]; + pelico?: boolean; + userId?: number; + status?: number; + responseActivityId?: number; + delayedDays?: number; + hasVisibilitySetToClass?: boolean; + teacherId?: number; + visibleToParent: boolean; +}; + const getActivities = async ({ limit = 200, page = 0, diff --git a/server/entities/activity.ts b/server/entities/activity.ts index 83b251d9f..dec977ad3 100644 --- a/server/entities/activity.ts +++ b/server/entities/activity.ts @@ -11,7 +11,7 @@ import { Index, } from 'typeorm'; -import type { Activity as ActivityInterface, AnyData, ActivityContent } from '../../types/activity.type'; +import type { Activity as ActivityInterface, AnyData, ActivityContent, ActivityPhaseStep } from '../../types/activity.type'; import { ActivityType, ActivityStatus } from '../../types/activity.type'; import { VillagePhase } from '../../types/village.type'; import { Game } from './game'; @@ -37,8 +37,8 @@ export class Activity implements ActivityInterface { @Column({ type: 'tinyint', nullable: false, default: VillagePhase.DISCOVER }) public phase: number; - @Column({ type: 'string', nullable: true }) - public phaseStep: string | null; + @Column({ type: 'text', nullable: true }) + public phaseStep: ActivityPhaseStep | null; @Column({ type: 'tinyint', diff --git a/server/migrations/1711637965713-PhaseStep-activity.ts b/server/migrations/1711637965713-PhaseStep-activity.ts new file mode 100644 index 000000000..8fa28bde6 --- /dev/null +++ b/server/migrations/1711637965713-PhaseStep-activity.ts @@ -0,0 +1,21 @@ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class PhaseStepActivity1711637965713 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const activityTable = await queryRunner.getTable('activity'); + if (activityTable) { + if (!activityTable.findColumnByName('phaseStep')) { + await queryRunner.query(`ALTER TABLE ${activityTable.name} ADD phaseStep TEXT`); + } + } + } + + public async down(queryRunner: QueryRunner): Promise { + const activityTable = await queryRunner.getTable('activity'); + if (activityTable) { + if (activityTable.findColumnByName('phaseStep')) { + await queryRunner.query(`ALTER TABLE ${activityTable.name} DROP COLUMN phaseStep`); + } + } + } +} diff --git a/src/pages/admin/newportal/publish/index.tsx b/src/pages/admin/newportal/publish/index.tsx index 75ff557e4..f0ef48b65 100644 --- a/src/pages/admin/newportal/publish/index.tsx +++ b/src/pages/admin/newportal/publish/index.tsx @@ -8,19 +8,12 @@ import { useGetActivities } from 'src/api/activities/activities.get'; import ActivityCardAdminList from 'src/components/activities/ActivityCard/activity-admin/ActivityCardAdminList'; import PelicoStar from 'src/svg/pelico/pelico_star.svg'; import PelicoVacances from 'src/svg/pelico/pelico_vacances.svg'; -import { phasesObject } from 'src/utils/phases'; const rows: GridRowsProp = [ // A row example of how it should look // { id: 1, 'village-name': 'Test', 'message-lancement-phase-1': 'Hello', 'relance-phase-1': 'World' }, ]; -const firstEmptyCol: GridColDef[] = [{ field: 'village-name', headerName: '', width: 200 }]; -const columns: GridColDef[] = firstEmptyCol.concat( - ...phasesObject.reduce((acc, curr) => { - acc.push(...curr.steps.map((e) => ({ field: e.id, headerName: e.name, width: 150 }))); - return acc; - }, []), -); +const columns: GridColDef[] = []; const Publier = () => { const draftActivities = useGetActivities({ limit: 2, isDraft: true, isPelico: true }); diff --git a/src/utils/phases.ts b/src/utils/phases.ts deleted file mode 100644 index 370b9f2d5..000000000 --- a/src/utils/phases.ts +++ /dev/null @@ -1,76 +0,0 @@ -export const phasesObject = [ - { - id: 'phase-1', - name: 'Phase 1', - steps: [ - { - id: 'message-lancement-phase-1', - name: 'Message de lancement phase 1', - }, - { - id: 'relance-phase-1', - name: 'Relance phase 1', - }, - { - id: 'enigme-pays-1', - name: 'Enigme pays 1', - }, - { - id: 'enigme-pays-2', - name: 'Enigme pays 2', - }, - ], - }, - { - id: 'phase-2', - name: 'Phase 2', - steps: [ - { - id: 'message-lancement-phase-2', - name: 'Message de lancement phase 2', - }, - { - id: 'relance-phase-2', - name: 'Relance phase 2', - }, - { - id: 'activite-8-mars', - name: 'Activité 8 mars', - }, - { - id: 'activite-EMI', - name: 'activité EMI', - }, - { - id: 'message-cloture-phase-2', - name: 'Message de clôture phase 2', - }, - ], - }, - { - id: 'phase-3', - name: 'Phase 3', - steps: [ - { - id: 'message-lancement-phase-3', - name: 'Message de lancement phase 3', - }, - { - id: 'relance-phase-3', - name: 'Relance phase 3', - }, - { - id: 'parametrage-hymne', - name: 'Paramétrage de l’hymne', - }, - { - id: 'mixage-hymne', - name: 'Mixage de l’hymne', - }, - { - id: 'message-cloture-phase-3', - name: 'Message de clôture phase 3', - }, - ], - }, -]; diff --git a/types/activity.type.ts b/types/activity.type.ts index c49ed64e3..eb548898b 100644 --- a/types/activity.type.ts +++ b/types/activity.type.ts @@ -67,3 +67,20 @@ export interface Activity { export const LinkNotAllowedInPath = { REACTION: '/reagir-a-une-activite/', }; + +export enum ActivityPhaseStep { + MESSAGE_LANCEMENT_PHASE_1 = 'Message de lancement phase 1', + RELANCE_PHASE_1 = 'Relance phase 1', + ENIGME_PAYS_1 = 'Enigme pays 1', + ENIGME_PAYS_2 = 'Enigme pays 2', + MESSAGE_LANCEMENT_PHASE_2 = 'Message de lancement phase 2', + RELANCE_PHASE_2 = 'Relance phase 2', + ACTIVITE_8_MARS = 'Activité 8 mars', + ACTIVITE_EMI = 'activité EMI', + MESSAGE_CLOTURE_PHASE_2 = 'Message de clôture phase 2', + MESSAGE_LANCEMENT_PHASE_3 = 'Message de lancement phase 3', + RELANCE_PHASE_3 = 'Relance phase 3', + PARAMETRAGE_DE_L_HYMNE = "Paramétrage de l'hymne", + MIXAGE_DE_L_HYMNE = "Mixage de l'hymne", + MESSAGE_CLOTURE_PHASE_3 = 'Message de clôture phase 3', +} From ee3db0980cf6002a17472531f9f1ceeb380893f7 Mon Sep 17 00:00:00 2001 From: Neo Ryo Date: Fri, 29 Mar 2024 11:06:42 +0100 Subject: [PATCH 03/10] created manager folder, exported manager activity code + update comment count function --- server/controllers/activity.ts | 149 +-------------------------------- server/manager/activity.ts | 142 +++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 148 deletions(-) create mode 100644 server/manager/activity.ts diff --git a/server/controllers/activity.ts b/server/controllers/activity.ts index 1e0976fb3..65921bc96 100644 --- a/server/controllers/activity.ts +++ b/server/controllers/activity.ts @@ -8,11 +8,11 @@ import type { GameData, GamesData } from '../../types/game.type'; import type { StoriesData, StoryElement } from '../../types/story.type'; import { ImageType } from '../../types/story.type'; import { Activity } from '../entities/activity'; -import { Comment } from '../entities/comment'; import { Game } from '../entities/game'; import { Image } from '../entities/image'; import { UserType } from '../entities/user'; import { VillagePhase } from '../entities/village'; +import { getActivities, getActivitiesCommentCount } from '../manager/activity'; import { AppError, ErrorCode } from '../middlewares/handleErrors'; import { getQueryString } from '../utils'; import { AppDataSource } from '../utils/data-source'; @@ -22,153 +22,6 @@ import { Controller } from './controller'; const activityController = new Controller('/activities'); -const getActivitiesCommentCount = async (ids: number[]): Promise<{ [key: number]: number }> => { - if (ids.length === 0) { - return {}; - } - const queryBuilder = await AppDataSource.getRepository(Activity) - .createQueryBuilder('activity') - .select('activity.id') - .addSelect('IFNULL(`commentCount`, 0) + IFNULL(`activityCount`, 0)', 'comments') - .leftJoin( - (qb) => { - qb.select('comment.activityId', 'cid').addSelect('COUNT(comment.id)', 'commentCount').from(Comment, 'comment').groupBy('comment.activityId'); - return qb; - }, - 'comments', - '`comments`.`cid` = activity.id', - ) - .leftJoin( - (qb) => { - qb.select('activity.responseActivityId', 'aid') - .addSelect('COUNT(activity.id)', 'activityCount') - .from(Activity, 'activity') - .where('activity.status != :status', { status: `${ActivityStatus.DRAFT}` }) - .groupBy('activity.responseActivityId'); - return qb; - }, - 'activities', - '`activities`.`aid` = activity.id', - ) - .where('activity.id in (:ids)', { ids }) - .getRawMany(); - return queryBuilder.reduce((acc, row) => { - acc[row.activity_id] = parseInt(row.comments, 10) || 0; - return acc; - }, {}); -}; - -type ActivityGetter = { - limit?: number; - page?: number; - phase?: number | null; - villageId?: number; - type?: string[]; - subType?: number | null; - countries?: string[]; - pelico?: boolean; - userId?: number; - status?: number; - responseActivityId?: number; - delayedDays?: number; - hasVisibilitySetToClass?: boolean; - teacherId?: number; - visibleToParent: boolean; -}; - -const getActivities = async ({ - limit = 200, - page = 0, - villageId, - type = [], - subType = null, - countries, - phase = null, - pelico = true, - status = 0, - userId, - responseActivityId, - delayedDays, - hasVisibilitySetToClass, - teacherId, - visibleToParent, -}: ActivityGetter) => { - // get ids - let subQueryBuilder = AppDataSource.getRepository(Activity).createQueryBuilder('activity').where('activity.status = :status', { status }); - if (villageId !== undefined) { - subQueryBuilder = subQueryBuilder.andWhere('activity.villageId = :villageId', { villageId }); - } - if (type.length > 0) { - subQueryBuilder = subQueryBuilder.andWhere('activity.type IN (:type)', { type }); - } - if (subType !== null) { - subQueryBuilder = subQueryBuilder.andWhere('activity.subType = :subType', { subType }); - } - if (phase !== null) { - subQueryBuilder = subQueryBuilder.andWhere('activity.phase = :phase', { phase }); - } - if (delayedDays !== undefined) { - // * Memo: we only select activity with updateDate + delayedDays lesser than current date - subQueryBuilder = subQueryBuilder.andWhere(`DATE_ADD(activity.updateDate, INTERVAL :delayedDays DAY) <= CURDATE()`, { delayedDays: delayedDays }); - } - if (visibleToParent) { - // * Here if user is a parent, we only select activities with the attribute is set to true - subQueryBuilder = subQueryBuilder.andWhere('activity.isVisibleToParent = :visibleToParent', { visibleToParent }); - } - if (hasVisibilitySetToClass !== undefined && teacherId !== undefined) { - subQueryBuilder = subQueryBuilder.andWhere('activity.user = :teacherId', { teacherId: teacherId }); - } - - if (responseActivityId !== undefined) { - subQueryBuilder = subQueryBuilder.andWhere('activity.responseActivityId = :responseActivityId', { responseActivityId }); - } else if (userId !== undefined) { - subQueryBuilder = subQueryBuilder.innerJoin('activity.user', 'user').andWhere('user.id = :userId', { - userId, - }); - } else if (pelico && countries !== undefined && countries.length > 0) { - subQueryBuilder = subQueryBuilder - .innerJoin('activity.user', 'user') - .andWhere('((user.countryCode IN (:countries) AND user.type >= :userType) OR user.type <= :userType2)', { - countries, - userType: UserType.TEACHER, - userType2: UserType.MEDIATOR, - }); - } else if (pelico && countries !== undefined && countries.length === 0) { - subQueryBuilder = subQueryBuilder.innerJoin('activity.user', 'user').andWhere('user.type <= :userType2', { - userType2: UserType.MEDIATOR, - }); - } else if (!pelico && countries !== undefined && countries.length > 0) { - subQueryBuilder = subQueryBuilder.innerJoin('activity.user', 'user').andWhere('user.countryCode IN (:countries) AND user.type >= :userType', { - countries, - userType: UserType.TEACHER, - }); - } else if (!pelico && countries !== undefined) { - return []; - } - - const activities = await subQueryBuilder - .orderBy('activity.isPinned', 'DESC') - .addOrderBy('activity.updateDate', 'DESC') - .limit(limit) - .offset(page * limit) - .getMany(); - - const ids = activities.map((a) => a.id); - if (ids.length === 0) { - return []; - } - - const comments = await getActivitiesCommentCount(ids); - for (const activity of activities) { - if (comments[activity.id] !== undefined) { - activity.commentCount = comments[activity.id]; - } else { - activity.commentCount = 0; - } - } - return activities; -}; - // --- Get all activities. --- activityController.get({ path: '', userType: UserType.OBSERVATOR }, async (req: Request, res: Response) => { if (!req.user) throw new AppError('Forbidden', ErrorCode.UNKNOWN); diff --git a/server/manager/activity.ts b/server/manager/activity.ts new file mode 100644 index 000000000..c8ade956a --- /dev/null +++ b/server/manager/activity.ts @@ -0,0 +1,142 @@ +import { In } from 'typeorm'; + +import type { ActivityPhaseStep } from '../../types/activity.type'; +import { Activity } from '../entities/activity'; +import { Comment } from '../entities/comment'; +import { UserType } from '../entities/user'; +import { AppDataSource } from '../utils/data-source'; + +type ActivityGetter = { + limit?: number; + page?: number; + phase?: number | null; + phaseStep?: ActivityPhaseStep; + villageId?: number; + type?: string[]; + subType?: number | null; + countries?: string[]; + pelico?: boolean; + userId?: number; + status?: number; + responseActivityId?: number; + delayedDays?: number; + hasVisibilitySetToClass?: boolean; + teacherId?: number; + visibleToParent: boolean; +}; + +export const getActivities = async ({ + limit = 200, + page = 0, + villageId, + type = [], + subType = null, + countries, + phase = null, + pelico = true, + status = 0, + userId, + responseActivityId, + delayedDays, + hasVisibilitySetToClass, + teacherId, + visibleToParent, + phaseStep, +}: ActivityGetter) => { + // get ids + let subQueryBuilder = AppDataSource.getRepository(Activity).createQueryBuilder('activity').where('activity.status = :status', { status }); + if (villageId !== undefined) { + subQueryBuilder = subQueryBuilder.andWhere('activity.villageId = :villageId', { villageId }); + } + if (type.length > 0) { + subQueryBuilder = subQueryBuilder.andWhere('activity.type IN (:type)', { type }); + } + if (subType !== null) { + subQueryBuilder = subQueryBuilder.andWhere('activity.subType = :subType', { subType }); + } + if (phase !== null) { + subQueryBuilder = subQueryBuilder.andWhere('activity.phase = :phase', { phase }); + } + if (phaseStep) { + subQueryBuilder = subQueryBuilder.andWhere('activity.phaseStep = :phaseStep', { phaseStep }); + } + if (delayedDays !== undefined) { + // * Memo: we only select activity with updateDate + delayedDays lesser than current date + subQueryBuilder = subQueryBuilder.andWhere(`DATE_ADD(activity.updateDate, INTERVAL :delayedDays DAY) <= CURDATE()`, { delayedDays: delayedDays }); + } + if (visibleToParent) { + // * Here if user is a parent, we only select activities with the attribute is set to true + subQueryBuilder = subQueryBuilder.andWhere('activity.isVisibleToParent = :visibleToParent', { visibleToParent }); + } + if (hasVisibilitySetToClass !== undefined && teacherId !== undefined) { + subQueryBuilder = subQueryBuilder.andWhere('activity.user = :teacherId', { teacherId: teacherId }); + } + + if (responseActivityId !== undefined) { + subQueryBuilder = subQueryBuilder.andWhere('activity.responseActivityId = :responseActivityId', { responseActivityId }); + } else if (userId !== undefined) { + subQueryBuilder = subQueryBuilder.innerJoin('activity.user', 'user').andWhere('user.id = :userId', { + userId, + }); + } else if (pelico && countries !== undefined && countries.length > 0) { + subQueryBuilder = subQueryBuilder + .innerJoin('activity.user', 'user') + .andWhere('((user.countryCode IN (:countries) AND user.type >= :userType) OR user.type <= :userType2)', { + countries, + userType: UserType.TEACHER, + userType2: UserType.MEDIATOR, + }); + } else if (pelico && countries !== undefined && countries.length === 0) { + subQueryBuilder = subQueryBuilder.innerJoin('activity.user', 'user').andWhere('user.type <= :userType2', { + userType2: UserType.MEDIATOR, + }); + } else if (!pelico && countries !== undefined && countries.length > 0) { + subQueryBuilder = subQueryBuilder.innerJoin('activity.user', 'user').andWhere('user.countryCode IN (:countries) AND user.type >= :userType', { + countries, + userType: UserType.TEACHER, + }); + } else if (!pelico && countries !== undefined) { + return []; + } + + const activities = await subQueryBuilder + .orderBy('activity.isPinned', 'DESC') + .addOrderBy('activity.updateDate', 'DESC') + .limit(limit) + .offset(page * limit) + .getMany(); + + const ids = activities.map((a) => a.id); + if (ids.length === 0) { + return []; + } + + const comments = await getActivitiesCommentCount(ids); + for (const activity of activities) { + if (comments[activity.id] !== undefined) { + activity.commentCount = comments[activity.id]; + } else { + activity.commentCount = 0; + } + } + return activities; +}; + +export const getActivitiesCommentCount = async (ids: number[]): Promise<{ [key: number]: number }> => { + if (ids.length === 0) { + return {}; + } + const comments = await AppDataSource.getRepository(Comment).find({ + where: { + activityId: In(ids), + }, + }); + return comments.reduce<{ [key: number]: number }>((acc, comment) => { + if (!acc[comment.activityId]) { + acc[comment.activityId] = 1; + } else { + acc[comment.activityId]++; + } + return acc; + }, {}); +}; From 566f66ac41c18eb266088fead1da1e28ef1fc14f Mon Sep 17 00:00:00 2001 From: Neo Ryo Date: Fri, 29 Mar 2024 11:14:46 +0100 Subject: [PATCH 04/10] updated enum, add query in controller --- server/controllers/activity.ts | 3 ++- types/activity.type.ts | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/server/controllers/activity.ts b/server/controllers/activity.ts index 65921bc96..d57541047 100644 --- a/server/controllers/activity.ts +++ b/server/controllers/activity.ts @@ -2,7 +2,7 @@ import type { JSONSchemaType } from 'ajv'; import type { NextFunction, Request, Response } from 'express'; import { IsNull } from 'typeorm'; -import type { ActivityContent, AnyData } from '../../types/activity.type'; +import type { ActivityContent, ActivityPhaseStep, AnyData } from '../../types/activity.type'; import { ActivityStatus, ActivityType } from '../../types/activity.type'; import type { GameData, GamesData } from '../../types/game.type'; import type { StoriesData, StoryElement } from '../../types/story.type'; @@ -40,6 +40,7 @@ activityController.get({ path: '', userType: UserType.OBSERVATOR }, async (req: type: req.query.type ? (getQueryString(req.query.type) || '').split(',') : undefined, subType: req.query.subType ? Number(getQueryString(req.query.subType)) || 0 : undefined, phase: req.query.phase ? Number(getQueryString(req.query.phase)) || 0 : undefined, + phaseStep: req.query.phaseStep as ActivityPhaseStep | undefined, status: req.query.status ? Number(getQueryString(req.query.status)) || 0 : undefined, userId: req.query.userId ? Number(getQueryString(req.query.userId)) || 0 : undefined, responseActivityId: req.query.responseActivityId ? Number(getQueryString(req.query.responseActivityId)) || 0 : undefined, diff --git a/types/activity.type.ts b/types/activity.type.ts index eb548898b..5be8e77a7 100644 --- a/types/activity.type.ts +++ b/types/activity.type.ts @@ -69,18 +69,18 @@ export const LinkNotAllowedInPath = { }; export enum ActivityPhaseStep { - MESSAGE_LANCEMENT_PHASE_1 = 'Message de lancement phase 1', - RELANCE_PHASE_1 = 'Relance phase 1', - ENIGME_PAYS_1 = 'Enigme pays 1', - ENIGME_PAYS_2 = 'Enigme pays 2', - MESSAGE_LANCEMENT_PHASE_2 = 'Message de lancement phase 2', - RELANCE_PHASE_2 = 'Relance phase 2', - ACTIVITE_8_MARS = 'Activité 8 mars', - ACTIVITE_EMI = 'activité EMI', - MESSAGE_CLOTURE_PHASE_2 = 'Message de clôture phase 2', - MESSAGE_LANCEMENT_PHASE_3 = 'Message de lancement phase 3', - RELANCE_PHASE_3 = 'Relance phase 3', - PARAMETRAGE_DE_L_HYMNE = "Paramétrage de l'hymne", - MIXAGE_DE_L_HYMNE = "Mixage de l'hymne", - MESSAGE_CLOTURE_PHASE_3 = 'Message de clôture phase 3', + MESSAGE_LANCEMENT_PHASE_1 = 'message-de-lancement-phase-1', + RELANCE_PHASE_1 = 'relance-phase-1', + ENIGME_PAYS_1 = 'enigme-pays-1', + ENIGME_PAYS_2 = 'enigme-pays-2', + MESSAGE_LANCEMENT_PHASE_2 = 'message-de-lancement-phase-2', + RELANCE_PHASE_2 = 'relance-phase-2', + ACTIVITE_8_MARS = 'activité-8-mars', + ACTIVITE_EMI = 'activite-EMI', + MESSAGE_CLOTURE_PHASE_2 = 'message-de-cloture-phase-2', + MESSAGE_LANCEMENT_PHASE_3 = 'message-de-lancement-phase-3', + RELANCE_PHASE_3 = 'relance-phase-3', + PARAMETRAGE_DE_L_HYMNE = 'parametrage-de-lhymne', + MIXAGE_DE_L_HYMNE = 'mixage-de-lhymne', + MESSAGE_CLOTURE_PHASE_3 = 'message-de-clôture-phase-3', } From 0ded3b883b629a0bf53d84f78397c21cba58a796 Mon Sep 17 00:00:00 2001 From: Neo Ryo Date: Fri, 29 Mar 2024 11:20:22 +0100 Subject: [PATCH 05/10] fix user controller import --- server/controllers/user.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/controllers/user.ts b/server/controllers/user.ts index 575b09e70..e9b71c021 100644 --- a/server/controllers/user.ts +++ b/server/controllers/user.ts @@ -4,9 +4,10 @@ import type { NextFunction, Request, Response } from 'express'; import type { FindOperator } from 'typeorm'; import { In, IsNull, LessThan } from 'typeorm'; +import { ActivityStatus, ActivityType } from '../../types/activity.type'; import { getAccessToken } from '../authentication/lib/tokens'; import { Email, sendMail } from '../emails'; -import { Activity, ActivityType, ActivityStatus } from '../entities/activity'; +import { Activity } from '../entities/activity'; import { Classroom } from '../entities/classroom'; import { FeatureFlag } from '../entities/featureFlag'; import { Student } from '../entities/student'; From a73dfd8dfdfa8dc050dd56925435ced7cc0c25a3 Mon Sep 17 00:00:00 2001 From: Neo Ryo Date: Fri, 29 Mar 2024 11:55:39 +0100 Subject: [PATCH 06/10] WIP update activities + phase typing --- server/controllers/activity.ts | 9 +++++++-- server/manager/activity.ts | 4 ++-- types/activity.type.ts | 9 ++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/server/controllers/activity.ts b/server/controllers/activity.ts index d57541047..10863e0fd 100644 --- a/server/controllers/activity.ts +++ b/server/controllers/activity.ts @@ -2,7 +2,7 @@ import type { JSONSchemaType } from 'ajv'; import type { NextFunction, Request, Response } from 'express'; import { IsNull } from 'typeorm'; -import type { ActivityContent, ActivityPhaseStep, AnyData } from '../../types/activity.type'; +import type { ActivityContent, EActivityPhaseStep, AnyData } from '../../types/activity.type'; import { ActivityStatus, ActivityType } from '../../types/activity.type'; import type { GameData, GamesData } from '../../types/game.type'; import type { StoriesData, StoryElement } from '../../types/story.type'; @@ -40,7 +40,7 @@ activityController.get({ path: '', userType: UserType.OBSERVATOR }, async (req: type: req.query.type ? (getQueryString(req.query.type) || '').split(',') : undefined, subType: req.query.subType ? Number(getQueryString(req.query.subType)) || 0 : undefined, phase: req.query.phase ? Number(getQueryString(req.query.phase)) || 0 : undefined, - phaseStep: req.query.phaseStep as ActivityPhaseStep | undefined, + phaseStep: req.query.phaseStep as EActivityPhaseStep | undefined, status: req.query.status ? Number(getQueryString(req.query.status)) || 0 : undefined, userId: req.query.userId ? Number(getQueryString(req.query.userId)) || 0 : undefined, responseActivityId: req.query.responseActivityId ? Number(getQueryString(req.query.responseActivityId)) || 0 : undefined, @@ -239,6 +239,7 @@ type UpdateActivity = { displayAsUser?: boolean; data?: AnyData; content?: ActivityContent[]; + phaseStep?: EActivityPhaseStep; }; const UPDATE_A_SCHEMA: JSONSchemaType = { @@ -252,6 +253,10 @@ const UPDATE_A_SCHEMA: JSONSchemaType = { type: 'number', nullable: true, }, + phaseStep: { + type: 'string', + nullable: true, + }, responseActivityId: { type: 'number', nullable: true }, responseType: { type: 'number', diff --git a/server/manager/activity.ts b/server/manager/activity.ts index c8ade956a..6b609a675 100644 --- a/server/manager/activity.ts +++ b/server/manager/activity.ts @@ -1,6 +1,6 @@ import { In } from 'typeorm'; -import type { ActivityPhaseStep } from '../../types/activity.type'; +import type { EActivityPhaseStep } from '../../types/activity.type'; import { Activity } from '../entities/activity'; import { Comment } from '../entities/comment'; import { UserType } from '../entities/user'; @@ -10,7 +10,7 @@ type ActivityGetter = { limit?: number; page?: number; phase?: number | null; - phaseStep?: ActivityPhaseStep; + phaseStep?: EActivityPhaseStep; villageId?: number; type?: string[]; subType?: number | null; diff --git a/types/activity.type.ts b/types/activity.type.ts index 5be8e77a7..cfe0ac44e 100644 --- a/types/activity.type.ts +++ b/types/activity.type.ts @@ -68,19 +68,26 @@ export const LinkNotAllowedInPath = { REACTION: '/reagir-a-une-activite/', }; -export enum ActivityPhaseStep { +enum EPhase1Steps { MESSAGE_LANCEMENT_PHASE_1 = 'message-de-lancement-phase-1', RELANCE_PHASE_1 = 'relance-phase-1', ENIGME_PAYS_1 = 'enigme-pays-1', +} +enum EPhase2Steps { ENIGME_PAYS_2 = 'enigme-pays-2', MESSAGE_LANCEMENT_PHASE_2 = 'message-de-lancement-phase-2', RELANCE_PHASE_2 = 'relance-phase-2', ACTIVITE_8_MARS = 'activité-8-mars', ACTIVITE_EMI = 'activite-EMI', MESSAGE_CLOTURE_PHASE_2 = 'message-de-cloture-phase-2', +} +enum EPhase3Steps { MESSAGE_LANCEMENT_PHASE_3 = 'message-de-lancement-phase-3', RELANCE_PHASE_3 = 'relance-phase-3', PARAMETRAGE_DE_L_HYMNE = 'parametrage-de-lhymne', MIXAGE_DE_L_HYMNE = 'mixage-de-lhymne', MESSAGE_CLOTURE_PHASE_3 = 'message-de-clôture-phase-3', } + +export type EActivityPhaseStep = EPhase1Steps | EPhase2Steps | EPhase3Steps; +export type Phase = T extends EPhase1Steps ? 1 : T extends EPhase2Steps ? 2 : 3; From 1236d9431378737d44e350b308455b43bc973883 Mon Sep 17 00:00:00 2001 From: Neo Ryo Date: Fri, 29 Mar 2024 16:31:29 +0100 Subject: [PATCH 07/10] update activity controller + check on phases types --- server/controllers/activity.ts | 35 +++++++++++++++++++++++++++------- server/entities/activity.ts | 4 ++-- types/activity.type.ts | 9 +++------ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/server/controllers/activity.ts b/server/controllers/activity.ts index 10863e0fd..e453bccb8 100644 --- a/server/controllers/activity.ts +++ b/server/controllers/activity.ts @@ -2,8 +2,8 @@ import type { JSONSchemaType } from 'ajv'; import type { NextFunction, Request, Response } from 'express'; import { IsNull } from 'typeorm'; -import type { ActivityContent, EActivityPhaseStep, AnyData } from '../../types/activity.type'; -import { ActivityStatus, ActivityType } from '../../types/activity.type'; +import type { ActivityContent, AnyData } from '../../types/activity.type'; +import { EPhase1Steps, ActivityStatus, ActivityType, EPhase2Steps, EPhase3Steps } from '../../types/activity.type'; import type { GameData, GamesData } from '../../types/game.type'; import type { StoriesData, StoryElement } from '../../types/story.type'; import { ImageType } from '../../types/story.type'; @@ -40,7 +40,7 @@ activityController.get({ path: '', userType: UserType.OBSERVATOR }, async (req: type: req.query.type ? (getQueryString(req.query.type) || '').split(',') : undefined, subType: req.query.subType ? Number(getQueryString(req.query.subType)) || 0 : undefined, phase: req.query.phase ? Number(getQueryString(req.query.phase)) || 0 : undefined, - phaseStep: req.query.phaseStep as EActivityPhaseStep | undefined, + phaseStep: req.query.phaseStep, status: req.query.status ? Number(getQueryString(req.query.status)) || 0 : undefined, userId: req.query.userId ? Number(getQueryString(req.query.userId)) || 0 : undefined, responseActivityId: req.query.responseActivityId ? Number(getQueryString(req.query.responseActivityId)) || 0 : undefined, @@ -232,14 +232,14 @@ activityController.post({ path: '', userType: UserType.TEACHER }, async (req: Re // --- Update activity --- type UpdateActivity = { status?: number; - phase?: number; + phase?: 1 | 2 | 3; responseActivityId?: number; responseType?: number; isPinned?: boolean; displayAsUser?: boolean; data?: AnyData; content?: ActivityContent[]; - phaseStep?: EActivityPhaseStep; + phaseStep?: string; }; const UPDATE_A_SCHEMA: JSONSchemaType = { @@ -310,10 +310,31 @@ activityController.put({ path: '/:id', userType: UserType.TEACHER }, async (req: next(); return; } - if (activity.status !== ActivityStatus.PUBLISHED) { - activity.phase = data.phase || activity.phase; + if (data.phase) activity.phase = data.phase; + if (data.phaseStep) { + switch (activity.phase) { + case 1: { + if (Object.values(EPhase1Steps).includes(data.phaseStep as EPhase1Steps)) activity.phaseStep = data.phaseStep; + else throw new AppError(`Phase step: ${data.phaseStep} isn't part of phase 1`, ErrorCode.INVALID_DATA); + break; + } + case 2: { + if (Object.values(EPhase2Steps).includes(data.phaseStep as EPhase2Steps)) activity.phaseStep = data.phaseStep; + else throw new AppError(`Phase step: ${data.phaseStep} isn't part of phase 2`, ErrorCode.INVALID_DATA); + break; + } + case 3: { + if (Object.values(EPhase3Steps).includes(data.phaseStep as EPhase3Steps)) activity.phaseStep = data.phaseStep; + else throw new AppError(`Phase step: ${data.phaseStep} isn't part of phase 3`, ErrorCode.INVALID_DATA); + break; + } + default: + break; + } + } } + activity.status = data.status ?? activity.status; activity.responseActivityId = data.responseActivityId !== undefined ? data.responseActivityId : activity.responseActivityId ?? null; activity.responseType = data.responseType !== undefined ? data.responseType : activity.responseType ?? null; diff --git a/server/entities/activity.ts b/server/entities/activity.ts index dec977ad3..0e7ccc6f3 100644 --- a/server/entities/activity.ts +++ b/server/entities/activity.ts @@ -11,7 +11,7 @@ import { Index, } from 'typeorm'; -import type { Activity as ActivityInterface, AnyData, ActivityContent, ActivityPhaseStep } from '../../types/activity.type'; +import type { Activity as ActivityInterface, AnyData, ActivityContent } from '../../types/activity.type'; import { ActivityType, ActivityStatus } from '../../types/activity.type'; import { VillagePhase } from '../../types/village.type'; import { Game } from './game'; @@ -38,7 +38,7 @@ export class Activity implements ActivityInterface { public phase: number; @Column({ type: 'text', nullable: true }) - public phaseStep: ActivityPhaseStep | null; + public phaseStep: string | null; @Column({ type: 'tinyint', diff --git a/types/activity.type.ts b/types/activity.type.ts index cfe0ac44e..6b68d7304 100644 --- a/types/activity.type.ts +++ b/types/activity.type.ts @@ -68,12 +68,12 @@ export const LinkNotAllowedInPath = { REACTION: '/reagir-a-une-activite/', }; -enum EPhase1Steps { +export enum EPhase1Steps { MESSAGE_LANCEMENT_PHASE_1 = 'message-de-lancement-phase-1', RELANCE_PHASE_1 = 'relance-phase-1', ENIGME_PAYS_1 = 'enigme-pays-1', } -enum EPhase2Steps { +export enum EPhase2Steps { ENIGME_PAYS_2 = 'enigme-pays-2', MESSAGE_LANCEMENT_PHASE_2 = 'message-de-lancement-phase-2', RELANCE_PHASE_2 = 'relance-phase-2', @@ -81,13 +81,10 @@ enum EPhase2Steps { ACTIVITE_EMI = 'activite-EMI', MESSAGE_CLOTURE_PHASE_2 = 'message-de-cloture-phase-2', } -enum EPhase3Steps { +export enum EPhase3Steps { MESSAGE_LANCEMENT_PHASE_3 = 'message-de-lancement-phase-3', RELANCE_PHASE_3 = 'relance-phase-3', PARAMETRAGE_DE_L_HYMNE = 'parametrage-de-lhymne', MIXAGE_DE_L_HYMNE = 'mixage-de-lhymne', MESSAGE_CLOTURE_PHASE_3 = 'message-de-clôture-phase-3', } - -export type EActivityPhaseStep = EPhase1Steps | EPhase2Steps | EPhase3Steps; -export type Phase = T extends EPhase1Steps ? 1 : T extends EPhase2Steps ? 2 : 3; From da89855a5387dcfe282bfddad6e588b2b1183666 Mon Sep 17 00:00:00 2001 From: Neo Ryo Date: Fri, 29 Mar 2024 16:34:26 +0100 Subject: [PATCH 08/10] fix broken import --- server/manager/activity.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/manager/activity.ts b/server/manager/activity.ts index 6b609a675..f5f36611e 100644 --- a/server/manager/activity.ts +++ b/server/manager/activity.ts @@ -1,6 +1,5 @@ import { In } from 'typeorm'; -import type { EActivityPhaseStep } from '../../types/activity.type'; import { Activity } from '../entities/activity'; import { Comment } from '../entities/comment'; import { UserType } from '../entities/user'; @@ -10,7 +9,7 @@ type ActivityGetter = { limit?: number; page?: number; phase?: number | null; - phaseStep?: EActivityPhaseStep; + phaseStep?: string; villageId?: number; type?: string[]; subType?: number | null; From 3e833b7cf490f0478ce3d980d3ddd91f30197f01 Mon Sep 17 00:00:00 2001 From: Neo Ryo Date: Fri, 29 Mar 2024 16:41:00 +0100 Subject: [PATCH 09/10] one type at a time --- server/controllers/activity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controllers/activity.ts b/server/controllers/activity.ts index e453bccb8..04a9412bc 100644 --- a/server/controllers/activity.ts +++ b/server/controllers/activity.ts @@ -40,7 +40,7 @@ activityController.get({ path: '', userType: UserType.OBSERVATOR }, async (req: type: req.query.type ? (getQueryString(req.query.type) || '').split(',') : undefined, subType: req.query.subType ? Number(getQueryString(req.query.subType)) || 0 : undefined, phase: req.query.phase ? Number(getQueryString(req.query.phase)) || 0 : undefined, - phaseStep: req.query.phaseStep, + phaseStep: req.query.phaseStep ? String(req.query.phaseStep) : undefined, status: req.query.status ? Number(getQueryString(req.query.status)) || 0 : undefined, userId: req.query.userId ? Number(getQueryString(req.query.userId)) || 0 : undefined, responseActivityId: req.query.responseActivityId ? Number(getQueryString(req.query.responseActivityId)) || 0 : undefined, From c6eafa7691a1aecefa6a378a92bb4d3bbbbaebcd Mon Sep 17 00:00:00 2001 From: Neo-Ryo Date: Tue, 2 Apr 2024 09:32:28 +0200 Subject: [PATCH 10/10] check status activities on comments count --- server/manager/activity.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/manager/activity.ts b/server/manager/activity.ts index f5f36611e..3ea43e393 100644 --- a/server/manager/activity.ts +++ b/server/manager/activity.ts @@ -122,12 +122,21 @@ export const getActivities = async ({ }; export const getActivitiesCommentCount = async (ids: number[]): Promise<{ [key: number]: number }> => { - if (ids.length === 0) { + const publishedActivityies = await AppDataSource.getRepository(Activity).find({ + where: { + id: In(ids), + status: 0, + }, + select: { + id: true, + }, + }); + if (publishedActivityies.length === 0) { return {}; } const comments = await AppDataSource.getRepository(Comment).find({ where: { - activityId: In(ids), + activityId: In(publishedActivityies.map((a) => a.id)), }, }); return comments.reduce<{ [key: number]: number }>((acc, comment) => {