cloude review

This commit is contained in:
oskar 2026-01-09 19:01:46 +01:00
parent 195ca7d961
commit ca166eb661
10 changed files with 112 additions and 56 deletions

View file

@ -4,19 +4,22 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import com.mosenioring.common.security.LocalAuthFilter
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.Customizer.withDefaults
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter
import org.springframework.beans.factory.ObjectProvider
@Configuration
@EnableMethodSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity, localAuthFilterProvider: ObjectProvider<LocalAuthFilter>): SecurityFilterChain {
fun filterChain(
http: HttpSecurity,
@Autowired(required = false) localAuthFilter: LocalAuthFilter?
): SecurityFilterChain {
http
.csrf { it.disable() }
.authorizeHttpRequests {
@ -25,9 +28,8 @@ class SecurityConfig {
it.anyRequest().authenticated()
}
.oauth2ResourceServer { it.jwt(withDefaults()) }
val localAuthFilter = localAuthFilterProvider.ifAvailable
if (localAuthFilter != null) {
http.addFilterBefore(localAuthFilter, BearerTokenAuthenticationFilter::class.java)
localAuthFilter?.let {
http.addFilterBefore(it, BearerTokenAuthenticationFilter::class.java)
}
return http.build()
}

View file

@ -4,6 +4,7 @@ import com.mosenioring.common.Events
import com.mosenioring.common.outbox.OutboxRepository
import org.slf4j.LoggerFactory
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.beans.factory.annotation.Value
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@ -11,18 +12,20 @@ import org.springframework.transaction.annotation.Transactional
@Component
class OutboxPublisher(
private val repository: OutboxRepository,
private val rabbitTemplate: RabbitTemplate
private val rabbitTemplate: RabbitTemplate,
@Value("\${app.outbox.batch-size:50}") private val batchSize: Int
) {
private val logger = LoggerFactory.getLogger(javaClass)
@Scheduled(fixedDelayString = "\${app.outbox.publish-delay-ms:2000}")
@Transactional
fun publishPending() {
val pending = repository.findTop50ByStatusOrderByCreatedAtAsc("PENDING")
val pending = repository.findTop50ByStatusOrderByCreatedAtAsc("PENDING").take(batchSize)
if (pending.isEmpty()) {
return
}
pending.forEach { event ->
try {
val exchange = when (event.eventType) {
Events.MEDICATION_PLAN_CREATED -> Events.MEDICATION_EXCHANGE
Events.NOTIFICATION_REQUESTED -> Events.NOTIFICATION_EXCHANGE
@ -35,6 +38,10 @@ class OutboxPublisher(
}
rabbitTemplate.convertAndSend(exchange, event.eventType, event.payload)
event.status = "PUBLISHED"
} catch (e: Exception) {
logger.error("Failed to publish event ${event.id}", e)
event.status = "FAILED"
}
}
repository.saveAll(pending)
}

View file

@ -41,8 +41,16 @@ storage:
app:
outbox:
publish-delay-ms: 2000
batch-size: 50
tenant:
header: X-Tenant-Id
events:
medication-plan-created: MedicationPlanCreated
notification-requested: NotificationRequested
medication-exchange: medication.events
notification-exchange: notification.events
medication-queue: medication.plan.created
notification-queue: notification.requested
management:
endpoints:

View file

@ -0,0 +1,40 @@
-- Add foreign key constraints for data integrity
-- Users reference tenants
alter table users add constraint fk_users_tenant
foreign key (tenant_id) references tenants(id);
-- Patient relationships
alter table patient_caregivers add constraint fk_patient_caregivers_patient
foreign key (patient_id) references patients(id) on delete cascade;
alter table patient_caregivers add constraint fk_patient_caregivers_user
foreign key (user_id) references users(id) on delete cascade;
alter table patient_doctors add constraint fk_patient_doctors_patient
foreign key (patient_id) references patients(id) on delete cascade;
alter table patient_doctors add constraint fk_patient_doctors_user
foreign key (user_id) references users(id) on delete cascade;
-- Clinical data
alter table medication_plans add constraint fk_medication_plans_patient
foreign key (patient_id) references patients(id) on delete cascade;
alter table tests add constraint fk_tests_patient
foreign key (patient_id) references patients(id) on delete cascade;
-- Messages
alter table messages add constraint fk_messages_patient
foreign key (patient_id) references patients(id) on delete cascade;
alter table messages add constraint fk_messages_sender
foreign key (sender_id) references users(id);
-- Files
alter table files add constraint fk_files_patient
foreign key (patient_id) references patients(id) on delete set null;
-- Audit events
alter table audit_events add constraint fk_audit_events_patient
foreign key (patient_id) references patients(id) on delete set null;

View file

@ -1,5 +1,7 @@
package com.mosenioring.common
import com.mosenioring.common.security.SecurityUtils
import com.mosenioring.common.tenant.TenantContext
import jakarta.persistence.Column
import jakarta.persistence.MappedSuperclass
import jakarta.persistence.PrePersist
@ -29,10 +31,22 @@ abstract class BaseEntity {
val now = Instant.now()
createdAt = now
updatedAt = now
if (!::tenantId.isInitialized) {
tenantId = TenantContext.getTenantId() ?: "unknown"
}
if (createdBy == null) {
createdBy = SecurityUtils.currentUserId()
}
if (updatedBy == null) {
updatedBy = SecurityUtils.currentUserId()
}
}
@PreUpdate
fun onUpdate() {
updatedAt = Instant.now()
if (updatedBy == null) {
updatedBy = SecurityUtils.currentUserId()
}
}
}

View file

@ -9,6 +9,4 @@ plugins {
dependencies {
api(project(":common"))
implementation(project(":modules:audit"))
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
}

View file

@ -9,6 +9,4 @@ plugins {
dependencies {
api(project(":common"))
implementation(project(":modules:audit"))
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
}

View file

@ -4,6 +4,7 @@ import org.springframework.stereotype.Service
@Service
class FhirGateway {
// TODO: Implement actual FHIR integration
fun submitObservation(payload: String): String {
return "stub-${payload.hashCode()}"
}

View file

@ -28,9 +28,9 @@ class FileService(
@Value("\${storage.s3.secret-key}") private val secretKey: String,
@Value("\${storage.s3.bucket}") private val bucket: String
) {
private fun presigner(): S3Presigner {
private val presigner: S3Presigner by lazy {
val creds = AwsBasicCredentials.create(accessKey, secretKey)
return S3Presigner.builder()
S3Presigner.builder()
.endpointOverride(URI.create(endpoint))
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(creds))
@ -48,8 +48,6 @@ class FileService(
metadata.updatedBy = SecurityUtils.currentUserId()
repository.save(metadata)
val presigner = presigner()
try {
val request = PutObjectRequest.builder()
.bucket(bucket)
.key(storageKey)
@ -61,16 +59,11 @@ class FileService(
.build()
val presigned = presigner.presignPutObject(presignRequest)
return PresignResponse(fileId, presigned.url().toString())
} finally {
presigner.close()
}
}
fun presignDownload(fileId: String): PresignResponse {
val tenantId = TenantContext.getTenantId() ?: throw IllegalStateException("Missing tenant")
val metadata = repository.findByIdAndTenantId(fileId, tenantId) ?: throw IllegalArgumentException("File not found")
val presigner = presigner()
try {
val request = GetObjectRequest.builder()
.bucket(bucket)
.key(metadata.storageKey)
@ -81,9 +74,6 @@ class FileService(
.build()
val presigned = presigner.presignGetObject(presignRequest)
return PresignResponse(metadata.id, presigned.url().toString())
} finally {
presigner.close()
}
}
}

View file

@ -9,6 +9,4 @@ plugins {
dependencies {
api(project(":common"))
implementation(project(":modules:audit"))
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
}