- Landing page with hero, feature grid, trust signals - Split-layout login redesign (admin + portal) - Pricing page with storage tiers (5GB/50GB/unlimited) - StorageQuotaService backend (V36 migration, 402 on exceeded) - Frontend storage integration + 402 error handling - StorageController uses TenantContext for tenant isolation - onTierChange() hook for subscription tier updates
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package de.cannamanage.api.controller;
|
||||
|
||||
import de.cannamanage.domain.entity.TenantContext;
|
||||
import de.cannamanage.service.StorageQuotaService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* REST controller for storage quota information.
|
||||
* Provides endpoint to check current storage usage for the caller's club.
|
||||
* Club ID is extracted from the JWT/tenant context — not from request params.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/storage")
|
||||
@PreAuthorize("hasAnyRole('ADMIN', 'STAFF')")
|
||||
public class StorageController {
|
||||
|
||||
private final StorageQuotaService storageQuotaService;
|
||||
|
||||
public StorageController(StorageQuotaService storageQuotaService) {
|
||||
this.storageQuotaService = storageQuotaService;
|
||||
}
|
||||
|
||||
@GetMapping("/usage")
|
||||
public ResponseEntity<StorageQuotaService.StorageUsageDTO> getUsage() {
|
||||
UUID clubId = TenantContext.getCurrentTenant();
|
||||
return ResponseEntity.ok(storageQuotaService.getUsage(clubId));
|
||||
}
|
||||
}
|
||||
+15
@@ -5,6 +5,7 @@ import de.cannamanage.service.exception.BatchNotFoundException;
|
||||
import de.cannamanage.service.exception.MemberNotFoundException;
|
||||
import de.cannamanage.service.exception.PreventionOfficerLimitExceededException;
|
||||
import de.cannamanage.service.exception.QuotaExceededException;
|
||||
import de.cannamanage.service.exception.StorageQuotaExceededException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
@@ -121,6 +122,20 @@ public class GlobalExceptionHandler {
|
||||
return problem;
|
||||
}
|
||||
|
||||
@ExceptionHandler(StorageQuotaExceededException.class)
|
||||
public ProblemDetail handleStorageQuotaExceeded(StorageQuotaExceededException ex) {
|
||||
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
|
||||
HttpStatus.PAYMENT_REQUIRED, ex.getMessage());
|
||||
problem.setTitle("Storage Quota Exceeded");
|
||||
problem.setType(URI.create("urn:cannamanage:error:STORAGE_QUOTA_EXCEEDED"));
|
||||
problem.setProperty("code", "STORAGE_QUOTA_EXCEEDED");
|
||||
problem.setProperty("currentUsage", ex.getCurrentUsage());
|
||||
problem.setProperty("limit", ex.getLimit());
|
||||
problem.setProperty("requestedBytes", ex.getRequestedBytes());
|
||||
problem.setProperty("timestamp", Instant.now().toString());
|
||||
return problem;
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResponseStatusException.class)
|
||||
public ProblemDetail handleResponseStatus(ResponseStatusException ex) {
|
||||
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- V36: Add storage quota tracking to clubs
|
||||
ALTER TABLE clubs ADD COLUMN IF NOT EXISTS storage_used_bytes BIGINT DEFAULT 0;
|
||||
ALTER TABLE clubs ADD COLUMN IF NOT EXISTS storage_limit_bytes BIGINT DEFAULT 5368709120;
|
||||
-- Default: 5 GB (5 * 1024^3) = Starter tier
|
||||
|
||||
-- Backfill existing clubs with actual usage
|
||||
UPDATE clubs c SET storage_used_bytes = COALESCE(
|
||||
(SELECT SUM(d.file_size) FROM documents d WHERE d.club_id = c.id), 0
|
||||
);
|
||||
Reference in New Issue
Block a user