plan(s0): chunk 2 - backend extraction step-by-step (W1, W2, W4)
+236
@@ -159,3 +159,239 @@ See § 8 for step-by-step.
|
||||
---
|
||||
|
||||
*Plan continues in the next section — backend extraction step-by-step.*
|
||||
|
||||
---
|
||||
|
||||
## 4. Backend extraction — step-by-step
|
||||
|
||||
### 4.1 W1 — Repo scaffolding
|
||||
|
||||
**Goal:** Create the directory layout + skeleton `pom.xml` files + npm workspace structure.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **W1-1** In the `plate-auth` repo root, create:
|
||||
```
|
||||
plate-auth/
|
||||
├── pom.xml # parent POM, packaging=pom
|
||||
├── plate-auth-starter/
|
||||
│ ├── pom.xml # the published artifact
|
||||
│ └── src/{main,test}/{java,resources}/
|
||||
├── packages/
|
||||
│ └── auth/
|
||||
│ ├── package.json # @platesoft/auth
|
||||
│ └── src/
|
||||
├── package.json # workspace root
|
||||
├── pnpm-workspace.yaml # if pnpm; npm/yarn equivalent works
|
||||
├── .gitea/workflows/ # Gitea Actions
|
||||
├── CHANGELOG.md
|
||||
└── README.md
|
||||
```
|
||||
2. **W1-2** Parent `pom.xml`: `<groupId>de.platesoft</groupId>`, `<artifactId>plate-auth-parent</artifactId>`,
|
||||
`<version>${revision}</version>` (CI-injected), `<packaging>pom</packaging>`,
|
||||
`<modules><module>plate-auth-starter</module></modules>`. Inherit from `spring-boot-starter-parent:4.1.0`.
|
||||
3. **W1-3** `plate-auth-starter/pom.xml`: `<artifactId>plate-auth-starter</artifactId>`,
|
||||
deps copied from InspectFlow `backend/pom.xml` minus app-specific (no ONNX, no openpdf, no pdfbox):
|
||||
```
|
||||
web, data-jpa, security, validation, actuator, mail,
|
||||
hibernate-envers, postgresql, flyway,
|
||||
jjwt-api/impl/jackson,
|
||||
mapstruct, lombok, logstash-logback,
|
||||
h2 (test scope), testcontainers (test scope)
|
||||
```
|
||||
4. **W1-4** Root `package.json` + `pnpm-workspace.yaml` configured to pick up `packages/*`.
|
||||
5. **W1-5** `packages/auth/package.json`: `"name": "@platesoft/auth"`, `"version": "0.1.0"`,
|
||||
peerDeps on `next@>=15`, `next-auth@^5.0.0-beta`, `react@>=19`.
|
||||
6. **W1-6** Add `.gitignore`, `.editorconfig`, basic `README.md` linking to the wiki.
|
||||
7. **W1-7** Commit + push to `main`. CI must pass on empty modules (just compiles).
|
||||
|
||||
**Done when:** `mvn -B verify` and `pnpm -r build` both succeed on a fresh clone.
|
||||
|
||||
### 4.2 W2-A — Backend: copy + rename classes
|
||||
|
||||
**Goal:** Mechanical move from `de.platesoft.inspectflow.*` → `de.platesoft.auth.*` for every auth class.
|
||||
|
||||
**Steps (per source class — repeat for the inventory in Sprint-0-Assessment § 1.1):**
|
||||
|
||||
1. **W2-1** Copy class file from `inspectflow/backend/src/main/java/de/platesoft/inspectflow/<pkg>/<X>.java`
|
||||
to `plate-auth/plate-auth-starter/src/main/java/de/platesoft/auth/<pkg>/<X>.java`.
|
||||
2. **W2-2** Update `package` declaration: `package de.platesoft.auth.<pkg>;`.
|
||||
3. **W2-3** Update **internal imports** (i.e. imports of other classes we are also moving) to the new
|
||||
package. Use search-replace, but verify each file compiles independently after.
|
||||
4. **W2-4** **Leave behind**: any reference to `Company`, `OnboardingService`, `TenantAutoMapService` —
|
||||
replace with an SPI call (see W4 below). DO NOT move these classes.
|
||||
|
||||
**Class-by-class checklist:**
|
||||
|
||||
- [ ] `filter/JwtAuthenticationFilter.java` — direct move, no rewrites
|
||||
- [ ] `filter/OrgContextResolver.java` — direct move
|
||||
- [ ] `config/SecurityConfig.java` — direct move, **but**: replace InspectFlow-specific permit-list
|
||||
(`/api/companies/*/public-info`) with config-driven list from `PlateAuthProperties.cors.additionalPermitPaths`
|
||||
- [ ] `service/JwtService.java` — direct move; rename `@Value("${jwt.secret}")` → `@Value("${plate.auth.jwt.secret}")`
|
||||
- [ ] `service/OAuthService.java` — move + **swap** `tenantAutoMapService.maybeAutoMap(...)` call for
|
||||
`onboardingHook.onFirstSignIn(...)` (SPI)
|
||||
- [ ] `service/ExchangeService.java` — direct move
|
||||
- [ ] `service/MembershipService.java` — move + **swap** `companyRepository.findById(...)` for
|
||||
`orgValidator.exists(orgType, orgId)` / `orgDisplayNameResolver.displayName(...)` calls
|
||||
- [ ] `service/InvitationService.java` — direct move (Sprint 14.3 already abstracted the mailer)
|
||||
- [ ] `service/AccessRequestService.java` — direct move
|
||||
- [ ] `service/LoginEventService.java` — direct move
|
||||
- [ ] `service/AuthService.java` — direct move (password login/register)
|
||||
- [ ] `controller/AuthController.java` — direct move
|
||||
- [ ] `controller/OAuthController.java` — direct move
|
||||
- [ ] `controller/InvitationController.java` — direct move
|
||||
- [ ] `controller/AccessRequestController.java` — direct move
|
||||
- [ ] `controller/AdminAuditController.java` — direct move
|
||||
- [ ] `entity/User.java` — direct move
|
||||
- [ ] `entity/UserIdentity.java` — direct move
|
||||
- [ ] `entity/Membership.java` — direct move
|
||||
- [ ] `entity/Invitation.java` — direct move
|
||||
- [ ] `entity/AccessRequest.java` — direct move
|
||||
- [ ] `entity/LoginEvent.java` — direct move
|
||||
- [ ] `entity/RevInfo.java` (Envers revinfo with actor) — direct move
|
||||
- [ ] `repository/*` — all auth-related repositories — direct move
|
||||
- [ ] `dto/request/*` and `dto/response/*` — direct move
|
||||
- [ ] `enums/*` — direct move (Role, OrgType, MembershipRole, MembershipStatus, InvitationStatus,
|
||||
AccessRequestStatus, LoginProvider)
|
||||
|
||||
**Done when:** `plate-auth-starter` compiles in isolation. No references to `de.platesoft.inspectflow.*`
|
||||
remain in moved classes.
|
||||
|
||||
### 4.3 W2-B — Configuration namespace
|
||||
|
||||
**Goal:** Consolidate all `@Value("${...}")` injections into a single
|
||||
`@ConfigurationProperties("plate.auth")` class.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **W2-8** Create `de.platesoft.auth.PlateAuthProperties`:
|
||||
```java
|
||||
@ConfigurationProperties(prefix = "plate.auth")
|
||||
@Data
|
||||
public class PlateAuthProperties {
|
||||
private Jwt jwt = new Jwt();
|
||||
private Exchange exchange = new Exchange();
|
||||
private Registration registration = new Registration();
|
||||
private Cors cors = new Cors();
|
||||
private Providers providers = new Providers();
|
||||
|
||||
@Data public static class Jwt {
|
||||
private String secret;
|
||||
private Duration accessExpiration = Duration.ofMinutes(15);
|
||||
private Duration refreshExpiration = Duration.ofDays(30);
|
||||
private String issuer = "plate-auth";
|
||||
}
|
||||
@Data public static class Exchange {
|
||||
private String secret;
|
||||
private Duration maxAge = Duration.ofSeconds(60);
|
||||
private Duration nonceTtl = Duration.ofMinutes(5);
|
||||
}
|
||||
@Data public static class Registration {
|
||||
private boolean enabled = false;
|
||||
}
|
||||
@Data public static class Cors {
|
||||
private List<String> allowedOrigins = new ArrayList<>();
|
||||
private List<String> additionalPermitPaths = new ArrayList<>();
|
||||
}
|
||||
@Data public static class Providers {
|
||||
private ProviderToggle google = new ProviderToggle(true);
|
||||
private ProviderToggle microsoft = new ProviderToggle(false);
|
||||
private ProviderToggle emailMagicLink = new ProviderToggle(false);
|
||||
}
|
||||
@Data @AllArgsConstructor @NoArgsConstructor public static class ProviderToggle {
|
||||
private boolean enabled;
|
||||
}
|
||||
}
|
||||
```
|
||||
2. **W2-9** Inject `PlateAuthProperties` (constructor injection) into every service that previously read
|
||||
`@Value("${jwt....}")` etc. Replace `@Value` annotations.
|
||||
3. **W2-10** Add `META-INF/spring-configuration-metadata.json` (or rely on `spring-boot-configuration-processor`
|
||||
annotation processor — preferred — add it to the build).
|
||||
4. **W2-11** Bean-validate critical fields:
|
||||
```java
|
||||
@NotBlank @Size(min=32) private String secret; // both jwt.secret and exchange.secret
|
||||
```
|
||||
|
||||
**Done when:** No `@Value("${jwt....}")` or `@Value("${nextauth....}")` strings remain. All config flows
|
||||
through `PlateAuthProperties`. App fails fast at startup if `secret` is missing or <32 chars.
|
||||
|
||||
### 4.4 W2-C — Auto-configuration
|
||||
|
||||
**Goal:** Make the starter "just work" when added as a dependency.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **W2-12** Create `de.platesoft.auth.PlateAuthAutoConfiguration`:
|
||||
```java
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(PlateAuthProperties.class)
|
||||
@ComponentScan(basePackages = "de.platesoft.auth")
|
||||
@EntityScan(basePackages = "de.platesoft.auth.entity")
|
||||
@EnableJpaRepositories(basePackages = "de.platesoft.auth.repository")
|
||||
@ConditionalOnProperty(prefix = "plate.auth", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class PlateAuthAutoConfiguration {
|
||||
// Default SPI beans, ConditionalOnMissingBean — see W4
|
||||
}
|
||||
```
|
||||
2. **W2-13** Register the auto-config:
|
||||
```
|
||||
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
|
||||
```
|
||||
Contents: single line `de.platesoft.auth.PlateAuthAutoConfiguration`.
|
||||
3. **W2-14** Verify that adding the starter to a minimal Spring Boot 4 app, setting `plate.auth.jwt.secret`
|
||||
+ `plate.auth.exchange.secret` env vars, and `spring.datasource.url` to a Postgres, makes the app boot
|
||||
and serve `/api/auth/config`.
|
||||
|
||||
**Done when:** Smoke test app (in `plate-auth-tests/`) boots and responds to `/api/auth/config`.
|
||||
|
||||
### 4.5 W4 — SPI design + default implementations
|
||||
|
||||
**Goal:** Five SPI interfaces with sensible defaults so consumers can override selectively.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **W4-1** Create the 5 SPI interfaces under `de.platesoft.auth.spi`:
|
||||
```java
|
||||
public interface OrgValidator {
|
||||
boolean exists(OrgType type, UUID orgId);
|
||||
}
|
||||
public interface OrgDisplayNameResolver {
|
||||
String displayName(OrgType type, UUID orgId);
|
||||
}
|
||||
public interface InvitationMailer {
|
||||
void sendInvitation(Invitation invitation, String acceptUrl);
|
||||
}
|
||||
public interface AccessRequestMailer {
|
||||
void notifyAdmins(AccessRequest request);
|
||||
void notifyRequester(AccessRequest request); // on decision
|
||||
}
|
||||
public interface OnboardingHook {
|
||||
void onFirstSignIn(User user, LoginProvider provider);
|
||||
default void onSubsequentSignIn(User user, LoginProvider provider) { /* no-op */ }
|
||||
}
|
||||
```
|
||||
2. **W4-2** Default implementations (annotated `@ConditionalOnMissingBean`, registered in
|
||||
`PlateAuthAutoConfiguration`):
|
||||
- `DefaultOrgValidator` — **does NOT exist as a default**; auto-config requires the consumer to
|
||||
provide one if T2 endpoints are used. If absent, T2 endpoints fail-fast on first invocation with a
|
||||
clear error: "Provide a `de.platesoft.auth.spi.OrgValidator` bean to use plate-auth multi-tenancy."
|
||||
- `DefaultOrgDisplayNameResolver` — returns `type + ":" + orgId.toString()`
|
||||
- `LoggingInvitationMailer` — logs the accept URL at INFO level
|
||||
- `LoggingAccessRequestMailer` — logs notifications at INFO level
|
||||
- `NoOpOnboardingHook` — no-op
|
||||
3. **W4-3** Wire each service to its SPI dep via constructor injection:
|
||||
- `MembershipService` ← `OrgValidator`, `OrgDisplayNameResolver`
|
||||
- `InvitationService` ← `InvitationMailer`, `OrgDisplayNameResolver`
|
||||
- `AccessRequestService` ← `AccessRequestMailer`, `OrgDisplayNameResolver`
|
||||
- `OAuthService` ← `OnboardingHook`
|
||||
4. **W4-4** Document each SPI with Javadoc including:
|
||||
- When it is called
|
||||
- What happens if the consumer doesn't provide one (which default kicks in)
|
||||
- Migration: if a previous version's signature changed, link to CHANGELOG
|
||||
|
||||
**Done when:** A consumer can register a single `OrgValidator` bean and have T2 fully functional. Default
|
||||
mailers log instead of crashing.
|
||||
|
||||
---
|
||||
|
||||
*Plan continues — frontend extraction, Flyway, publishing.*
|
||||
|
||||
Reference in New Issue
Block a user