Skip to content

Commit 8494d58

Browse files
authored
Merge pull request #5 from poyrazK/feat/identity-persistence-foundation
feat(identity): add persistence foundation with Flyway and JPA
2 parents 3ff1053 + 5af2e0a commit 8494d58

19 files changed

Lines changed: 745 additions & 3 deletions

docs/modular-implementation-roadmap.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ This roadmap breaks implementation into small, reviewable slices with one primar
2222

2323
### PR-003: identity-service MVP
2424
- Phase A (done): auth controller stubs and request/response contracts.
25-
- Phase B (next): JWT + refresh rotation plumbing and storage interfaces.
26-
- Phase C (next): login/social/refresh/logout service logic + tests.
25+
- Phase B (in progress): persistence foundation added (Flyway migrations, JPA entities, repositories, repository tests).
26+
- Phase C (next): JWT + refresh rotation plumbing and storage interfaces.
27+
- Phase D (next): login/social/refresh/logout service logic + tests.
2728

2829
### PR-004: content-service MVP
2930
- Draft, publish, unpublish endpoints.
@@ -73,4 +74,4 @@ This roadmap breaks implementation into small, reviewable slices with one primar
7374

7475
- PR-001: completed
7576
- PR-002: completed
76-
- PR-003: in progress (Phase A completed)
77+
- PR-003: in progress (Phase A and persistence foundation completed)

docs/mvp-backend-implementation-plan.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,23 @@ Implementation starts from these contract docs:
448448

449449
- REST API contract: `docs/contracts/rest-api-v1.md`
450450
- Kafka event contract: `docs/contracts/kafka-event-catalog.md`
451+
452+
---
453+
454+
## 19) Implementation Status Snapshot
455+
456+
Current completed slices:
457+
458+
- CI baseline + lint gates are in place (`docs-and-contracts`, `java-quality`, `java-tests-identity`)
459+
- Java multi-module service skeleton is complete
460+
- Identity API stubs and response/error contracts are implemented
461+
- Identity test baseline is implemented (controller contract tests, exception handler tests, DTO validation tests)
462+
- Identity persistence foundation is implemented:
463+
- Flyway migrations for `users`, `user_credentials`, `oauth_accounts`, `sessions`, `refresh_tokens`
464+
- JPA entities and Spring Data repositories
465+
- DataJpa repository tests for uniqueness constraints and lookup queries
466+
467+
Next active slice:
468+
469+
- Identity token/session logic (JWT issue/verify, refresh rotation, reuse detection, and max-5 session cap behavior)
470+
- Kafka event contract: `docs/contracts/kafka-event-catalog.md`

services/java/identity-service/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,29 @@
2727
<groupId>org.springframework.boot</groupId>
2828
<artifactId>spring-boot-starter-actuator</artifactId>
2929
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-data-jpa</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.flywaydb</groupId>
36+
<artifactId>flyway-core</artifactId>
37+
</dependency>
38+
<dependency>
39+
<groupId>org.postgresql</groupId>
40+
<artifactId>postgresql</artifactId>
41+
<scope>runtime</scope>
42+
</dependency>
3043
<dependency>
3144
<groupId>org.springframework.boot</groupId>
3245
<artifactId>spring-boot-starter-test</artifactId>
3346
<scope>test</scope>
3447
</dependency>
48+
<dependency>
49+
<groupId>com.h2database</groupId>
50+
<artifactId>h2</artifactId>
51+
<scope>test</scope>
52+
</dependency>
3553
</dependencies>
3654

3755
<build>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.cloudmedia.identity.persistence.entity;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.EnumType;
6+
import jakarta.persistence.Enumerated;
7+
import jakarta.persistence.FetchType;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.JoinColumn;
10+
import jakarta.persistence.ManyToOne;
11+
import jakarta.persistence.Table;
12+
import java.time.LocalDateTime;
13+
14+
@Entity
15+
@Table(name = "oauth_accounts")
16+
public class OAuthAccountEntity {
17+
18+
@Id
19+
@Column(nullable = false, length = 36)
20+
private String id;
21+
22+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
23+
@JoinColumn(name = "user_id", nullable = false)
24+
private UserEntity user;
25+
26+
@Enumerated(EnumType.STRING)
27+
@Column(nullable = false, length = 32)
28+
private OAuthProvider provider;
29+
30+
@Column(name = "provider_subject", nullable = false, length = 255)
31+
private String providerSubject;
32+
33+
@Column(name = "linked_at", nullable = false)
34+
private LocalDateTime linkedAt;
35+
36+
public String getId() {
37+
return id;
38+
}
39+
40+
public void setId(String id) {
41+
this.id = id;
42+
}
43+
44+
public UserEntity getUser() {
45+
return user;
46+
}
47+
48+
public void setUser(UserEntity user) {
49+
this.user = user;
50+
}
51+
52+
public OAuthProvider getProvider() {
53+
return provider;
54+
}
55+
56+
public void setProvider(OAuthProvider provider) {
57+
this.provider = provider;
58+
}
59+
60+
public String getProviderSubject() {
61+
return providerSubject;
62+
}
63+
64+
public void setProviderSubject(String providerSubject) {
65+
this.providerSubject = providerSubject;
66+
}
67+
68+
public LocalDateTime getLinkedAt() {
69+
return linkedAt;
70+
}
71+
72+
public void setLinkedAt(LocalDateTime linkedAt) {
73+
this.linkedAt = linkedAt;
74+
}
75+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.cloudmedia.identity.persistence.entity;
2+
3+
public enum OAuthProvider {
4+
GOOGLE
5+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.cloudmedia.identity.persistence.entity;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.FetchType;
6+
import jakarta.persistence.Id;
7+
import jakarta.persistence.JoinColumn;
8+
import jakarta.persistence.ManyToOne;
9+
import jakarta.persistence.Table;
10+
import java.time.LocalDateTime;
11+
12+
@Entity
13+
@Table(name = "refresh_tokens")
14+
public class RefreshTokenEntity {
15+
16+
@Id
17+
@Column(nullable = false, length = 36)
18+
private String id;
19+
20+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
21+
@JoinColumn(name = "session_id", nullable = false)
22+
private SessionEntity session;
23+
24+
@Column(name = "token_hash", nullable = false, length = 128)
25+
private String tokenHash;
26+
27+
@ManyToOne(fetch = FetchType.LAZY)
28+
@JoinColumn(name = "parent_token_id")
29+
private RefreshTokenEntity parentToken;
30+
31+
@Column(name = "issued_at", nullable = false)
32+
private LocalDateTime issuedAt;
33+
34+
@Column(name = "expires_at", nullable = false)
35+
private LocalDateTime expiresAt;
36+
37+
@Column(name = "revoked_at")
38+
private LocalDateTime revokedAt;
39+
40+
@ManyToOne(fetch = FetchType.LAZY)
41+
@JoinColumn(name = "replaced_by")
42+
private RefreshTokenEntity replacedBy;
43+
44+
public String getId() {
45+
return id;
46+
}
47+
48+
public void setId(String id) {
49+
this.id = id;
50+
}
51+
52+
public SessionEntity getSession() {
53+
return session;
54+
}
55+
56+
public void setSession(SessionEntity session) {
57+
this.session = session;
58+
}
59+
60+
public String getTokenHash() {
61+
return tokenHash;
62+
}
63+
64+
public void setTokenHash(String tokenHash) {
65+
this.tokenHash = tokenHash;
66+
}
67+
68+
public RefreshTokenEntity getParentToken() {
69+
return parentToken;
70+
}
71+
72+
public void setParentToken(RefreshTokenEntity parentToken) {
73+
this.parentToken = parentToken;
74+
}
75+
76+
public LocalDateTime getIssuedAt() {
77+
return issuedAt;
78+
}
79+
80+
public void setIssuedAt(LocalDateTime issuedAt) {
81+
this.issuedAt = issuedAt;
82+
}
83+
84+
public LocalDateTime getExpiresAt() {
85+
return expiresAt;
86+
}
87+
88+
public void setExpiresAt(LocalDateTime expiresAt) {
89+
this.expiresAt = expiresAt;
90+
}
91+
92+
public LocalDateTime getRevokedAt() {
93+
return revokedAt;
94+
}
95+
96+
public void setRevokedAt(LocalDateTime revokedAt) {
97+
this.revokedAt = revokedAt;
98+
}
99+
100+
public RefreshTokenEntity getReplacedBy() {
101+
return replacedBy;
102+
}
103+
104+
public void setReplacedBy(RefreshTokenEntity replacedBy) {
105+
this.replacedBy = replacedBy;
106+
}
107+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.cloudmedia.identity.persistence.entity;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.FetchType;
6+
import jakarta.persistence.Id;
7+
import jakarta.persistence.JoinColumn;
8+
import jakarta.persistence.ManyToOne;
9+
import jakarta.persistence.Table;
10+
import java.time.LocalDateTime;
11+
12+
@Entity
13+
@Table(name = "sessions")
14+
public class SessionEntity {
15+
16+
@Id
17+
@Column(nullable = false, length = 36)
18+
private String id;
19+
20+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
21+
@JoinColumn(name = "user_id", nullable = false)
22+
private UserEntity user;
23+
24+
@Column(name = "device_id", length = 64)
25+
private String deviceId;
26+
27+
@Column(name = "user_agent", length = 255)
28+
private String userAgent;
29+
30+
@Column(name = "ip_address", length = 64)
31+
private String ipAddress;
32+
33+
@Column(name = "created_at", nullable = false)
34+
private LocalDateTime createdAt;
35+
36+
@Column(name = "expires_at", nullable = false)
37+
private LocalDateTime expiresAt;
38+
39+
@Column(name = "revoked_at")
40+
private LocalDateTime revokedAt;
41+
42+
public String getId() {
43+
return id;
44+
}
45+
46+
public void setId(String id) {
47+
this.id = id;
48+
}
49+
50+
public UserEntity getUser() {
51+
return user;
52+
}
53+
54+
public void setUser(UserEntity user) {
55+
this.user = user;
56+
}
57+
58+
public String getDeviceId() {
59+
return deviceId;
60+
}
61+
62+
public void setDeviceId(String deviceId) {
63+
this.deviceId = deviceId;
64+
}
65+
66+
public String getUserAgent() {
67+
return userAgent;
68+
}
69+
70+
public void setUserAgent(String userAgent) {
71+
this.userAgent = userAgent;
72+
}
73+
74+
public String getIpAddress() {
75+
return ipAddress;
76+
}
77+
78+
public void setIpAddress(String ipAddress) {
79+
this.ipAddress = ipAddress;
80+
}
81+
82+
public LocalDateTime getCreatedAt() {
83+
return createdAt;
84+
}
85+
86+
public void setCreatedAt(LocalDateTime createdAt) {
87+
this.createdAt = createdAt;
88+
}
89+
90+
public LocalDateTime getExpiresAt() {
91+
return expiresAt;
92+
}
93+
94+
public void setExpiresAt(LocalDateTime expiresAt) {
95+
this.expiresAt = expiresAt;
96+
}
97+
98+
public LocalDateTime getRevokedAt() {
99+
return revokedAt;
100+
}
101+
102+
public void setRevokedAt(LocalDateTime revokedAt) {
103+
this.revokedAt = revokedAt;
104+
}
105+
}

0 commit comments

Comments
 (0)