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
+139
View File
@@ -0,0 +1,139 @@
import { ChangeDetectionStrategy, Component, computed, effect, inject, input } from '@angular/core'
import { RouterLink } from '@angular/router'
import { COMPANY_PAGES, companyPagePath, findCompanyPage } from '../../content/company-pages'
import { SITE_CONFIG } from '../core/app-config'
import { SeoService } from '../core/seo.service'
import { breadcrumbJsonLd } from '../core/structured-data'
import { PageHeader } from '../shared/page-header'
import { PlaceholderBadge } from '../shared/placeholder-badge'
/**
* /azienda e /azienda/:pagina, generate da src/content/company/<slug>.ts. /azienda mostra la prima pagina
* (Chi siamo); gli slug sconosciuti non arrivano qui (canMatch → 404).
*/
@Component({
selector: 'app-company-page',
imports: [RouterLink, PageHeader, PlaceholderBadge],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@let p = page();
<app-page-header [heading]="p.title" [lead]="p.lead" [crumbs]="crumbs()">
<app-placeholder-badge badge [show]="p.placeholder" />
</app-page-header>
<div class="container layout">
<nav class="subnav" aria-label="Azienda">
<ul>
@for (item of pages; track item.slug) {
<li>
<a [routerLink]="path(item)" [attr.aria-current]="item === p ? 'page' : null">{{ item.title }}</a>
</li>
}
</ul>
</nav>
<article>
@for (section of p.sections; track section.heading) {
<section>
<h2>{{ section.heading }}</h2>
@for (paragraph of section.paragraphs; track $index) {
<p>{{ paragraph }}</p>
}
@if (section.items?.length) {
<ul class="items">
@for (item of section.items; track $index) {
<li>{{ item }}</li>
}
</ul>
}
</section>
}
@if (p.cta; as cta) {
<a class="btn btn--primary" [routerLink]="cta.path" [queryParams]="cta.query ?? null">{{ cta.label }}</a>
}
</article>
</div>
`,
styles: `
@use 'mixins' as mq;
.layout {
display: grid;
gap: var(--space-6);
padding-block: var(--space-6) var(--space-8);
@include mq.up(lg) {
grid-template-columns: 220px 1fr;
gap: var(--space-8);
}
}
.subnav ul {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin: 0;
padding: 0;
list-style: none;
@include mq.up(lg) {
flex-direction: column;
position: sticky;
top: calc(var(--header-height) + var(--space-5));
}
}
.subnav a {
display: flex;
align-items: center;
min-height: 44px;
padding-inline: var(--space-3);
border-radius: var(--radius-md);
color: var(--color-ink);
text-decoration: none;
&[aria-current='page'] {
background: var(--color-primary-soft);
color: var(--color-primary);
font-weight: var(--font-weight-semibold);
}
}
article {
max-width: 70ch;
}
section {
margin-bottom: var(--space-6);
}
h2 {
font-size: var(--font-size-2xl);
}
.items {
padding-left: var(--space-5);
li {
margin-bottom: var(--space-2);
}
}
`,
})
export default class CompanyPage {
/** Parametro di route (assente su /azienda). */
readonly pagina = input<string>()
protected readonly pages = COMPANY_PAGES
protected readonly path = companyPagePath
protected readonly page = computed(() => findCompanyPage(this.pagina() ?? '') ?? COMPANY_PAGES[0]!)
protected readonly crumbs = computed(() =>
this.pagina() ? [{ label: 'Azienda', path: '/azienda' }, { label: this.page().title }] : [{ label: 'Azienda' }],
)
constructor() {
const seo = inject(SeoService)
const { siteUrl } = inject(SITE_CONFIG)
effect(() => {
const page = this.page()
const path = companyPagePath(page)
seo.setPage({ title: page.title, description: page.description, path })
seo.setJsonLd(
'breadcrumb',
breadcrumbJsonLd(siteUrl, path === '/azienda' ? [{ name: 'Azienda', path }] : [{ name: 'Azienda', path: '/azienda' }, { name: page.title, path }]),
)
})
}
}