15 Commits

15 changed files with 146 additions and 67 deletions

View File

@@ -2,7 +2,7 @@ import datetime
from typing import List, Literal from typing import List, Literal
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, validator
from beanie.operators import ElemMatch from beanie.operators import ElemMatch
from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
@@ -181,6 +181,19 @@ class Contract(CrudDocument):
lawyer: Entity = Field(title="Avocat en charge") lawyer: Entity = Field(title="Avocat en charge")
location: str = Field(title="Lieu") location: str = Field(title="Lieu")
date: datetime.date = Field(title="Date") date: datetime.date = Field(title="Date")
label: str = None
@validator("label", always=True)
def generate_label(cls, v, values, **kwargs):
if not v:
contract_label = values['title']
for p in values['parties']:
contract_label = contract_label + f" - {p.entity.label}"
contract_label = contract_label + f" - {values['date'].strftime('%m/%d/%Y')}"
return contract_label
return v
class Settings(CrudDocument.Settings): class Settings(CrudDocument.Settings):
fulltext_search = ['name', 'title'] fulltext_search = ['name', 'title']

View File

@@ -130,7 +130,7 @@ async def create_pdf(contract_id: str) -> str:
return FileResponse( return FileResponse(
contract_path, contract_path,
media_type="application/pdf", media_type="application/pdf",
filename=contract.name) filename=contract.label)
def retrieve_signature_png(filepath): def retrieve_signature_png(filepath):
@@ -139,3 +139,18 @@ def retrieve_signature_png(filepath):
base64_utf8_str = base64.b64encode(b_content).decode('utf-8') base64_utf8_str = base64.b64encode(b_content).decode('utf-8')
ext = filepath.split('.')[-1] ext = filepath.split('.')[-1]
return f'data:image/{ext};base64,{base64_utf8_str}' return f'data:image/{ext};base64,{base64_utf8_str}'
@print_router.get("/opengraph/{signature_id}", response_class=HTMLResponse)
async def get_signature_opengraph(signature_id: str, request: Request) -> str:
contract = await Contract.find_by_signature_id(signature_id)
signature = contract.get_signature(signature_id)
template = templates.get_template("opengraph.html")
signatory = signature.representative.label if signature.representative else signature.entity.label
return template.render({
"signatory": signatory,
"title": contract.label,
"origin_url": f"{request.url.scheme}://{request.url.hostname}"
})

View File

@@ -0,0 +1,10 @@
<html>
<head>
<meta property="og:title" content="{{ title }}" />
<meta property="og:description" content="Cette page est à la destination exclusive de {{ signatory }}
Si vous n'êtes pas {{ signatory }}, veuillez fermer cette page immédiatement et surpprimer tous les liens en votre possession menant vers celle-ci.
En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l'article L.229 du code pénal de l'Etat de San Andreas pour usurpation d'identité et vous vous exposez ainsi à une amende de 20 000$ ainsi qu'à des poursuites civiles.
Le cabinet Cooper, Hillman & Toshi LLC" />
<meta property="og:image" content="{{ origin_url }}/assets/logo.png" />
</head>
</html>

View File

@@ -117,6 +117,10 @@ p {
text-align: justify; text-align: justify;
} }
li {
margin: 16px 0;
}
.footer { .footer {
margin-top: 30px; margin-top: 30px;
page-break-inside: avoid; page-break-inside: avoid;

View File

@@ -28,8 +28,8 @@ class Individual(EntityType):
@property @property
def label(self) -> str: def label(self) -> str:
if len(self.surnames) > 0: # if len(self.surnames) > 0:
return '{} "{}" {}'.format(self.firstname, self.surnames[0], self.lastname) # return '{} "{}" {}'.format(self.firstname, self.surnames[0], self.lastname)
return '{} {}'.format(self.firstname, self.lastname) return '{} {}'.format(self.firstname, self.lastname)

View File

@@ -78,37 +78,22 @@ export class ContractsCardComponent extends BaseContractsComponent{
@Component({ @Component({
template: ` template: `
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-1"> <div>
<ngb-panel> <iframe width="100%"
<ng-template ngbPanelTitle> [src]="getPreview()"
<span i18n>Preview</span> onload='javascript:(function(o){o.style.height=o.contentWindow.document.body.scrollHeight+"px";o.style.width=o.contentWindow.document.body.scrollWidth+"px";}(this));' style="height:200px;width:100%;border:none;overflow:hidden;">
</ng-template> </iframe>
<ng-template ngbPanelContent> </div>
<iframe width="100%" <div class="row" *ngIf="!this.affixed">
[src]="getPreview()" <signature-drawer class="col-7"
onload='javascript:(function(o){o.style.height=o.contentWindow.document.body.scrollHeight+"px";o.style.width=o.contentWindow.document.body.scrollWidth+"px";}(this));' style="height:200px;width:100%;border:none;overflow:hidden;"></iframe> (signatureDrawn$)="postSignature($event)"></signature-drawer>
</ng-template> <div class="col-5" i18n>
</ngb-panel> <p>Cette page est à la destination exclusive de <strong>{{ this.signatory }}</strong></p>
<ngb-panel> <p>Si vous n'êtes <strong>pas</strong> {{ this.signatory }}, veuillez <strong>fermer cette page immédiatement</strong> et surpprimer tous les liens en votre possession menant vers celle-ci.</p>
<ng-template ngbPanelTitle> <p>En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l'article L.229 du code pénal de l'Etat de San Andreas pour <strong>usurpation d'identité</strong> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu'à des poursuites civiles.</p>
<span i18n>Signature</span> <p>Le cabinet Cooper, Hillman & Toshi LLC</p>
</ng-template> </div>
<ng-template ngbPanelContent> </div>`
<ng-container *ngIf="this.affixed"><ng-container i18n>This Contract has already been signed by</ng-container> {{ this.signatory }}</ng-container>
<div class="row" *ngIf="!this.affixed">
<signature-drawer class="col-7"
(signatureDrawn$)="postSignature($event)"></signature-drawer>
<div class="col-5" i18n>
<p>Cette page est à la destination exclusive de <strong>{{ this.signatory }}</strong></p>
<p>Si vous n'êtes <strong>pas</strong> {{ this.signatory }}, veuillez <strong>fermer cette page immédiatement</strong> et surpprimer tous les liens en votre possession menant vers celle-ci.</p>
<p>En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l'article L.229 du code pénal de l'Etat de San Andreas pour <strong>usurpation d'identité</strong> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu'à des poursuites civiles.</p>
<p>Le cabinet Cooper, Hillman & Toshi LLC</p>
</div>
</div>
</ng-template>
</ngb-panel>
</ngb-accordion>
`
}) })
export class ContractsSignatureComponent implements OnInit { export class ContractsSignatureComponent implements OnInit {
signature_id: string | null = null; signature_id: string | null = null;

View File

@@ -21,7 +21,6 @@
(filterChange)="onFilterChange($event)" (filterChange)="onFilterChange($event)"
></crud-list-filter-list> ></crud-list-filter-list>
</div> </div>
<span class="col col-form-label" i18n *ngIf="loading$ | async">Loading...</span>
</div> </div>
<div class="table-responsive-md"> <div class="table-responsive-md">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
@@ -31,6 +30,9 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngIf="loading$ | async">
<td class="text-center" [attr.colspan]="this.displayedColumns.length" i18n>Loading...</td>
</tr>
<tr *ngFor="let row of listData$ | async" (click)="onRowClick(row._id)" (auxclick)="onRowMiddleClick(row._id);" class="table-row-link"> <tr *ngFor="let row of listData$ | async" (click)="onRowClick(row._id)" (auxclick)="onRowMiddleClick(row._id);" class="table-row-link">
<td class="text-truncate" *ngFor="let col of this.displayedColumns" style="max-width: 150px;"> <td class="text-truncate" *ngFor="let col of this.displayedColumns" style="max-width: 150px;">
<ngb-highlight [result]="getColumnValue(row, col.path)" [term]="searchTerm"></ngb-highlight> <ngb-highlight [result]="getColumnValue(row, col.path)" [term]="searchTerm"></ngb-highlight>
@@ -40,7 +42,7 @@
</table> </table>
</div> </div>
<div class="d-flex justify-content-between p-2"> <div class="d-flex justify-content-between p-2" *ngIf="! (loading$ | async)" >
<ngb-pagination [collectionSize]="(total$ | async)!" [(page)]="page" [pageSize]="pageSize"> <ngb-pagination [collectionSize]="(total$ | async)!" [(page)]="page" [pageSize]="pageSize">
</ngb-pagination> </ngb-pagination>

View File

@@ -68,7 +68,6 @@ export class ListComponent implements OnInit {
if (parsedParams.hasOwnProperty('searchFilters')) { if (parsedParams.hasOwnProperty('searchFilters')) {
parsedParams['searchFilters'] = JSON.parse(parsedParams['searchFilters']); parsedParams['searchFilters'] = JSON.parse(parsedParams['searchFilters']);
} }
this._total$.next(this.page * this.pageSize);
this._set(parsedParams) this._set(parsedParams)
}); });
} }

View File

@@ -2,36 +2,44 @@ import {Component, OnInit} from '@angular/core';
import { FieldArrayType } from '@ngx-formly/core'; import { FieldArrayType } from '@ngx-formly/core';
@Component({ @Component({
selector: 'formly-array-type', selector: 'formly-array-type',
template: ` template: `
<div> <div class="mb-3">
<label *ngIf="props.label" class="form-label">{{ props.label }}</label> <ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0">
<p *ngIf="props.description">{{ props.description }}</p> <ngb-panel id="ngb-panel-0">
<div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors"> <ng-template ngbPanelTitle>
<formly-validation-message [field]="field"></formly-validation-message> <label *ngIf="props.label" class="form-label">{{ props.label }}</label>
</div> <p *ngIf="props.description">{{ props.description }}</p>
<div class="row row-cols-1 row-cols-md-{{this.itemsPerRow}} g-1"> <div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors">
<div *ngFor="let entry of field.fieldGroup; let i = index" class="col"> <formly-validation-message [field]="field"></formly-validation-message>
<div class="card">
<div class="card-header">
<div *ngIf="props['numbered']" class="float-start">
<strong>{{ i + 1 }}</strong>
</div> </div>
<div *ngIf="! this.field.props.readonly" class="btn-group float-end"> </ng-template>
<button class="btn btn-primary btn-sm" [attr.disabled]="i == 0 ? 'disabled' : null" type="button" (click)="move(i, i-1)"><i-bs name="caret-up-fill"></i-bs></button> <ng-template ngbPanelContent>
<button class="btn btn-primary btn-sm" [attr.disabled]="i == this.field.fieldGroup!.length-1 ? 'disabled' : null" type="button" (click)="move(i, i+1)"><i-bs name="caret-down-fill"></i-bs></button> <div class="row row-cols-1 row-cols-md-{{this.itemsPerRow}} g-1">
<button class="btn btn-danger btn-sm" [attr.disabled]="field.props!['removable'] === false ? 'disabled' : null" type="button" (click)="remove(i)"><i-bs name="x-octagon-fill"></i-bs></button> <div *ngFor="let entry of field.fieldGroup; let i = index" class="col">
<div class="card">
<div class="card-header">
<div *ngIf="props['numbered']" class="float-start">
<strong>{{ i + 1 }}</strong>
</div>
<div *ngIf="! this.field.props.readonly" class="btn-group float-end">
<button class="btn btn-primary btn-sm" [attr.disabled]="i == 0 ? 'disabled' : null" type="button" (click)="move(i, i-1)"><i-bs name="caret-up-fill"></i-bs></button>
<button class="btn btn-primary btn-sm" [attr.disabled]="i == this.field.fieldGroup!.length-1 ? 'disabled' : null" type="button" (click)="move(i, i+1)"><i-bs name="caret-down-fill"></i-bs></button>
<button class="btn btn-danger btn-sm" [attr.disabled]="field.props!['removable'] === false ? 'disabled' : null" type="button" (click)="remove(i)"><i-bs name="x-octagon-fill"></i-bs></button>
</div>
</div>
<div class="card-body">
<formly-field class="col" [field]="entry"></formly-field>
</div>
</div>
</div>
</div> </div>
</div> <button *ngIf="! this.field.props.readonly" class="btn btn-success col-sm-12 gap-3" type="button" (click)="add()"><i-bs name="plus-square-fill"></i-bs></button>
<div class="card-body"> </ng-template>
<formly-field class="col" [field]="entry"></formly-field> </ngb-panel>
</div> </ngb-accordion>
</div>
</div>
</div> </div>
<button *ngIf="! this.field.props.readonly" class="btn btn-success col-sm-12" type="button" (click)="add()"><i-bs name="plus-square-fill"></i-bs></button> `,
</div>
`,
}) })
export class ArrayTypeComponent extends FieldArrayType implements OnInit { export class ArrayTypeComponent extends FieldArrayType implements OnInit {
colSm: string = "col-sm-6" colSm: string = "col-sm-6"

View File

@@ -10,7 +10,7 @@ import {FormlyJsonschema} from "@ngx-formly/core/json-schema";
template: ` template: `
<div class="mb-3"> <div class="mb-3">
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0"> <ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0">
<ngb-panel> <ngb-panel id="ngb-panel-0">
<ng-template ngbPanelTitle> <ng-template ngbPanelTitle>
<label *ngIf="props.label && props['hideLabel'] !== true" [attr.for]="id" <label *ngIf="props.label && props['hideLabel'] !== true" [attr.for]="id"
class="form-label">{{ props.label }} class="form-label">{{ props.label }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 318 B

View File

@@ -2,10 +2,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>App</title> <title>Cooper, Hillman & Toshi</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<meta property="og:title" content="Cooper, Hillman & Toshi">
<meta property="og:description" content="Interface d'administration des contrats">
<meta property="og:image" content="/assets/logo.png">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>

View File

@@ -36,4 +36,5 @@
.nav-link.active { .nav-link.active {
border: #D2BA6F solid 2px; border: #D2BA6F solid 2px;
border-radius: 0;
} }

View File

@@ -41,6 +41,25 @@ http {
default_type text/javascript; default_type text/javascript;
} }
location /contracts/signature/ {
set $is_robot 0;
if ($http_user_agent ~* "Discordbot|googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp") {
rewrite /contracts/signature/(.*) /api/v1/contract/print/opengraph/$1 last;
proxy_pass http://docker-back;
set $is_robot 1;
}
if ($is_robot = 0) {
rewrite ^ /index.html?$args last;
}
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /api/v1/ { location /api/v1/ {
proxy_pass http://docker-back/; proxy_pass http://docker-back/;
proxy_redirect off; proxy_redirect off;

View File

@@ -26,6 +26,26 @@ http {
proxy_set_header X-Forwarded-Host $server_name; proxy_set_header X-Forwarded-Host $server_name;
} }
location /contracts/signature/ {
set $is_robot 0;
if ($http_user_agent ~* "Discordbot|googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp") {
rewrite /contracts/signature/(.*) /api/v1/contract/print/opengraph/$1 last;
proxy_pass http://docker-back;
break;
set $is_robot 1;
}
if ($is_robot = 0) {
proxy_pass http://docker-front;
}
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /api/v1/ { location /api/v1/ {
proxy_pass http://docker-back/; proxy_pass http://docker-back/;
proxy_redirect off; proxy_redirect off;