Skip to content

Commit 5557951

Browse files
authored
Feat: Weeklies routes (#118)
Original PR #92 * New route: `POST /assos/:assoId/daymail` * Decorator `HasSomeAmong` is now applied to classes instead of properties
1 parent 30dac31 commit 5557951

42 files changed

Lines changed: 965 additions & 30 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ SMTP_REJECT_UNAUTHORIZED=false
4040
SMTP_SERVER_NAME=EtuUTT
4141
SMTP_SENDING_NAME=EtuUTT
4242
SMTP_SENDING_EMAIL=etuutt@utt.fr
43+
44+
# Weekly
45+
WEEKLY_SEND_DAY=1
46+
WEEKLY_SEND_HOUR=8

.env.test.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,7 @@ SMTP_REJECT_UNAUTHORIZED=false
3737
SMTP_SERVER_NAME=EtuUTT
3838
SMTP_SENDING_NAME=EtuUTT
3939
SMTP_SENDING_EMAIL=etuutt@utt.fr
40+
41+
# Weekly
42+
WEEKLY_SEND_DAY=1
43+
WEEKLY_SEND_HOUR=8

migration/etuutt_old/modules/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export async function migrateUsers(
7272
},
7373
preference: {
7474
create: {
75-
wantDaymail: user.daymail,
75+
wantWeekly: user.daymail,
7676
language: user.language,
7777
wantDayNotif: false,
7878
wantDiscordUtt: user.wantsJoinUTTDiscord,

prisma/schema.prisma

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ model Asso {
6565
descriptionShortTranslation Translation? @relation(name: "descriptionShortTranslation", fields: [descriptionShortTranslationId], references: [id], onDelete: Cascade)
6666
descriptionTranslation Translation? @relation(name: "descriptionTranslation", fields: [descriptionTranslationId], references: [id], onDelete: Cascade)
6767
assoMemberships AssoMembership[]
68-
assoMessages AssoMessage[]
68+
weeklies AssoWeekly[]
6969
events Event[]
7070
assoMembershipRoles AssoMembershipRole[]
7171
assoAccount User @relation(fields: [assoAccountId], references: [id], onDelete: Cascade)
@@ -103,19 +103,19 @@ model AssoMembershipRole {
103103
asso Asso @relation(fields: [assoId], references: [id])
104104
}
105105

106-
model AssoMessage {
106+
model AssoWeekly {
107107
id String @id @default(uuid())
108-
date DateTime
109-
sendToMobile Boolean
110-
sendAsDaymail Boolean
111108
createdAt DateTime @default(now())
112109
assoId String
113110
titleTranslationId String @unique
114111
bodyTranslationId String @unique
112+
date DateTime
115113
116114
asso Asso @relation(fields: [assoId], references: [id])
117115
titleTranslation Translation @relation(name: "titleTranslation", fields: [titleTranslationId], references: [id], onDelete: Cascade)
118116
bodyTranslation Translation @relation(name: "bodyTranslation", fields: [bodyTranslationId], references: [id], onDelete: Cascade)
117+
118+
@@unique([assoId, date])
119119
}
120120

121121
model Event {
@@ -249,8 +249,8 @@ model Translation {
249249
250250
assoDescription Asso? @relation("descriptionTranslation")
251251
assoDescriptionShort Asso? @relation("descriptionShortTranslation")
252-
assoMessageTitle AssoMessage? @relation("titleTranslation")
253-
assoMessageTitleBody AssoMessage? @relation("bodyTranslation")
252+
assoWeeklyTitle AssoWeekly? @relation("titleTranslation")
253+
assoWeeklyTitleBody AssoWeekly? @relation("bodyTranslation")
254254
eventDescription Event? @relation("descriptionTranslation")
255255
eventTitle Event? @relation("titleTranslation")
256256
ueofInfo UeofInfo? @relation("ueofInfoObjectivesTranslation")
@@ -752,7 +752,7 @@ model UserHomepageWidget {
752752
model UserPreference {
753753
id String @id @default(uuid())
754754
language Language @default(fr)
755-
wantDaymail Boolean @default(false)
755+
wantWeekly Boolean @default(false)
756756
wantDayNotif Boolean @default(false)
757757
wantDiscordUtt Boolean @default(false)
758758

src/app.dto.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { applyDecorators, HttpStatus, Injectable } from '@nestjs/common';
33
import * as ApiResponses from '@nestjs/swagger';
44
import { ApiProperty } from '@nestjs/swagger';
55
import { Type } from '@nestjs/common/interfaces/type.interface';
6+
import { IsOptional, IsString } from 'class-validator';
7+
import { HasSomeAmong } from './validation';
8+
import { languages } from './utils';
69

710
// Redefine the mixin function in node_modules/.pnpm/@nestjs+common@<version>_class-transformer@<version>_class-validator@<version>_reflect-metadata@<version>_rxjs@<version>/node_modules/@nestjs/common/decorators/core/injectable.decorator.js
811
// This implementation allows to give a name to the class
@@ -44,3 +47,27 @@ export function paginatedResponseDto<TBase extends Constructor>(Base: TBase) {
4447
}
4548
return mixin(ResponseDto, `${Base.name}$Paginated`); // This is important otherwise you will get always the same instance
4649
}
50+
51+
@HasSomeAmong(...languages)
52+
export class TranslationReqDto {
53+
@IsString()
54+
@IsOptional()
55+
fr?: string;
56+
57+
@IsString()
58+
@IsOptional()
59+
en?: string;
60+
61+
@IsString()
62+
@IsOptional()
63+
es?: string;
64+
65+
@IsString()
66+
@IsOptional()
67+
de?: string;
68+
69+
@IsString()
70+
@IsOptional()
71+
zh?: string;
72+
}
73+

src/assos/assos.controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import AssosMemberCreateReqDto from './dto/req/assos-member-create.dto';
2424
import AssosMemberUpdateReqDto from './dto/req/assos-member-update.dto';
2525
import AssoMembershipResDto from './dto/res/assos-membership-res.dto';
2626
import UsersService from '../users/users.service';
27+
import { ConfigModule } from '../config/config.module';
2728
import AssosUpdateReqDto from './dto/req/assos-update-req.dto';
2829
import { ImageMediaPreset } from '@prisma/client';
2930

@@ -33,6 +34,7 @@ export class AssosController {
3334
constructor(
3435
readonly assosService: AssosService,
3536
readonly userService: UsersService,
37+
readonly config: ConfigModule,
3638
readonly mediaService: ImageMediaService,
3739
) {}
3840

src/assos/assos.module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import { AssosController } from './assos.controller';
33
import { AssosService } from './assos.service';
44
import { ImageMediaModule } from '../media/image/imagemedia.module';
55
import UsersService from '../users/users.service';
6+
import WeeklyWithoutAssoidController from './weekly/weekly-without-asso-id.controller';
7+
import { WeeklyWithAssoIdController } from './weekly/weekly-with-asso-id.controller';
8+
import WeeklyService from './weekly/weekly.service';
69

710
/**
811
* Defines the `Assos` module. This module handles all routes prefixed by `/assos`.
912
* Includes `Assos` listing, details
1013
*/
1114
@Module({
12-
controllers: [AssosController],
13-
providers: [AssosService, UsersService],
15+
controllers: [AssosController, WeeklyWithoutAssoidController, WeeklyWithAssoIdController],
16+
providers: [AssosService, WeeklyService, UsersService],
1417
imports: [ImageMediaModule],
1518
})
1619
export class AssosModule {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Prisma, PrismaClient } from '@prisma/client';
2+
import { generateCustomModel } from '../../prisma/prisma.service';
3+
import { pick, translationSelect } from '../../utils';
4+
import { Translation } from '../../prisma/types';
5+
6+
const ASSO_WEEKLY_SELECT_FILTER = {
7+
select: {
8+
id: true,
9+
assoId: true,
10+
createdAt: true,
11+
titleTranslation: translationSelect,
12+
bodyTranslation: translationSelect,
13+
date: true,
14+
},
15+
orderBy: { date: 'asc'}
16+
} as const satisfies Prisma.AssoWeeklyFindManyArgs;
17+
18+
export type UnformattedAssoWeekly = Prisma.AssoWeeklyGetPayload<typeof ASSO_WEEKLY_SELECT_FILTER>;
19+
export type AssoWeekly = Pick<UnformattedAssoWeekly, 'id' | 'assoId' | 'createdAt' | 'date'> & { title: Translation, message: Translation }
20+
21+
export const generateCustomAssoWeeklyModel = (prisma: PrismaClient) =>
22+
generateCustomModel(prisma, 'assoWeekly', ASSO_WEEKLY_SELECT_FILTER, formatAssoWeekly);
23+
24+
function formatAssoWeekly(_: PrismaClient, r: UnformattedAssoWeekly): AssoWeekly {
25+
return {
26+
...pick(r, 'id', 'assoId', 'createdAt', 'date'), title: r.titleTranslation, message: r.bodyTranslation
27+
}
28+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Type } from 'class-transformer';
2+
import { IsDate, IsNotEmpty, ValidateNested } from 'class-validator';
3+
import { TranslationReqDto } from '../../../../app.dto';
4+
import { IsWeekDate } from '../../../../validation';
5+
6+
export default class WeeklyReqDto {
7+
@ValidateNested()
8+
@IsNotEmpty()
9+
@Type(() => TranslationReqDto)
10+
title: TranslationReqDto
11+
12+
@ValidateNested()
13+
@IsNotEmpty()
14+
@Type(() => TranslationReqDto)
15+
message: TranslationReqDto;
16+
17+
@IsDate()
18+
@IsNotEmpty()
19+
@Type(() => Date)
20+
@IsWeekDate()
21+
date: Date;
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { IsDate, IsInt, IsOptional } from 'class-validator';
2+
import { Type } from 'class-transformer';
3+
4+
export default class WeeklySearchReqDto {
5+
@IsDate()
6+
@Type(() => Date)
7+
from?: Date;
8+
9+
@IsOptional()
10+
@IsDate()
11+
@Type(() => Date)
12+
to?: Date;
13+
14+
@IsOptional()
15+
@IsInt()
16+
page: number = 1;
17+
}

0 commit comments

Comments
 (0)