7 Commits

Author SHA1 Message Date
fce53eae3c ci: bump runner-image pin v0.1.0 → v0.2.0
All checks were successful
build / build (pull_request) Successful in 5m50s
2026-05-04 21:50:17 +02:00
fa2a81ecd1 fix(ci): correct image digest separator
All checks were successful
build / build (push) Successful in 7m47s
2026-04-30 12:20:33 +02:00
8e0554572b ci: digest-pin runner-image v0.1.0
Some checks failed
build / build (push) Failing after 0s
2026-04-30 12:17:55 +02:00
3213a5a400 ci: update runner-image path to public/ namespace
Some checks failed
build / build (push) Failing after 1s
2026-04-30 11:57:23 +02:00
5c36ce7dbb chore: update action ref to public/actions
All checks were successful
build / build (push) Successful in 6m4s
2026-04-29 22:50:14 +02:00
9600bc8fd3 chore: slim build.yml to use shared docker-build action (#3)
All checks were successful
build / build (push) Successful in 6m43s
2026-04-29 22:08:31 +02:00
defd078e4b feat(cms): white-label email templates and API doc config (#2)
All checks were successful
Build and publish / build (push) Successful in 7m11s
## Summary

- Replace all hardcoded muellerprints brand identity in email templates with template variables sourced from env vars
- Inject shop context into both `sendInvoice` and `sendDeliveryNote` template calls in `order.ts`
- Make API documentation title/description env-var driven

## New env vars (all optional with safe defaults)

| Var | Default | Used in |
|-----|---------|---------|
| `SHOP_NAME` | `"Shop"` | Email subject, body, footer |
| `SHOP_CONTACT_NAME` | `""` | Email footer |
| `SHOP_ADDRESS` | `""` | Email footer |
| `SHOP_PHONE` | `""` | Email footer |
| `SHOP_EMAIL` | `ADMIN_EMAIL_ADDRESS` | Email footer |
| `SHOP_LOGO_URL` | `""` | Email header logo |
| `SHOP_SECONDARY_LOGO_URL` | `""` | Email footer logo |
| `API_TITLE` | `"Paperwork API"` | Swagger/OpenAPI title |
| `API_DESCRIPTION` | `"Paperwork API"` | Swagger/OpenAPI description |

## Test plan

- [ ] Set `SHOP_NAME=TestShop` and trigger invoice send → confirm subject and body use `TestShop`
- [ ] Leave `SHOP_NAME` unset → confirm default `"Shop"` appears
- [ ] Set `SHOP_LOGO_URL` → confirm logo renders in email header; unset → confirm no broken `<img>` tag
- [ ] Check Strapi admin `/documentation` with `API_TITLE=MyAPI` env var

Closes #1

Reviewed-on: #2
Co-authored-by: Michael Czechowski <mail@dailysh.it>
Co-committed-by: Michael Czechowski <mail@dailysh.it>
2026-04-29 20:13:23 +02:00
7 changed files with 80 additions and 86 deletions

View File

@@ -1,4 +1,4 @@
name: Build and publish name: build
on: on:
push: push:
@@ -7,49 +7,18 @@ on:
pull_request: pull_request:
branches: [main] branches: [main]
# Required secrets:
# REGISTRY git.librete.ch
# REGISTRY_USER libretech-bot
# REGISTRY_PASS bot PAT (write:package; bot is in libreshop Owners team)
# Required variable:
# PUBLISH_ENABLED "true" to actually push (off = build-only on PRs)
#
# Image: git.librete.ch/libreshop/cms
# main pushes → :main + :sha-<short>
# tag pushes → :<tag> + :latest
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: git.librete.ch/libretech/runner-image:v1 image: git.librete.ch/public/runner-image:v0.2.0@sha256:f60c587d3c0b0aac04a572db5349e27672bf76baec2ce547a3dcc28cebcf1b7e
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3 - uses: https://git.librete.ch/public/actions/.gitea/actions/docker-build@main
- name: Login (only when publishing)
if: ${{ vars.PUBLISH_ENABLED == 'true' }}
uses: docker/login-action@v3
with: with:
registry: ${{ secrets.REGISTRY }} registry: ${{ secrets.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }} registry_user: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASS }} registry_pass: ${{ secrets.REGISTRY_PASS }}
publish: ${{ github.event_name == 'push' && vars.PUBLISH_ENABLED == 'true' }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.REGISTRY }}/libreshop/cms
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
- uses: docker/build-push-action@v6
with:
context: .
push: ${{ vars.PUBLISH_ENABLED == 'true' && github.event_name == 'push' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -2,7 +2,26 @@
All notable changes to libreshop/cms are documented here. All notable changes to libreshop/cms are documented here.
## Unreleased ## [0.1.1] - 2026-04-29
### Changed
- White-label email templates: replace all hardcoded muellerprints company
identity (name, contact, address, phone, email, logo URLs) with
`<%= shopName %>` / `<%= shopContactName %>` / `<%= shopAddress %>` /
`<%= shopPhone %>` / `<%= shopEmail %>` / `<%= shopLogoUrl %>` /
`<%= shopSecondaryLogoUrl %>` template variables, sourced from env vars
`SHOP_NAME`, `SHOP_CONTACT_NAME`, `SHOP_ADDRESS`, `SHOP_PHONE`,
`SHOP_EMAIL`, `SHOP_LOGO_URL`, `SHOP_SECONDARY_LOGO_URL`.
- `config/constants.ts`: export shop brand constants from env.
- `src/api/order/services/order.ts`: inject shop context into template calls.
- `config/plugins.ts`: API doc title/description driven by `API_TITLE` /
`API_DESCRIPTION` env vars (defaults: `"Paperwork API"`).
- `src/extensions/documentation/config/settings.json`: neutral placeholder.
Closes #1
## [0.1.0] - 2026-04-29
- Extracted from `mp/cms/` (2026-04-29). The component history before - Extracted from `mp/cms/` (2026-04-29). The component history before
the extraction lives in the `muellerprints` repository. the extraction lives in the `muellerprints` repository.

View File

@@ -10,6 +10,14 @@ export const paypalEnvironment = process.env.PAYPAL_ENVIRONMENT! === "production
export const adminEmail = process.env.ADMIN_EMAIL_ADDRESS!; export const adminEmail = process.env.ADMIN_EMAIL_ADDRESS!;
export const shopName = process.env.SHOP_NAME ?? "Shop";
export const shopContactName = process.env.SHOP_CONTACT_NAME ?? "";
export const shopAddress = process.env.SHOP_ADDRESS ?? "";
export const shopPhone = process.env.SHOP_PHONE ?? "";
export const shopEmail = process.env.SHOP_EMAIL ?? process.env.ADMIN_EMAIL_ADDRESS ?? "";
export const shopLogoUrl = process.env.SHOP_LOGO_URL ?? "";
export const shopSecondaryLogoUrl = process.env.SHOP_SECONDARY_LOGO_URL ?? "";
// TODO: Should be retrieved from DepotApi // TODO: Should be retrieved from DepotApi
export const vatIncludedDecimal = 1.19; export const vatIncludedDecimal = 1.19;
// TODO: Should be retrieved from DepotApi // TODO: Should be retrieved from DepotApi

View File

@@ -10,15 +10,11 @@ export default ({ env }) => ({
openapi: "3.0.1", openapi: "3.0.1",
info: { info: {
version: "1.0.0", version: "1.0.0",
title: "MUELLERPTINTS. Paperwork", title: env("API_TITLE", "Paperwork API"),
description: "API Documentation for MUELLERPRINTS. Paperwork", description: env("API_DESCRIPTION", "Paperwork API"),
termsOfService: false, termsOfService: false,
contact: { contact: false,
name: "Michael W. Czechowski", license: false,
email: "mail@dailysh.it",
url: "https://dailysh.it"
},
license: "Copyright (C) 2024 Michael W. Czechowski",
externalDocs: false externalDocs: false
}, },
"x-strapi-config": { "x-strapi-config": {

View File

@@ -3,7 +3,7 @@ import fs from "fs";
import { factories } from "@strapi/strapi"; import { factories } from "@strapi/strapi";
import { sanitize } from "@strapi/utils"; import { sanitize } from "@strapi/utils";
import { ID } from "@strapi/database/dist/types"; import { ID } from "@strapi/database/dist/types";
import { vatDecimal, vatIncludedDecimal, baseUrl, adminEmail } from "../../../../config/constants"; import { vatDecimal, vatIncludedDecimal, baseUrl, adminEmail, shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl } from "../../../../config/constants";
import { calculateTotalProductPrice, productDefaultParams } from "../../product/services/product"; import { calculateTotalProductPrice, productDefaultParams } from "../../product/services/product";
import { CartProduct, Order, PdfBody } from "../../../../types"; import { CartProduct, Order, PdfBody } from "../../../../types";
import { pdfApi } from "../../../services/PdfApi"; import { pdfApi } from "../../../services/PdfApi";
@@ -81,12 +81,13 @@ export default factories.createCoreService("api::order.order", ({ strapi }) => (
strapi.log.info(`app:i:order-service: - Sending invoice for order ${uuid}`); strapi.log.info(`app:i:order-service: - Sending invoice for order ${uuid}`);
const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>; const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>;
const shopContext = { shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl };
try { try {
const emailConfig: MessageBody = { const emailConfig: MessageBody = {
to_email: oderUnsafe.email, to_email: oderUnsafe.email,
subject: template(invoiceEmailTemplate.subject)(oderUnsafe), subject: template(invoiceEmailTemplate.subject)({ ...shopContext, ...oderUnsafe }),
message: template(invoiceEmailTemplate.text)({ baseUrl, ...oderUnsafe }), message: template(invoiceEmailTemplate.text)({ baseUrl, ...shopContext, ...oderUnsafe }),
html: template(invoiceEmailTemplate.html)({ baseUrl, ...oderUnsafe }) html: template(invoiceEmailTemplate.html)({ baseUrl, ...shopContext, ...oderUnsafe })
}; };
strapi.log.debug(`app:d:order-service: - Email config ${emailConfig.html}`); strapi.log.debug(`app:d:order-service: - Email config ${emailConfig.html}`);
@@ -118,12 +119,13 @@ export default factories.createCoreService("api::order.order", ({ strapi }) => (
sendDeliveryNote: async (uuid: string, blob?: ArrayBuffer) => { sendDeliveryNote: async (uuid: string, blob?: ArrayBuffer) => {
strapi.log.info(`app:i:order-service: - Sending delivery note for order ${uuid}`); strapi.log.info(`app:i:order-service: - Sending delivery note for order ${uuid}`);
const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>; const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>;
const shopContext = { shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl };
try { try {
const emailConfig: MessageBody = { const emailConfig: MessageBody = {
to_email: oderUnsafe.email, to_email: oderUnsafe.email,
subject: template(deliveryNoteEmailTemplate.subject)(oderUnsafe), subject: template(deliveryNoteEmailTemplate.subject)({ ...shopContext, ...oderUnsafe }),
message: template(deliveryNoteEmailTemplate.text)(oderUnsafe), message: template(deliveryNoteEmailTemplate.text)({ ...shopContext, ...oderUnsafe }),
html: template(deliveryNoteEmailTemplate.html)({ ...oderUnsafe, baseUrl }) html: template(deliveryNoteEmailTemplate.html)({ baseUrl, ...shopContext, ...oderUnsafe })
}; };
await mailApi.sendTextMessage(emailConfig); await mailApi.sendTextMessage(emailConfig);
strapi.log.info(`app:i:order-service: ✔ Sending delivery note for order ${uuid}`); strapi.log.info(`app:i:order-service: ✔ Sending delivery note for order ${uuid}`);

View File

@@ -2,8 +2,8 @@
"openapi": "3.0.1", "openapi": "3.0.1",
"info": { "info": {
"version": "1.0.0", "version": "1.0.0",
"title": "MUELLERPTINTS. Paperwork", "title": "Paperwork API",
"description": "API Documentation for MUELLERPRINTS. Paperwork", "description": "Paperwork API",
"termsOfService": false, "termsOfService": false,
"contact": false, "contact": false,
"license": false "license": false

View File

@@ -1,5 +1,5 @@
export const invoiceEmailTemplate = { export const invoiceEmailTemplate = {
subject: "MUELLERPRINTS Rechnungsnummer <%= invoiceNumber %>", subject: "<%= shopName %> Rechnungsnummer <%= invoiceNumber %>",
text: `Rechnungsnummer: <%= invoiceNumber %> text: `Rechnungsnummer: <%= invoiceNumber %>
Ausstellungsdatum: <%= new Date(acceptedTermsAndConditionsAt).toLocaleDateString("de-DE") %> Ausstellungsdatum: <%= new Date(acceptedTermsAndConditionsAt).toLocaleDateString("de-DE") %>
@@ -7,7 +7,7 @@ Ausstellungsdatum: <%= new Date(acceptedTermsAndConditionsAt).toLocaleDateString
Hallo <%= invoiceAddressStructured.givenName %>, Hallo <%= invoiceAddressStructured.givenName %>,
vielen Dank für deinen Einkauf bei MUELLERPRINTS. vielen Dank für deinen Einkauf bei <%= shopName %>.
Deine Bestellnummer: <%= id %> Deine Bestellnummer: <%= id %>
Zur Bestellübersicht: <%= baseUrl %>/checkout/result/<%= uuid %> Zur Bestellübersicht: <%= baseUrl %>/checkout/result/<%= uuid %>
@@ -49,14 +49,14 @@ inkl. MwSt. (19%): <%= VAT.toFixed(2) %> EUR
Deine Rechnung kannst du jederzeit über diesen Link herunterladen: <%= baseUrl %><%= invoice.url %> Deine Rechnung kannst du jederzeit über diesen Link herunterladen: <%= baseUrl %><%= invoice.url %>
<% } %> <% } %>
Vielen Dank, dass du dich für MUELLERPRINTS entschieden hast. Vielen Dank, dass du dich für <%= shopName %> entschieden hast.
------------------ ------------------
MUELLERPRINTS <%= shopName %>
Max Müller <%= shopContactName %>
Rotenbergstraße 39, 70190 Stuttgart <%= shopAddress %>
T +49 (0)711/262 49 64 <%= shopPhone ? "T " + shopPhone : "" %>
paperwork@muellerprints.de`, <%= shopEmail %>`,
html: `<!DOCTYPE html> html: `<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@@ -74,7 +74,7 @@ paperwork@muellerprints.de`,
<!-- Header --> <!-- Header -->
<tr> <tr>
<td style="background-color: #E31E26; padding: 30px 40px;"> <td style="background-color: #E31E26; padding: 30px 40px;">
<img src="https://muellerprints-paperwork.com/paperwork-logo.png" alt="MUELLERPRINTS PAPERWORK" style="max-width: 200px; height: auto;"> <% if (shopLogoUrl) { %><img src="<%= shopLogoUrl %>" alt="<%= shopName %>" style="max-width: 200px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
@@ -83,7 +83,7 @@ paperwork@muellerprints.de`,
<td style="padding: 40px;"> <td style="padding: 40px;">
<!-- Greeting --> <!-- Greeting -->
<h1 style="margin: 0 0 20px; color: #333; font-size: 24px; font-weight: normal;"> <h1 style="margin: 0 0 20px; color: #333; font-size: 24px; font-weight: normal;">
Vielen Dank für deinen Einkauf bei MUELLERPRINTS. Vielen Dank für deinen Einkauf bei <%= shopName %>.
</h1> </h1>
<!-- Order Info --> <!-- Order Info -->
@@ -197,7 +197,7 @@ paperwork@muellerprints.de`,
<% } %> <% } %>
<p style="color: #666; font-size: 14px; margin: 20px 0;"> <p style="color: #666; font-size: 14px; margin: 20px 0;">
Vielen Dank, dass du dich für MUELLERPRINTS entschieden hast. Vielen Dank, dass du dich für <%= shopName %> entschieden hast.
</p> </p>
</td> </td>
</tr> </tr>
@@ -209,15 +209,15 @@ paperwork@muellerprints.de`,
<tr> <tr>
<td style="color: #666; font-size: 12px; line-height: 1.5;"> <td style="color: #666; font-size: 12px; line-height: 1.5;">
<p style="margin: 0 0 10px;"> <p style="margin: 0 0 10px;">
MUELLERPRINTS<br> <%= shopName %><br>
Max Müller<br> <% if (shopContactName) { %><%= shopContactName %><br><% } %>
Rotenbergstraße 39, 70190 Stuttgart<br> <% if (shopAddress) { %><%= shopAddress %><br><% } %>
T +49 (0)711/262 49 64<br> <% if (shopPhone) { %>T <%= shopPhone %><br><% } %>
paperwork@muellerprints.de <% if (shopEmail) { %><%= shopEmail %><% } %>
</p> </p>
</td> </td>
<td style="text-align: right; vertical-align: bottom;"> <td style="text-align: right; vertical-align: bottom;">
<img src="https://www.muellerprints.de/images/digi_logo.png" alt="MUELLERPRINTS" style="max-width: 100px; height: auto;"> <% if (shopSecondaryLogoUrl) { %><img src="<%= shopSecondaryLogoUrl %>" alt="<%= shopName %>" style="max-width: 100px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
</table> </table>
@@ -243,7 +243,7 @@ paperwork@muellerprints.de`,
}; };
export const deliveryNoteEmailTemplate = { export const deliveryNoteEmailTemplate = {
subject: "Your Delivery Note <%= deliveryNoteNumber %> from MUELLERPRINTS", subject: "Your Delivery Note <%= deliveryNoteNumber %> from <%= shopName %>",
text: `Dear Customer, text: `Dear Customer,
Thank you for your Your delivery note <%= deliveryNoteNumber %> is attached to this email. Thank you for your Your delivery note <%= deliveryNoteNumber %> is attached to this email.
@@ -272,14 +272,14 @@ Delivery Address:
If you have any questions about your delivery, please don't hesitate to contact us. If you have any questions about your delivery, please don't hesitate to contact us.
Best regards, Best regards,
MUELLERPRINTS PAPERWORK <%= shopName %>
------------------ ------------------
MUELLERPRINTS PAPERWORK <%= shopName %>
Max Müller <%= shopContactName %>
Rotenbergstraße 39, 70190 Stuttgart <%= shopAddress %>
T +49 (0)711/262 49 64 <%= shopPhone ? "T " + shopPhone : "" %>
paperwork@muellerprints.de`, <%= shopEmail %>`,
html: `<!DOCTYPE html> html: `<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@@ -295,7 +295,7 @@ paperwork@muellerprints.de`,
<!-- Header --> <!-- Header -->
<tr> <tr>
<td style="background-color: #E31E26; padding: 30px 40px;"> <td style="background-color: #E31E26; padding: 30px 40px;">
<img src="https://muellerprints-paperwork.com/paperwork-logo.png" alt="MUELLERPRINTS PAPERWORK" style="max-width: 200px; height: auto;"> <% if (shopLogoUrl) { %><img src="<%= shopLogoUrl %>" alt="<%= shopName %>" style="max-width: 200px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
@@ -407,15 +407,15 @@ paperwork@muellerprints.de`,
<tr> <tr>
<td style="color: #666; font-size: 12px; line-height: 1.5;"> <td style="color: #666; font-size: 12px; line-height: 1.5;">
<p style="margin: 0 0 10px;"> <p style="margin: 0 0 10px;">
MUELLERPRINTS PAPERWORK<br> <%= shopName %><br>
Max Müller<br> <% if (shopContactName) { %><%= shopContactName %><br><% } %>
Rotenbergstraße 39, 70190 Stuttgart<br> <% if (shopAddress) { %><%= shopAddress %><br><% } %>
T +49 (0)711/262 49 64<br> <% if (shopPhone) { %>T <%= shopPhone %><br><% } %>
paperwork@muellerprints.de <% if (shopEmail) { %><%= shopEmail %><% } %>
</p> </p>
</td> </td>
<td style="text-align: right; vertical-align: bottom;"> <td style="text-align: right; vertical-align: bottom;">
<img src="https://www.muellerprints.de/images/digi_logo.png" alt="MUELLERPRINTS" style="max-width: 100px; height: auto;"> <% if (shopSecondaryLogoUrl) { %><img src="<%= shopSecondaryLogoUrl %>" alt="<%= shopName %>" style="max-width: 100px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
</table> </table>