Compare commits

..

4 commits

Author SHA1 Message Date
oskar df17dc3f2e Remove redundant workflow filters for .forgejo/workflows/**.
All checks were successful
ci / changes (push) Successful in 3s
ci / backend (push) Has been skipped
ci / flutter (push) Has been skipped
2026-01-16 22:11:39 +01:00
oskar 6d3b1cbffd Set fetch-depth: 0 in Forgejo Actions CI workflows to fetch full history
Some checks failed
ci / changes (push) Successful in 4s
ci / backend (push) Successful in 1m46s
ci / flutter (push) Has been cancelled
2026-01-16 22:09:28 +01:00
oskar dc4bc979af Ensure tenant ID is properly set for patients, users, invitations, and patient-subject links in InvitationService; add corresponding unit tests. 2026-01-16 21:46:00 +01:00
oskar c65d1031ed Update Forgejo CI workflows to use full GitHub action URLs
All checks were successful
ci / changes (push) Successful in 21s
ci / backend (push) Successful in 3m3s
ci / flutter (push) Successful in 1m10s
2026-01-16 21:44:46 +01:00
3 changed files with 44 additions and 47 deletions

View file

@ -13,51 +13,21 @@ jobs:
changes: changes:
runs-on: docker runs-on: docker
steps: steps:
- uses: actions/checkout@v4 - uses: https://github.com/actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 0
- id: filter - id: filter
name: Detect changes uses: https://github.com/dorny/paths-filter@v3
run: | with:
set -euo pipefail list-files: shell
git fetch --quiet --depth=1 origin "${GITHUB_BASE_REF:-}" || true filters: |
backend:
files="" - 'back001/**'
if [ "${GITHUB_EVENT_NAME:-}" = "pull_request" ] && [ -n "${GITHUB_BASE_REF:-}" ]; then - 'ci/**'
base_ref="origin/${GITHUB_BASE_REF}" frontend:
if git rev-parse --verify "$base_ref" >/dev/null 2>&1; then - 'front001/**'
files="$(git diff --name-only "$base_ref"...HEAD)" || true - 'ci/**'
else
files=""
fi
fi
if [ -z "$files" ]; then
if [ -n "${GITHUB_SHA:-}" ]; then
files="$(git show --name-only --pretty= "${GITHUB_SHA}")"
else
files="$(git show --name-only --pretty= HEAD)"
fi
fi
backend=false
frontend=false
while IFS= read -r path; do
case "$path" in
back001/*|.forgejo/workflows/*|ci/*) backend=true ;;
esac
case "$path" in
front001/*|.forgejo/workflows/*|ci/*) frontend=true ;;
esac
done <<EOF
$files
EOF
{
echo "backend=$backend"
echo "frontend=$frontend"
} >> "$GITHUB_OUTPUT"
outputs: outputs:
backend: ${{ steps.filter.outputs.backend }} backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }} frontend: ${{ steps.filter.outputs.frontend }}
@ -69,9 +39,9 @@ jobs:
container: container:
image: forgejo.okit.pl/oskar/ci-gradle-node:8.7-jdk17 image: forgejo.okit.pl/oskar/ci-gradle-node:8.7-jdk17
steps: steps:
- uses: actions/checkout@v4 - uses: https://github.com/actions/checkout@v4
- uses: actions/cache@v4 - uses: https://github.com/actions/cache@v4
with: with:
path: | path: |
/home/gradle/.gradle/caches /home/gradle/.gradle/caches
@ -91,9 +61,9 @@ jobs:
container: container:
image: forgejo.okit.pl/oskar/ci-flutter-node:stable image: forgejo.okit.pl/oskar/ci-flutter-node:stable
steps: steps:
- uses: actions/checkout@v4 - uses: https://github.com/actions/checkout@v4
- uses: actions/cache@v4 - uses: https://github.com/actions/cache@v4
with: with:
path: | path: |
/root/.pub-cache /root/.pub-cache

View file

@ -40,13 +40,15 @@ class InvitationService(
@Transactional @Transactional
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun createPatientInvite(email: String, createdByAdmin: String?): InviteCreationResult { fun createPatientInvite(email: String, createdByAdmin: String?): InviteCreationResult {
requireNotNull(TenantContext.getTenantId()) { "Missing tenant" } val tenantId = TenantContext.getTenantId() ?: throw IllegalArgumentException("Missing tenant")
val patient = Patient(UUID.randomUUID().toString(), generatePatientPlaceholderName()) val patient = Patient(UUID.randomUUID().toString(), generatePatientPlaceholderName())
patient.tenantId = tenantId
val savedPatient = patientRepository.save(patient) val savedPatient = patientRepository.save(patient)
keycloakProvisioningService.provisionUser(email, Invitation.ROLE_PATIENT)?.let { userId -> keycloakProvisioningService.provisionUser(email, Invitation.ROLE_PATIENT)?.let { userId ->
val user = User(userId, email, Invitation.ROLE_PATIENT, "INVITED") val user = User(userId, email, Invitation.ROLE_PATIENT, "INVITED")
user.tenantId = tenantId
userRepository.save(user) userRepository.save(user)
} }
keycloakProvisioningService.sendSetPasswordEmail(email) keycloakProvisioningService.sendSetPasswordEmail(email)
@ -64,6 +66,7 @@ class InvitationService(
acceptedBy = null, acceptedBy = null,
createdByAdmin = createdByAdmin createdByAdmin = createdByAdmin
) )
invitation.tenantId = tenantId
invitationRepository.save(invitation) invitationRepository.save(invitation)
return InviteCreationResult(token, invitation.expiresAt) return InviteCreationResult(token, invitation.expiresAt)
} }
@ -103,6 +106,7 @@ class InvitationService(
throw IllegalArgumentException("Invitation email mismatch") throw IllegalArgumentException("Invitation email mismatch")
} }
val newUser = User(authenticatedUserId, authenticatedEmail, invitation.role, "ACTIVE") val newUser = User(authenticatedUserId, authenticatedEmail, invitation.role, "ACTIVE")
newUser.tenantId = TenantContext.getTenantId() ?: throw IllegalStateException("Missing tenant")
userRepository.save(newUser) userRepository.save(newUser)
} }
@ -140,6 +144,7 @@ class InvitationService(
val tenantId = TenantContext.getTenantId() ?: throw IllegalStateException("Missing tenant") val tenantId = TenantContext.getTenantId() ?: throw IllegalStateException("Missing tenant")
if (!subjectRepository.existsByTenantIdAndPatientIdAndUserId(tenantId, patientId, userId)) { if (!subjectRepository.existsByTenantIdAndPatientIdAndUserId(tenantId, patientId, userId)) {
val link = PatientSubject(UUID.randomUUID().toString(), patientId, userId) val link = PatientSubject(UUID.randomUUID().toString(), patientId, userId)
link.tenantId = tenantId
subjectRepository.save(link) subjectRepository.save(link)
} }
} }

View file

@ -79,6 +79,28 @@ class InvitationServiceTest {
assertEquals(Invitation.STATUS_ACCEPTED, invitation.status) assertEquals(Invitation.STATUS_ACCEPTED, invitation.status)
assertNotNull(invitation.acceptedAt) assertNotNull(invitation.acceptedAt)
assertEquals(user, invitation.acceptedBy) assertEquals(user, invitation.acceptedBy)
org.mockito.kotlin.verify(subjectRepository).save(org.mockito.kotlin.check {
assertEquals("t1", it.tenantId)
})
}
@Test
fun `creates patient invite with tenantId`() {
val email = "new@example.com"
whenever(patientRepository.save(any<Patient>())).thenAnswer { it.arguments[0] as Patient }
whenever(userRepository.save(any<User>())).thenAnswer { it.arguments[0] as User }
whenever(invitationRepository.save(any<Invitation>())).thenAnswer { it.arguments[0] as Invitation }
val result = service.createPatientInvite(email, "admin-1")
assertNotNull(result.token)
org.mockito.kotlin.verify(patientRepository).save(org.mockito.kotlin.check<Patient> {
assertEquals("t1", it.tenantId)
})
org.mockito.kotlin.verify(invitationRepository).save(org.mockito.kotlin.check<Invitation> {
assertEquals("t1", it.tenantId)
assertEquals(email, it.email)
})
} }
@Test @Test