Skip to content

Commit

Permalink
✨ add signature to user and answer (#256)
Browse files Browse the repository at this point in the history
* 🎉 Add field signature to user and answer

* ✨ Update user with his signature

* 🌱 Add signature to userPremium seeder

* ✨ Add signature in templating and PDF

* 🔥 Removed useless entity

* 🩹 Invert signature from owner and recipient

* ✨ Add signature for answer
  • Loading branch information
MaloLebrin authored May 31, 2023
1 parent cd5fe45 commit 5284564
Show file tree
Hide file tree
Showing 17 changed files with 179 additions and 46 deletions.
9 changes: 6 additions & 3 deletions src/controllers/DownloadController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class DownloadController {
const partner = event.partner
const company = event.company
const employeeAddress = answer.employee.address
const user = company.users.find(user => isUserOwner(user))
const owner = company.users.find(user => isUserOwner(user))

return {
todayDate: answer.signedAt.toISOString(),
Expand All @@ -72,9 +72,12 @@ export class DownloadController {
partnerLastName: partner.lastName,

userCity: company.address?.city,
userFirstName: user?.firstName,
userLastName: user?.lastName,
userFirstName: owner?.firstName,
userLastName: owner?.lastName,
isAccepted: answer.hasSigned,

recipientSignature: answer.signature,
ownerSignature: owner?.signature,
}
})
}
Expand Down
32 changes: 32 additions & 0 deletions src/controllers/UserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default class UserController {

private saveUserInCache = async (user: UserEntity) => {
await this.redisCache.save(`user-id-${user.id}`, user)
await this.redisCache.save(`user-token-${user.token}`, user)
}

/**
Expand Down Expand Up @@ -468,4 +469,35 @@ export default class UserController {
throw new ApiError(422, 'Veuillez renseigner l\'email')
})
}

public addSignatureToUser = async (req: Request, res: Response) => {
await wrapperRequest(req, res, async () => {
const { signature }: { signature: string } = req.body
const ctx = Context.get(req)

if (!signature) {
throw new ApiError(422, 'Signature non reçu')
}

if (!ctx?.user) {
throw new ApiError(401, 'Action non authorisée')
}

await this.repository.update(ctx.user.id, {
signature,
})

const user = await this.repository.findOne({
where: {
id: ctx.user.id,
},
})

await this.saveUserInCache(user)

return res.status(200).json({
user,
})
})
}
}
101 changes: 81 additions & 20 deletions src/controllers/employees/AnswerSpecificController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Request, Response } from 'express'
import type { DataSource, Repository } from 'typeorm'
import { IsNull } from 'typeorm'
import { verify } from 'jsonwebtoken'
import puppeteer from 'puppeteer'
import { wrapperRequest } from '../../utils'
import AnswerService from '../../services/AnswerService'
import AnswerEntity from '../../entity/AnswerEntity'
Expand All @@ -11,24 +12,27 @@ import type { DecodedJWTToken } from '../../types'
import { EmployeeEntity } from '../../entity/employees/EmployeeEntity'
import { answerResponse } from '../../utils/answerHelper'
import EventEntity from '../../entity/EventEntity'
import { defaultQueue } from '../../jobs/queue/queue'
import { UpdateEventStatusJob } from '../../jobs/queue/jobs/updateEventStatus.job'
import { generateQueueName } from '../../jobs/queue/jobs/provider'
import { SendSubmitAnswerConfirmationJob } from '../../jobs/queue/jobs/sendSubmitAnswerConfirmation.job'
// import { defaultQueue } from '../../jobs/queue/queue'
// import { UpdateEventStatusJob } from '../../jobs/queue/jobs/updateEventStatus.job'
// import { generateQueueName } from '../../jobs/queue/jobs/provider'
// import { SendSubmitAnswerConfirmationJob } from '../../jobs/queue/jobs/sendSubmitAnswerConfirmation.job'
import { getfullUsername, isUserOwner } from '../../utils/userHelper'
import { MailjetService } from '../../services'

export class AnswerSpecificController {
AnswerService: AnswerService
EventRepository: Repository<EventEntity>
AnswerRepository: Repository<AnswerEntity>
EmployeeRepository: Repository<EmployeeEntity>
MailJetService: MailjetService

constructor(DATA_SOURCE: DataSource) {
if (DATA_SOURCE) {
this.EmployeeRepository = DATA_SOURCE.getRepository(EmployeeEntity)
this.EventRepository = DATA_SOURCE.getRepository(EventEntity)
this.AnswerService = new AnswerService(DATA_SOURCE)
this.AnswerRepository = DATA_SOURCE.getRepository(AnswerEntity)
this.MailJetService = new MailjetService(DATA_SOURCE)
}
}

Expand Down Expand Up @@ -134,12 +138,16 @@ export class AnswerSpecificController {
token,
email,
hasSigned,
signature,
reason,
isSavedSignatureForNextTime,
}: {
token: string
email: string
signature: string
reason?: string
hasSigned: boolean
isSavedSignatureForNextTime?: boolean
} = req.body

if (!token || !email || !answerId) {
Expand All @@ -148,7 +156,7 @@ export class AnswerSpecificController {

await this.isValidToken(token, email)

const answer = await this.AnswerRepository.findOne({
const isAnswerExist = await this.AnswerRepository.exist({
where: {
token,
id: answerId,
Expand All @@ -157,38 +165,91 @@ export class AnswerSpecificController {
},
signedAt: IsNull(),
},
relations: ['employee', 'event.company.users'],
})

if (!answer) {
if (!isAnswerExist) {
throw new ApiError(422, 'Élément introuvable ou vous avez déjà répondu')
}

await this.AnswerRepository.update(answerId, {
signedAt: new Date(),
hasSigned,
signature,
reason: reason || null,
})

const answer = await this.AnswerRepository.findOne({
where: {
token,
id: answerId,
employee: {
email,
},
},
relations: ['employee', 'event.company.users'],
})

if (!answer) {
throw new ApiError(422, 'Une erreur est survenue')
}

const creator = answer?.event?.company?.users.find(user => isUserOwner(user))

if (!creator || !answer.event.company) {
throw new ApiError(422, 'Créateur introuvable')
}

await defaultQueue.add(
generateQueueName('SendSubmitAnswerConfirmationJob'),
new SendSubmitAnswerConfirmationJob({
req,
answer,
creatorFullName: getfullUsername(creator),
companyName: answer.event.company.name,
}),
)

await defaultQueue.add(generateQueueName('UpdateEventStatusJob'), new UpdateEventStatusJob({
eventId: answer.eventId,
}))
// await defaultQueue.add(
// generateQueueName('SendSubmitAnswerConfirmationJob'),
// new SendSubmitAnswerConfirmationJob({
// req,
// answer,
// creatorFullName: getfullUsername(creator),
// companyName: answer.event.company.name,
// }),
// )

// await defaultQueue.add(generateQueueName('UpdateEventStatusJob'), new UpdateEventStatusJob({
// eventId: answer.eventId,
// }))

const baseUrl = `${req.protocol}://${req.get('host')}`
const url = `${baseUrl}/answer/view/?ids=${req.query.ids}`
const fileName = `droit-image-${answer.employee.slug}.pdf`
const filePath = `/app/src/uploads/${fileName}`

const browser = await puppeteer.launch({
headless: 'new',
executablePath: '/usr/bin/chromium-browser',
args: [
'--no-sandbox',
'--disable-gpu',
],
})

const page = await browser.newPage()

await page.goto(url)
const pdf = await page.pdf({ path: filePath, format: 'a4', printBackground: true })
await browser.close()

await this.MailJetService.sendEmployeeAnswerWithPDF({
creatorFullName: getfullUsername(creator),
employee: answer.employee,
companyName: answer.event.company.name,
pdfBase64: pdf.toString('base64'),
fileName,
})

const employee = answer.employee

if (isSavedSignatureForNextTime) {
if (!employee.signature || employee.signature !== signature) {
await this.EmployeeRepository.update(answer.employeeId, {
signature,
})
}
}

return res.status(200).json(answer)
})
Expand Down
3 changes: 3 additions & 0 deletions src/entity/AnswerEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export default class AnswerEntity extends BaseEntity {
@Column({ nullable: true, default: null })
mailSendAt: Date

@Column({ nullable: true, default: null })
signature: string

@ManyToOne(() => EmployeeEntity, employee => employee.id, { orphanedRowAction: 'soft-delete' })
@JoinColumn({ name: 'employeeId' })
employee: EmployeeEntity
Expand Down
14 changes: 0 additions & 14 deletions src/entity/ImageRightConditionEntity.ts

This file was deleted.

3 changes: 3 additions & 0 deletions src/entity/UserEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { BadgeEntity } from './repositories/Badge.entity'

@Entity()
export class UserEntity extends BaseAuthEntity {
@Column({ nullable: true, default: null })
signature: string

@Column({ type: 'enum', enum: Role, default: Role.USER })
roles: Role

Expand Down
3 changes: 3 additions & 0 deletions src/entity/employees/EmployeeEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export class EmployeeEntity extends BasePersonEntity {
@Column({ unique: true, nullable: true })
slug: string

@Column({ nullable: true, default: null })
signature: string

@OneToOne(() => AddressEntity, { cascade: true })
@JoinColumn()
address: AddressEntity
Expand Down
3 changes: 0 additions & 3 deletions src/entity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { BugReportEntity } from './BugReportEntity'
import { EmployeeEntity } from './employees/EmployeeEntity'
import EventEntity from './EventEntity'
import { FileEntity } from './FileEntity'
import { ImageRightConditionEntity } from './ImageRightConditionEntity'
import { NewsletterRecipient } from './NewsletterRecipientEntity'
import { PaymentEntity } from './PaymentEntity'
import { SubscriptionEntity } from './SubscriptionEntity'
Expand All @@ -31,7 +30,6 @@ export default {
EventNotificationEntity,
FileEntity,
GroupEntity,
ImageRightConditionEntity,
MailEntity,
NewsletterRecipient,
NotificationEntity,
Expand All @@ -54,7 +52,6 @@ export const entities = [
EventNotificationEntity,
FileEntity,
GroupEntity,
ImageRightConditionEntity,
MailEntity,
NewsletterRecipient,
NotificationEntity,
Expand Down
2 changes: 2 additions & 0 deletions src/middlewares/validation/answerValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const signeAnswerValidation = object({
token: string().min(128).required('Le token est requis'),
email: string().email('vous devez entrer in email valide').required('L\'adresse email est requise'),
hasSigned: boolean().required('Vous devez accpeter ou refuser le droit à l\'image'),
signature: string().required('La signature est requise'),
isSavedSignatureForNextTime: boolean(),
}),
params: object({
id: number().required('L\'identifiant de la réponse est requis'),
Expand Down
14 changes: 14 additions & 0 deletions src/middlewares/validation/userValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
number,
object,
string,
} from 'yup'

export const addSignatureToUser = object({
body: object({
signature: string().required('La signature est requise'),
}),
params: object({
id: number().required('L\'identifiant de la réponse est requis'),
}),
})
4 changes: 4 additions & 0 deletions src/routes/UserRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { DataSource } from 'typeorm'
import { checkUserRole, isAuthenticated, useValidation } from '../middlewares'
import { Role } from '../types'
import UserController from '../controllers/UserController'
import { addSignatureToUser } from '../middlewares/validation/userValidation'
import type { BaseInterfaceRouter } from './BaseRouter'
import { BaseRouter } from './BaseRouter'

Expand All @@ -23,12 +24,15 @@ export class UserRoutes extends BaseRouter implements BaseInterfaceRouter {
this.router.get('/many', [isAuthenticated], new UserController(this.DATA_SOURCE).getMany)
this.router.get('/partners', [isAuthenticated], new UserController(this.DATA_SOURCE).getPhotographerAlreadyWorkWith)
this.router.get('/:id', [validate(idParamsSchema)], new UserController(this.DATA_SOURCE).getOne)

this.router.post('/token', [validate(tokenSchema)], new UserController(this.DATA_SOURCE).getOneByToken)
this.router.post('/', [validate(newUserSchema), isAuthenticated, checkUserRole([Role.ADMIN, Role.OWNER])], new UserController(this.DATA_SOURCE).newUser)
this.router.post('/login', [validate(loginSchema)], new UserController(this.DATA_SOURCE).login)
this.router.post('/photographer', [validate(createPhotographerSchema)], new UserController(this.DATA_SOURCE).createPhotographer)
this.router.post('/isMailAlreadyExist', [validate(emailAlreadyExistSchema)], new UserController(this.DATA_SOURCE).isMailAlreadyUsed)

this.router.patch('/:id', [validate(patchUserSchema), isAuthenticated], new UserController(this.DATA_SOURCE).updateOne)
this.router.patch('/signature/:id', [validate(addSignatureToUser), isAuthenticated], new UserController(this.DATA_SOURCE).addSignatureToUser)
this.router.delete('/:id', [validate(idParamsSchema), isAuthenticated], new UserController(this.DATA_SOURCE).deleteOne)

return this.router
Expand Down
3 changes: 3 additions & 0 deletions src/seed/UserCompany/UserSeedClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
userCompanyFixturePremium,
userNotUsed,
} from './fixtures'
import { tomJedusorSignature } from './signatureFixture'

export class UserSeedClass extends BaseSeedClass {
GroupService: GroupService
Expand Down Expand Up @@ -285,6 +286,7 @@ export class UserSeedClass extends BaseSeedClass {
...answer,
hasSigned: true,
signedAt: new Date(),
signature: tomJedusorSignature,
})

const answerEventNotif = this.getManager.create(EventNotificationEntity, {
Expand Down Expand Up @@ -431,6 +433,7 @@ export class UserSeedClass extends BaseSeedClass {
...answer,
hasSigned: true,
signedAt: new Date(),
signature: tomJedusorSignature,
})

const answer2 = await this.AnswerService.getOneAnswerForEventEmployee({
Expand Down
2 changes: 2 additions & 0 deletions src/seed/UserCompany/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import dayjs from 'dayjs'
import { Role, SubscriptionEnum } from '../../types'
import { signatureAlbus } from './signatureFixture'

export const userCompanyFixturePremium = {
companyName: 'Poudlard',
Expand All @@ -9,6 +10,7 @@ export const userCompanyFixturePremium = {
password: 'password',
role: Role.OWNER,
subscription: SubscriptionEnum.PREMIUM,
signature: signatureAlbus,
}

export const subscriptionUserCompanyFixturePremium = {
Expand Down
3 changes: 3 additions & 0 deletions src/seed/UserCompany/signatureFixture.ts

Large diffs are not rendered by default.

Loading

0 comments on commit 5284564

Please sign in to comment.