Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions confiture-rest-api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ enum EmailType {
EMAIL_UPDATE_VERIFICATION
EMAIL_UPDATE_CONFIRMATION
PASSWORD_RESET_REQUEST
AUDIT_TRANSFER
}

model EmailLog {
Expand Down
40 changes: 40 additions & 0 deletions confiture-rest-api/src/audits/audit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1870,4 +1870,44 @@ export class AuditService {

return audit;
}

/**
* Transfer an audit ownership to another user and link it to its account (if any)
*/
async transferAudit(uniqueId: string, newEmail: string) {
const updatedAudit = this.prisma.$transaction(async (tx) => {
// Get new owner info
const user = await tx.user.findUnique({
where: {
username: newEmail
},
select: {
name: true,
orgName: true
}
});

const auditToUpdate = await tx.audit.findUnique({
where: { editUniqueId: uniqueId },
select: { auditorName: true }
});

// Update audit with new owner info if any
const data: Pick<Audit, "auditorEmail" | "auditorName" | "auditorOrganisation"> = {
auditorEmail: newEmail,
auditorName: user?.name
? user.name
: (auditToUpdate.auditorName || ""),
auditorOrganisation: (user && user.orgName) ? user.orgName : ""
};

return await tx.audit.update({
where: { editUniqueId: uniqueId },
data,
select: AUDIT_PRISMA_SELECT
});
});

return updatedAudit;
}
}
36 changes: 36 additions & 0 deletions confiture-rest-api/src/audits/audits.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { GetPageWithResultsDto } from "./dto/get-page-with-results.dto";
import { CreateAuditDto } from "./dto/requests/create-audit.dto";
import { DuplicateAuditDto } from "./dto/requests/duplicate-audit.dto";
import { PatchAuditDto } from "./dto/requests/patch-audit.dto";
import { TransferAuditDto } from "./dto/requests/transfer-audit.dto";
import { UpdateAuditDto } from "./dto/requests/update-audit.dto";
import { UpdateResultsDto } from "./dto/requests/update-results.dto";
import { UploadImageDto } from "./dto/requests/upload-image.dto";
Expand Down Expand Up @@ -407,6 +408,41 @@ export class AuditsController {
return file;
}

/**
* TODO: add decorator @AuditId if PR is merged
*/
@Put("/:uniqueId/transfer")
@ApiCreatedResponse({
description: "The audit has been successfully transfered.",
type: AuditDto
})
@ApiNotFoundResponse({ description: "The audit does not exist." })
@ApiGoneResponse({ description: "The audit has been previously deleted." })
async transferAudit(
@Param("uniqueId") uniqueId: string,
@Body() body: TransferAuditDto
) {
const newAudit = await this.auditService.transferAudit(uniqueId, body.newEmail);

if (!newAudit) {
await this.sendAuditNotFoundStatus(uniqueId);
}

this.mailer.sendAuditTransferEmail(body.newEmail, {
editUniqueId: uniqueId,
auditorEmail: body.senderEmail,
auditorName: newAudit.auditorName,
procedureName: newAudit.procedureName
}).catch((err) => {
console.error(
`Failed to send transfer email for audit ${newAudit.editUniqueId}`
);
console.error(err);
});

return newAudit;
}

/**
* Send 404 (Not Found) status for audits that never existed
* and 410 (Gone) for audits that existed but were deleted.
Expand Down
15 changes: 15 additions & 0 deletions confiture-rest-api/src/audits/dto/requests/transfer-audit.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsEmail } from "class-validator";

export class TransferAuditDto {
/**
* @example "name@domain.com"
*/
@IsEmail()
senderEmail: string;

/**
* @example "name@domaine.com"
*/
@IsEmail()
newEmail: string;
}
27 changes: 27 additions & 0 deletions confiture-rest-api/src/mail/audit-transfer-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { renderMailTemplate } from "./render-mjml-template";

export interface AuditTransferEmailData {
procedureName: string;
auditUrl: string;
senderEmail: string;
senderName: string;
}

export function subject(): string {
return "Un audit vous a été transféré";
}

export function html(data: AuditTransferEmailData): string {
return renderMailTemplate("audit-transfer", data);
}

export function plain(data: AuditTransferEmailData): string {
return `Un audit vous a été transféré

${data.senderName ? data.senderName + " (" + data.senderEmail + ")" : data.senderEmail} vous a transféré l’audit « ${data.procedureName} ». Si vous possédez un compte Ara, vous retrouverez cet audit dans votre espace.

${data.auditUrl}

Besoin d’aide ? Écrivez-nous : ara@design.numerique.gouv.fr
`;
}
19 changes: 18 additions & 1 deletion confiture-rest-api/src/mail/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as accountConfirmationEmail from "./account-confirmation-email";
import * as accountVerificationEmail from "./account-verification-email";
import * as auditCreationEmail from "./audit-creation-email";
import { AuditCreationEmailData } from "./audit-creation-email";
import * as auditTransferEmail from "./audit-transfer-email";
import { AuditTransferEmailData } from "./audit-transfer-email";
import { EmailConfig } from "./email-config.interface";
import * as passwordUpdateConfirmationEmail from "./password-update-confirmation-email";
import * as requestPasswordResetEmail from "./request-password-reset-email";
Expand All @@ -22,7 +24,8 @@ const EMAILS: Record<EmailType, EmailConfig> = {
[EmailType.PASSWORD_UPDATE_CONFIRMATION]: passwordUpdateConfirmationEmail,
[EmailType.EMAIL_UPDATE_VERIFICATION]: updateEmailVerificationEmail,
[EmailType.EMAIL_UPDATE_CONFIRMATION]: updateEmailConfirmationEmail,
[EmailType.PASSWORD_RESET_REQUEST]: requestPasswordResetEmail
[EmailType.PASSWORD_RESET_REQUEST]: requestPasswordResetEmail,
[EmailType.AUDIT_TRANSFER]: auditTransferEmail
};

@Injectable()
Expand Down Expand Up @@ -161,4 +164,18 @@ export class MailService {
verificationLink
});
}

sendAuditTransferEmail(email: string, audit: Pick<Audit, "editUniqueId" | "procedureName" | "auditorName" | "auditorEmail">) {
const baseUrl = this.config.get<string>("FRONT_BASE_URL");
const auditUrl = `${baseUrl}/audits/${audit.editUniqueId}/synthese`;

const data: AuditTransferEmailData = {
auditUrl,
senderName: audit.auditorName,
senderEmail: audit.auditorEmail,
procedureName: audit.procedureName
};

return this.sendMail(email, EmailType.AUDIT_TRANSFER, data);
}
}
37 changes: 37 additions & 0 deletions confiture-rest-api/templates/audit-transfer.mjml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<mjml>
<mj-head>
<mj-title>Un audit vous a été transféré</mj-title>
<mj-attributes>
<mj-preview>Un audit vous a été transféré</mj-preview>
<mj-include path="./styles.mjml" />
</mj-attributes>
</mj-head>
<mj-body background-color="#fff">
<!-- HEADER -->
<mj-include path="./header.mjml" />

<!-- BODY -->
<mj-section>
<mj-column>
<mj-text font-size="20px" font-weight="700" padding-bottom="8px">
Un audit vous a été transféré
</mj-text>
<mj-text padding-bottom="24px">
{{#if senderName}}{{senderName}} ({{senderEmail}}){{else}}{{senderEmail}}{{/if}}
vous a transféré l’audit « <strong>{{procedureName}}</strong> ». Si vous possédez un compte Ara, vous retrouverez cet audit dans votre espace.
</mj-text>
<mj-button href="{{auditUrl}}" mj-class="blue-button">Accéder à l’audit</mj-button>
</mj-column>
</mj-section>

<mj-section>
<mj-column>
<mj-text font-size="16px" font-weight="700" padding-top="32px" padding-bottom="12px">Vous ne possédez pas encore de compte Ara ?</mj-text>
<mj-text>Créez votre compte pour retrouver facilement tous vos audits au même endroit : <br/><a href="https://ara.numerique.gouv.fr/compte/nouveau" style="color: #000091">Créer un compte Ara</a></mj-text>
</mj-column>
</mj-section>

<!-- FOOTER -->
<mj-include path="./footer.mjml" />
</mj-body>
</mjml>
2 changes: 1 addition & 1 deletion confiture-web-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Il extiste des variantes des composants de champs fréquement utilisés (`<DsfrF
<DsfrField :ref="focusRef" v-model="email" :error="error" label="Votre email" />
</FieldValidation>

<!-- Shortcut component. Also exists for DsfrFiel -->
<!-- Shortcut component. Also exists for DsfrField -->
<DsfrPasswordWithValidation
v-model="userPassword"
label="Mot de passe"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 41 additions & 1 deletion confiture-web-app/src/components/account/dashboard/AuditRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const props = defineProps<{
zIndex?: number;
}>();

defineEmits(["delete"]);
defineEmits(["delete", "transfer"]);

const notify = useNotifications();
const auditStore = useAuditStore();
Expand Down Expand Up @@ -75,6 +75,25 @@ function duplicateAudit(name: string) {
});
}

// const transferModalRef = useTemplateRef<InstanceType<typeof DuplicateModal>>("transferModalRef");

// async function transferAudit(newEmail: string) {
// try {
// await auditStore.transferAudit(props.audit.editUniqueId, newEmail);

// transferModalRef.value?.hide();

// notify("success", `Audit « ${props.audit.procedureName} » transféré`, `Un lien d’accès a été envoyé à : ${newEmail}`);
// } catch (error) {
// notify(
// "error",
// "Échec du transfert de l'audit",
// DEFAULT_NOTIFICATION_ERROR_DESCRIPTION
// );
// captureWithPayloads(error);
// }
// }

const csvExportUrl = computed(
() => `/api/audits/${props.audit.editUniqueId}/exports/csv`
);
Expand Down Expand Up @@ -284,6 +303,16 @@ defineExpose({
<span class="fr-sr-only"> {{ audit.procedureName }}</span>
</button>
</li>
<li class="dropdown-item">
<button
class="fr-btn fr-btn--tertiary-no-outline fr-btn--icon-left fr-icon-share-forward-line fr-m-0"
@click="$emit('transfer')"
>
Transférer l’audit
<span class="fr-sr-only"> {{ audit.procedureName }}</span>
<span class="fr-badge fr-badge--sm fr-badge--yellow-tournesol fr-icon-flashlight-fill fr-badge--icon-left fr-ml-1-5v">Nouveau</span>
</button>
</li>
<li class="dropdown-item">
<RouterLink
class="fr-btn fr-btn--tertiary-no-outline fr-btn--icon-left fr-icon-settings-5-line fr-m-0"
Expand Down Expand Up @@ -357,6 +386,17 @@ defineExpose({
optionsDropdownRef?.closeOptions();
"
/>

<!-- <TransferModal
:id="audit.editUniqueId"
ref="transferModalRef"
:procedure-name="audit.procedureName"
@confirm="transferAudit"
@closed="
optionsDropdownRef?.buttonRef?.focus();
optionsDropdownRef?.closeOptions();
"
/> -->
</template>

<style scoped>
Expand Down
Loading
Loading