Compare commits
13 Commits
c8cf2892c9
...
f4b08e4c73
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4b08e4c73 | ||
|
|
db5c57ea5c | ||
|
|
0a56c741e8 | ||
|
|
668a67c094 | ||
|
|
acf3229f3f | ||
|
|
5367449d80 | ||
|
|
68407b944e | ||
|
|
c12d155e23 | ||
|
|
3f08f6189e | ||
|
|
b8fc55bcc2 | ||
|
|
6cfc8e7533 | ||
|
|
2e26cce91f | ||
|
|
8e8ca4d5e7 |
90
.github/workflows/deploy.yaml
vendored
Normal file
90
.github/workflows/deploy.yaml
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
name: deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
qa:
|
||||||
|
name: QA gates
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT_NAME: breadcrumb-ci
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create .env for CI
|
||||||
|
run: cp .env.example .env
|
||||||
|
|
||||||
|
- name: Start project services
|
||||||
|
run: |
|
||||||
|
docker compose up -d --force-recreate php db memcached
|
||||||
|
sleep 20
|
||||||
|
|
||||||
|
- name: Install Composer dependencies
|
||||||
|
run: |
|
||||||
|
docker compose exec -T php bash -c "cd /var/www && composer install --no-interaction --prefer-dist --optimize-autoloader"
|
||||||
|
|
||||||
|
- name: Run DB migrations
|
||||||
|
run: docker compose exec -T php php bin/console db:migrate
|
||||||
|
|
||||||
|
- name: Run required QA gates
|
||||||
|
run: bin/qa-required.sh
|
||||||
|
|
||||||
|
build-and-deploy:
|
||||||
|
name: Build & deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: qa
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build production image
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
--target prod \
|
||||||
|
-f docker/php/Dockerfile \
|
||||||
|
-t breadcrumb-the-shire:latest \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Export image to tar.gz
|
||||||
|
run: docker save breadcrumb-the-shire:latest | gzip > breadcrumb-the-shire.tar.gz
|
||||||
|
|
||||||
|
- name: Copy image to server
|
||||||
|
uses: appleboy/scp-action@v0.1.7
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
source: breadcrumb-the-shire.tar.gz
|
||||||
|
target: /root/dockerfiles-breadcrumb-infra/projects/
|
||||||
|
|
||||||
|
- name: Copy updated deploy files to server
|
||||||
|
uses: appleboy/scp-action@v0.1.7
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
source: "deploy/docker-compose.yml,deploy/nginx/"
|
||||||
|
target: /root/dockerfiles-breadcrumb-infra/projects/
|
||||||
|
strip_components: 1
|
||||||
|
|
||||||
|
- name: Deploy on server
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
cd /root/dockerfiles-breadcrumb-infra/projects
|
||||||
|
|
||||||
|
docker load < breadcrumb-the-shire.tar.gz
|
||||||
|
rm breadcrumb-the-shire.tar.gz
|
||||||
|
|
||||||
|
docker compose up -d --remove-orphans
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
docker compose exec -T php php bin/console module:sync
|
||||||
39
.github/workflows/qa-required.yaml
vendored
Normal file
39
.github/workflows/qa-required.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: qa-required
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
qa-required:
|
||||||
|
name: qa-required
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT_NAME: breadcrumb-ci
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create .env for CI
|
||||||
|
run: cp .env.example .env
|
||||||
|
|
||||||
|
- name: Start project services
|
||||||
|
run: |
|
||||||
|
docker compose up -d --force-recreate php db memcached
|
||||||
|
sleep 20
|
||||||
|
|
||||||
|
- name: Install Composer dependencies
|
||||||
|
run: |
|
||||||
|
docker compose exec -T php bash -c "cd /var/www && composer install --no-interaction --prefer-dist --optimize-autoloader"
|
||||||
|
|
||||||
|
- name: Run DB migrations
|
||||||
|
run: docker compose exec -T php php bin/console db:migrate
|
||||||
|
|
||||||
|
- name: Install ripgrep
|
||||||
|
run: sudo apt-get install -y ripgrep
|
||||||
|
|
||||||
|
- name: Run required QA gates
|
||||||
|
run: bin/qa-required.sh
|
||||||
@@ -59,7 +59,7 @@ class AuthServicesFactory
|
|||||||
$this->authRepositoryFactory->createPasswordResetRepository(),
|
$this->authRepositoryFactory->createPasswordResetRepository(),
|
||||||
$this->userServicesFactory->createUserPasswordService(),
|
$this->userServicesFactory->createUserPasswordService(),
|
||||||
$this->createRememberMeService(),
|
$this->createRememberMeService(),
|
||||||
$this->mailServicesFactory->createMailService()
|
$this->mailServicesFactory->createMailService(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class AuthServicesFactory
|
|||||||
$this->userServicesFactory->createUserReadRepository(),
|
$this->userServicesFactory->createUserReadRepository(),
|
||||||
$this->userServicesFactory->createUserWriteRepository(),
|
$this->userServicesFactory->createUserWriteRepository(),
|
||||||
$this->authRepositoryFactory->createEmailVerificationRepository(),
|
$this->authRepositoryFactory->createEmailVerificationRepository(),
|
||||||
$this->mailServicesFactory->createMailService()
|
$this->mailServicesFactory->createMailService(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,25 @@ class EmailVerificationService
|
|||||||
private readonly UserReadRepositoryInterface $userReadRepository,
|
private readonly UserReadRepositoryInterface $userReadRepository,
|
||||||
private readonly UserWriteRepositoryInterface $userWriteRepository,
|
private readonly UserWriteRepositoryInterface $userWriteRepository,
|
||||||
private readonly EmailVerificationRepositoryInterface $emailVerificationRepository,
|
private readonly EmailVerificationRepositoryInterface $emailVerificationRepository,
|
||||||
private readonly MailService $mailService
|
private readonly MailService $mailService,
|
||||||
|
private readonly string $appTitle = '',
|
||||||
|
private readonly string $appUrl = '',
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveAppTitle(): string
|
||||||
|
{
|
||||||
|
return $this->appTitle !== '' ? $this->appTitle : appTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveAppUrl(string $path = ''): string
|
||||||
|
{
|
||||||
|
if ($this->appUrl !== '') {
|
||||||
|
return rtrim($this->appUrl, '/') . ($path !== '' ? '/' . ltrim($path, '/') : '');
|
||||||
|
}
|
||||||
|
return appUrl($path);
|
||||||
|
}
|
||||||
|
|
||||||
public function sendVerification(int $userId, ?string $locale = null): array
|
public function sendVerification(int $userId, ?string $locale = null): array
|
||||||
{
|
{
|
||||||
$user = $this->userReadRepository->find($userId);
|
$user = $this->userReadRepository->find($userId);
|
||||||
@@ -55,17 +70,17 @@ class EmailVerificationService
|
|||||||
}
|
}
|
||||||
$greeting .= ',';
|
$greeting .= ',';
|
||||||
$verifyPath = Request::withLocale('verify-email', $locale);
|
$verifyPath = Request::withLocale('verify-email', $locale);
|
||||||
$verifyUrl = appUrl($verifyPath);
|
$verifyUrl = $this->resolveAppUrl($verifyPath);
|
||||||
$previousLocale = I18n::$locale ?? null;
|
$previousLocale = I18n::$locale ?? null;
|
||||||
I18n::$locale = $locale;
|
I18n::$locale = $locale;
|
||||||
$subject = t('Email verification code');
|
$subject = t('Email verification code');
|
||||||
I18n::$locale = $previousLocale;
|
I18n::$locale = $previousLocale;
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'app_name' => appTitle(),
|
'app_name' => $this->resolveAppTitle(),
|
||||||
'app_logo_url' => appLogoUrlAbsolute(128),
|
'app_logo_url' => $this->resolveAppUrl(appLogoUrl(128)),
|
||||||
'imprint_url' => appUrl(Request::withLocale('imprint', $locale)),
|
'imprint_url' => $this->resolveAppUrl(Request::withLocale('imprint', $locale)),
|
||||||
'privacy_url' => appUrl(Request::withLocale('privacy', $locale)),
|
'privacy_url' => $this->resolveAppUrl(Request::withLocale('privacy', $locale)),
|
||||||
'code' => $code,
|
'code' => $code,
|
||||||
'expires_minutes' => self::EXPIRY_MINUTES,
|
'expires_minutes' => self::EXPIRY_MINUTES,
|
||||||
'verify_url' => $verifyUrl,
|
'verify_url' => $verifyUrl,
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace MintyPHP\Service\Auth;
|
namespace MintyPHP\Service\Auth;
|
||||||
|
|
||||||
use MintyPHP\Curl;
|
|
||||||
|
|
||||||
class OidcHttpGateway
|
class OidcHttpGateway
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +47,7 @@ class OidcHttpGateway
|
|||||||
$body = @file_get_contents($url, false, $ctx);
|
$body = @file_get_contents($url, false, $ctx);
|
||||||
$status = 0;
|
$status = 0;
|
||||||
$responseHeaders = [];
|
$responseHeaders = [];
|
||||||
if (isset($http_response_header) && is_array($http_response_header)) {
|
if ($http_response_header !== []) {
|
||||||
foreach ($http_response_header as $i => $line) {
|
foreach ($http_response_header as $i => $line) {
|
||||||
if ($i === 0 && preg_match('/HTTP\/[\d.]+ (\d+)/', $line, $m)) {
|
if ($i === 0 && preg_match('/HTTP\/[\d.]+ (\d+)/', $line, $m)) {
|
||||||
$status = (int) $m[1];
|
$status = (int) $m[1];
|
||||||
|
|||||||
@@ -20,10 +20,25 @@ class PasswordResetService
|
|||||||
private readonly PasswordResetRepositoryInterface $passwordResetRepository,
|
private readonly PasswordResetRepositoryInterface $passwordResetRepository,
|
||||||
private readonly UserPasswordService $userPasswordService,
|
private readonly UserPasswordService $userPasswordService,
|
||||||
private readonly RememberMeService $rememberMeService,
|
private readonly RememberMeService $rememberMeService,
|
||||||
private readonly MailService $mailService
|
private readonly MailService $mailService,
|
||||||
|
private readonly string $appTitle = '',
|
||||||
|
private readonly string $appUrl = '',
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveAppTitle(): string
|
||||||
|
{
|
||||||
|
return $this->appTitle !== '' ? $this->appTitle : appTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveAppUrl(string $path = ''): string
|
||||||
|
{
|
||||||
|
if ($this->appUrl !== '') {
|
||||||
|
return rtrim($this->appUrl, '/') . ($path !== '' ? '/' . ltrim($path, '/') : '');
|
||||||
|
}
|
||||||
|
return appUrl($path);
|
||||||
|
}
|
||||||
|
|
||||||
public function requestReset(string $email, ?string $locale = null): array
|
public function requestReset(string $email, ?string $locale = null): array
|
||||||
{
|
{
|
||||||
$email = trim($email);
|
$email = trim($email);
|
||||||
@@ -58,17 +73,17 @@ class PasswordResetService
|
|||||||
}
|
}
|
||||||
$greeting .= ',';
|
$greeting .= ',';
|
||||||
$verifyPath = Request::withLocale('password/verify', $locale);
|
$verifyPath = Request::withLocale('password/verify', $locale);
|
||||||
$verifyUrl = appUrl($verifyPath);
|
$verifyUrl = $this->resolveAppUrl($verifyPath);
|
||||||
$previousLocale = I18n::$locale ?? null;
|
$previousLocale = I18n::$locale ?? null;
|
||||||
I18n::$locale = $locale;
|
I18n::$locale = $locale;
|
||||||
$subject = t('Password reset code');
|
$subject = t('Password reset code');
|
||||||
I18n::$locale = $previousLocale;
|
I18n::$locale = $previousLocale;
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'app_name' => appTitle(),
|
'app_name' => $this->resolveAppTitle(),
|
||||||
'app_logo_url' => appLogoUrlAbsolute(128),
|
'app_logo_url' => $this->resolveAppUrl(appLogoUrl(128)),
|
||||||
'imprint_url' => appUrl(Request::withLocale('imprint', $locale)),
|
'imprint_url' => $this->resolveAppUrl(Request::withLocale('imprint', $locale)),
|
||||||
'privacy_url' => appUrl(Request::withLocale('privacy', $locale)),
|
'privacy_url' => $this->resolveAppUrl(Request::withLocale('privacy', $locale)),
|
||||||
'code' => $code,
|
'code' => $code,
|
||||||
'expires_minutes' => self::EXPIRY_MINUTES,
|
'expires_minutes' => self::EXPIRY_MINUTES,
|
||||||
'verify_url' => $verifyUrl,
|
'verify_url' => $verifyUrl,
|
||||||
|
|||||||
@@ -145,7 +145,9 @@ final class HelpdeskContainerRegistrar implements ContainerRegistrar
|
|||||||
));
|
));
|
||||||
|
|
||||||
$container->set(HandoverNotificationService::class, static fn (AppContainer $c): HandoverNotificationService => new HandoverNotificationService(
|
$container->set(HandoverNotificationService::class, static fn (AppContainer $c): HandoverNotificationService => new HandoverNotificationService(
|
||||||
$c->get(MailService::class)
|
$c->get(MailService::class),
|
||||||
|
appTitle(),
|
||||||
|
appUrl(),
|
||||||
));
|
));
|
||||||
|
|
||||||
$container->set(HandoverService::class, static fn (AppContainer $c): HandoverService => new HandoverService(
|
$container->set(HandoverService::class, static fn (AppContainer $c): HandoverService => new HandoverService(
|
||||||
|
|||||||
@@ -8,9 +8,24 @@ class HandoverNotificationService
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly MailService $mailService,
|
private readonly MailService $mailService,
|
||||||
|
private readonly string $appTitle = '',
|
||||||
|
private readonly string $appUrl = '',
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveAppTitle(): string
|
||||||
|
{
|
||||||
|
return $this->appTitle !== '' ? $this->appTitle : appTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveAppUrl(string $path = ''): string
|
||||||
|
{
|
||||||
|
if ($this->appUrl !== '') {
|
||||||
|
return rtrim($this->appUrl, '/') . ($path !== '' ? '/' . ltrim($path, '/') : '');
|
||||||
|
}
|
||||||
|
return appUrl($path);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify the assignee that a handover was assigned to them.
|
* Notify the assignee that a handover was assigned to them.
|
||||||
*
|
*
|
||||||
@@ -29,7 +44,7 @@ class HandoverNotificationService
|
|||||||
$domainUrl = trim((string) ($handover['domain_url'] ?? ''));
|
$domainUrl = trim((string) ($handover['domain_url'] ?? ''));
|
||||||
$dueDate = trim((string) ($handover['due_date'] ?? ''));
|
$dueDate = trim((string) ($handover['due_date'] ?? ''));
|
||||||
$handoverId = (int) ($handover['id'] ?? 0);
|
$handoverId = (int) ($handover['id'] ?? 0);
|
||||||
$handoverUrl = appUrl('helpdesk/handovers/edit/' . $handoverId);
|
$handoverUrl = $this->resolveAppUrl('helpdesk/handovers/edit/' . $handoverId);
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'assignee_name' => $assigneeName,
|
'assignee_name' => $assigneeName,
|
||||||
@@ -38,10 +53,10 @@ class HandoverNotificationService
|
|||||||
'domain_url' => $domainUrl !== '' ? $domainUrl : '—',
|
'domain_url' => $domainUrl !== '' ? $domainUrl : '—',
|
||||||
'due_date' => $dueDate !== '' ? $dueDate : '—',
|
'due_date' => $dueDate !== '' ? $dueDate : '—',
|
||||||
'handover_url' => $handoverUrl,
|
'handover_url' => $handoverUrl,
|
||||||
'app_name' => appTitle(),
|
'app_name' => $this->resolveAppTitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$subject = sprintf('[%s] Ihnen wurde eine Übergabe zugewiesen', appTitle());
|
$subject = sprintf('[%s] Ihnen wurde eine Übergabe zugewiesen', $this->resolveAppTitle());
|
||||||
|
|
||||||
$this->mailService->sendTemplate('handover_assigned', $vars, $toEmail, $subject);
|
$this->mailService->sendTemplate('handover_assigned', $vars, $toEmail, $subject);
|
||||||
}
|
}
|
||||||
@@ -69,7 +84,7 @@ class HandoverNotificationService
|
|||||||
$debitorName = trim((string) ($handover['debitor_name'] ?? ''));
|
$debitorName = trim((string) ($handover['debitor_name'] ?? ''));
|
||||||
$domainUrl = trim((string) ($handover['domain_url'] ?? ''));
|
$domainUrl = trim((string) ($handover['domain_url'] ?? ''));
|
||||||
$handoverId = (int) ($handover['id'] ?? 0);
|
$handoverId = (int) ($handover['id'] ?? 0);
|
||||||
$handoverUrl = appUrl('helpdesk/handovers/edit/' . $handoverId);
|
$handoverUrl = $this->resolveAppUrl('helpdesk/handovers/edit/' . $handoverId);
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'manager_name' => $managerName,
|
'manager_name' => $managerName,
|
||||||
@@ -77,10 +92,10 @@ class HandoverNotificationService
|
|||||||
'debitor_name' => $debitorName !== '' ? $debitorName : '—',
|
'debitor_name' => $debitorName !== '' ? $debitorName : '—',
|
||||||
'domain_url' => $domainUrl !== '' ? $domainUrl : '—',
|
'domain_url' => $domainUrl !== '' ? $domainUrl : '—',
|
||||||
'handover_url' => $handoverUrl,
|
'handover_url' => $handoverUrl,
|
||||||
'app_name' => appTitle(),
|
'app_name' => $this->resolveAppTitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$subject = sprintf('[%s] Übergabe wurde zur Prüfung eingereicht', appTitle());
|
$subject = sprintf('[%s] Übergabe wurde zur Prüfung eingereicht', $this->resolveAppTitle());
|
||||||
|
|
||||||
$this->mailService->sendTemplate('handover_review_requested', $vars, $toEmail, $subject);
|
$this->mailService->sendTemplate('handover_review_requested', $vars, $toEmail, $subject);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ return gridFilterSchema([
|
|||||||
'search' => true,
|
'search' => true,
|
||||||
'query' => ['type' => 'string'],
|
'query' => ['type' => 'string'],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'key' => 'view',
|
||||||
|
'type' => 'hidden',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'key' => 'status',
|
'key' => 'status',
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ class HandoverNotificationServiceTest extends TestCase
|
|||||||
private function createNotificationService(?MailService $mailService = null): HandoverNotificationService
|
private function createNotificationService(?MailService $mailService = null): HandoverNotificationService
|
||||||
{
|
{
|
||||||
return new HandoverNotificationService(
|
return new HandoverNotificationService(
|
||||||
$mailService ?? $this->createMock(MailService::class)
|
$mailService ?? $this->createMock(MailService::class),
|
||||||
|
'TestApp',
|
||||||
|
'http://localhost',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -309,7 +309,9 @@ class EmailVerificationServiceTest extends TestCase
|
|||||||
$userReadRepository,
|
$userReadRepository,
|
||||||
$userWriteRepository,
|
$userWriteRepository,
|
||||||
$emailVerificationRepository,
|
$emailVerificationRepository,
|
||||||
$mailService
|
$mailService,
|
||||||
|
'TestApp',
|
||||||
|
'http://localhost',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,7 +356,9 @@ class PasswordResetServiceTest extends TestCase
|
|||||||
$passwordResetRepository,
|
$passwordResetRepository,
|
||||||
$userPasswordService,
|
$userPasswordService,
|
||||||
$rememberMeService,
|
$rememberMeService,
|
||||||
$mailService
|
$mailService,
|
||||||
|
'TestApp',
|
||||||
|
'http://localhost',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user