Piattaforma ITI TECHNOLOGY: monorepo, Medusa, API, sito, area riservata e deploy
Deploy stack su Proxmox / deploy (push) Failing after 3s

Monorepo npm con apps/public (Angular SSR), apps/private (Angular SPA),
apps/api (Fastify), packages/shared e packages/styles; Medusa 2.21 in medusa/;
infrastruttura Docker (Postgres PostGIS+pgvector, Redis, Caddy, backup) e
workflow Gitea Actions per il deploy via SSH a ogni push su master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jQyT6dR9czzGwqb7FkG6u
This commit is contained in:
Lorenzo Sanesi
2026-10-20 09:30:48 +02:00
co-authored by Claude Opus 5.5
commit b83ce039fb
505 changed files with 84544 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import { ChangeDetectionStrategy, Component, inject, input, signal } from '@angular/core'
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'
import { Title } from '@angular/platform-browser'
import { RouterLink } from '@angular/router'
import { ApiError, toApiError } from '../../core/api.service'
import { AuthStore } from '../../core/auth.store'
import { authErrorMessage } from '../../shared/form-errors'
import { FormField } from '../../shared/form-field'
/**
* Login. `?redirect=` (validato in core/redirect.ts) indica dove tornare: una pagina dell'area riservata
* o del sito pubblico (es. `/checkout`). `?scaduta=1` = sessione persa durante l'uso;
* `?account=disattivato` = account appena disattivato dall'utente.
*/
@Component({
selector: 'app-login',
imports: [ReactiveFormsModule, RouterLink, FormField],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<h1>Accedi</h1>
@if (account() === 'disattivato') {
<p class="alert alert--success" role="status">
Il tuo account è stato disattivato e la richiesta di cancellazione è stata inviata. Grazie per averci scelto.
</p>
}
@if (scaduta()) {
<p class="alert" role="status">La sessione è scaduta: accedi di nuovo per continuare.</p>
}
@if (error(); as message) {
<p class="alert alert--error" role="alert">{{ message }}</p>
}
<form [formGroup]="form" (ngSubmit)="submit()" novalidate>
<div class="form-grid single">
<app-form-field [control]="form.controls.email" label="Email" type="email" autocomplete="email" [submitted]="submitted()" />
<app-form-field
[control]="form.controls.password"
label="Password"
type="password"
autocomplete="current-password"
[submitted]="submitted()"
/>
</div>
<p class="forgot"><a routerLink="/forgot-password">Password dimenticata?</a></p>
<button type="submit" class="btn btn--primary btn--block" [disabled]="busy()">
{{ busy() ? 'Accesso in corso…' : 'Accedi' }}
</button>
</form>
<p class="switch">
Non hai un account?
<a routerLink="/registrazione" [queryParams]="{ redirect: redirect() || null }">Registrati</a>
</p>
`,
styles: `
.single {
grid-template-columns: 1fr;
}
.forgot {
margin: var(--space-2) 0 var(--space-5);
text-align: right;
}
.switch {
margin: var(--space-5) 0 0;
text-align: center;
}
`,
})
export default class Login {
private readonly auth = inject(AuthStore)
readonly redirect = input<string>()
readonly scaduta = input<string>()
/** `disattivato` dopo la richiesta di cancellazione dell'account. */
readonly account = input<string>()
protected readonly form = new FormGroup({
email: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] }),
password: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
})
protected readonly submitted = signal(false)
protected readonly busy = signal(false)
protected readonly error = signal<string | null>(null)
constructor() {
inject(Title).setTitle('Accedi | Area riservata ITI TECHNOLOGY')
}
protected async submit(): Promise<void> {
this.submitted.set(true)
this.error.set(null)
if (this.form.invalid || this.busy()) return
this.busy.set(true)
try {
const { email, password } = this.form.getRawValue()
await this.auth.login(email.trim(), password)
await this.auth.navigateAfterLogin(this.redirect() ?? null)
} catch (err) {
const error = err instanceof ApiError ? err : toApiError(err)
this.error.set(error.code === 'INVALID_CREDENTIALS' ? 'Email o password non corretti.' : authErrorMessage(error))
this.form.controls.password.reset()
} finally {
this.busy.set(false)
}
}
}