kybernus 2.0.9 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/package.json +2 -2
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/PostgresUserRepository.java.hbs +40 -0
- package/templates/java-spring/clean/src/main/resources/application.properties.hbs +18 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/web/controller → adapters/inbound/web}/AuthController.java.hbs +4 -5
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/JpaUserAdapter.java.hbs +40 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/entity/UserEntity.java.hbs +61 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/repository/JpaUserRepository.java.hbs +11 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/security/SecurityAdapters.java.hbs → adapters/outbound/security/SecurityAdapter.java.hbs} +14 -14
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/entity → core/domain}/User.java.hbs +2 -2
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/LoginUserUseCase.java.hbs +8 -8
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/RegisterUserUseCase.java.hbs +7 -8
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/repository → core/ports/outbound}/UserRepository.java.hbs +4 -4
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{application → core}/service/AuthService.java.hbs +9 -9
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{{projectNamePascalCase}}Application.java.hbs +2 -2
- package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +18 -0
- package/templates/nestjs/clean/package.json.hbs +9 -3
- package/templates/nestjs/clean/prisma/schema.prisma.hbs +20 -0
- package/templates/nestjs/clean/src/app.module.ts.hbs +17 -0
- package/templates/nestjs/clean/src/auth.module.ts.hbs +12 -10
- package/templates/nestjs/clean/src/infrastructure/database/prisma.service.ts.hbs +13 -0
- package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +32 -0
- package/templates/nestjs/clean/src/main.ts.hbs +11 -0
- package/templates/nestjs/hexagonal/package.json.hbs +9 -3
- package/templates/nestjs/hexagonal/prisma/schema.prisma +20 -0
- package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.service.ts.hbs +13 -0
- package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.user.adapter.ts.hbs +32 -0
- package/templates/nestjs/hexagonal/src/app.module.ts.hbs +17 -0
- package/templates/nestjs/hexagonal/src/auth.module.ts.hbs +15 -13
- package/templates/nestjs/hexagonal/src/main.ts.hbs +11 -0
- package/templates/nextjs/mvc/package.json.hbs +35 -32
- package/templates/nextjs/mvc/prisma/schema.prisma.hbs +12 -9
- package/templates/nextjs/mvc/src/lib/db.ts +15 -0
- package/templates/nodejs-express/clean/docker-compose.yml.hbs +5 -6
- package/templates/nodejs-express/clean/package.json.hbs +14 -8
- package/templates/nodejs-express/clean/prisma/schema.prisma +20 -0
- package/templates/nodejs-express/clean/src/config/index.ts +27 -0
- package/templates/nodejs-express/clean/src/index.ts.hbs +20 -24
- package/templates/nodejs-express/clean/src/infrastructure/database/PrismaUserRepository.ts.hbs +61 -0
- package/templates/nodejs-express/clean/src/infrastructure/database/prisma.ts.hbs +5 -0
- package/templates/nodejs-express/clean/src/infrastructure/http/controllers/AuthController.ts.hbs +24 -40
- package/templates/nodejs-express/clean/src/infrastructure/http/middlewares/errorHandler.ts +24 -0
- package/templates/nodejs-express/clean/tsconfig.json.hbs +8 -17
- package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +5 -6
- package/templates/nodejs-express/hexagonal/package.json.hbs +14 -8
- package/templates/nodejs-express/hexagonal/prisma/schema.prisma +20 -0
- package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/AuthController.ts.hbs +29 -44
- package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/middlewares/errorHandler.ts +24 -0
- package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/PrismaUserAdapter.ts.hbs +61 -0
- package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +5 -0
- package/templates/nodejs-express/hexagonal/src/config/index.ts +27 -0
- package/templates/nodejs-express/hexagonal/src/index.ts.hbs +24 -27
- package/templates/nodejs-express/hexagonal/tsconfig.json.hbs +8 -17
- package/templates/python-fastapi/clean/app/application/services/__init__.py +0 -0
- package/templates/python-fastapi/clean/app/application/services/user_service.py.hbs +20 -0
- package/templates/python-fastapi/clean/app/config.py.hbs +24 -0
- package/templates/python-fastapi/clean/app/infrastructure/database/models.py.hbs +24 -0
- package/templates/python-fastapi/clean/app/infrastructure/database/postgres_repository.py.hbs +62 -0
- package/templates/python-fastapi/clean/app/infrastructure/database/session.py.hbs +27 -0
- package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +14 -8
- package/templates/python-fastapi/clean/app/main.py.hbs +25 -3
- package/templates/python-fastapi/clean/requirements.txt.hbs +3 -1
- package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +41 -17
- package/templates/python-fastapi/hexagonal/app/adapters/outbound/postgres_user_repository.py.hbs +50 -0
- package/templates/python-fastapi/hexagonal/app/config.py.hbs +20 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/database/models.py.hbs +24 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +20 -0
- package/templates/python-fastapi/hexagonal/app/main.py.hbs +22 -14
- package/templates/python-fastapi/hexagonal/requirements.txt.hbs +3 -1
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
- package/templates/nestjs/clean/src/infrastructure/database/in-memory.repository.ts.hbs +0 -17
- package/templates/nodejs-express/clean/src/infrastructure/database/InMemoryUserRepository.ts.hbs +0 -46
- package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/InMemoryUserAdapter.ts.hbs +0 -38
- /package/templates/python-fastapi/hexagonal/app/core/{ports.py.hbs → ports/ports.py.hbs} +0 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Build production-ready applications in minutes. Kybernus generates robust, scala
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
|
|
15
|
+
npx kybernus@latest init
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
---
|
|
@@ -21,7 +21,7 @@ npm install -g kybernus
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
# Create your first project
|
|
24
|
-
kybernus init
|
|
24
|
+
npx kybernus@latest init
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
---
|
|
@@ -19,7 +19,7 @@ export async function initCommand(options) {
|
|
|
19
19
|
await generator.generate(config, process.cwd());
|
|
20
20
|
// Track generation
|
|
21
21
|
const analytics = new AnalyticsClient();
|
|
22
|
-
analytics.track('project_generated', {
|
|
22
|
+
await analytics.track('project_generated', {
|
|
23
23
|
name: config.projectName,
|
|
24
24
|
stack: config.stack,
|
|
25
25
|
architecture: config.architecture,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAWjE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAoB;IAClD,6DAA6D;IAC7D,iEAAiE;IACjE,iBAAiB;IACjB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IAExC,mBAAmB;IACnB,MAAM,SAAS,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACzC,MAAM,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAEhD,mBAAmB;IACnB,MAAM,SAAS,GAAG,IAAI,eAAe,EAAE,CAAC;IACxC,SAAS,CAAC,KAAK,CAAC,mBAAmB,EAAE;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAWjE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAoB;IAClD,6DAA6D;IAC7D,iEAAiE;IACjE,iBAAiB;IACjB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IAExC,mBAAmB;IACnB,MAAM,SAAS,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACzC,MAAM,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAEhD,mBAAmB;IACnB,MAAM,SAAS,GAAG,IAAI,eAAe,EAAE,CAAC;IACxC,MAAM,SAAS,CAAC,KAAK,CAAC,mBAAmB,EAAE;QACvC,IAAI,EAAE,MAAM,CAAC,WAAW;QACxB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,MAAM;KAClB,CAAC,CAAC;IAEH,KAAK,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;AAClE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kybernus",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Ultimate Scaffolding CLI for Modern Developers",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -76,4 +76,4 @@
|
|
|
76
76
|
"overrides": {
|
|
77
77
|
"rimraf": "^6.1.2"
|
|
78
78
|
}
|
|
79
|
-
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package {{packageName}}.infrastructure.persistence;
|
|
2
|
+
|
|
3
|
+
import {{packageName}}.domain.entity.User;
|
|
4
|
+
import {{packageName}}.domain.repository.UserRepository;
|
|
5
|
+
import {{packageName}}.infrastructure.persistence.entity.UserEntity;
|
|
6
|
+
import {{packageName}}.infrastructure.persistence.repository.JpaUserRepository;
|
|
7
|
+
import org.springframework.stereotype.Repository;
|
|
8
|
+
|
|
9
|
+
import java.util.Optional;
|
|
10
|
+
|
|
11
|
+
@Repository
|
|
12
|
+
public class PostgresUserRepository implements UserRepository {
|
|
13
|
+
|
|
14
|
+
private final JpaUserRepository jpaUserRepository;
|
|
15
|
+
|
|
16
|
+
public PostgresUserRepository(JpaUserRepository jpaUserRepository) {
|
|
17
|
+
this.jpaUserRepository = jpaUserRepository;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Override
|
|
21
|
+
public Optional<User> findById(String id) {
|
|
22
|
+
return jpaUserRepository.findById(id).map(UserEntity::toDomain);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public Optional<User> findByEmail(String email) {
|
|
27
|
+
return jpaUserRepository.findByEmail(email).map(UserEntity::toDomain);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Override
|
|
31
|
+
public User save(User user) {
|
|
32
|
+
UserEntity entity = UserEntity.fromDomain(user);
|
|
33
|
+
return jpaUserRepository.save(entity).toDomain();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Override
|
|
37
|
+
public void delete(String id) {
|
|
38
|
+
jpaUserRepository.deleteById(id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
spring.application.name={{projectNameKebabCase}}
|
|
2
|
+
|
|
3
|
+
# Database Configuration
|
|
4
|
+
spring.datasource.url=jdbc:postgresql://postgres:5432/{{projectNameKebabCase}}
|
|
5
|
+
spring.datasource.username=postgres
|
|
6
|
+
spring.datasource.password=postgres
|
|
7
|
+
spring.datasource.driver-class-name=org.postgresql.Driver
|
|
8
|
+
|
|
9
|
+
# JPA / Hibernate
|
|
10
|
+
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
|
11
|
+
spring.jpa.hibernate.ddl-auto=update
|
|
12
|
+
spring.jpa.show-sql=true
|
|
13
|
+
spring.jpa.properties.hibernate.format_sql=true
|
|
14
|
+
|
|
15
|
+
# JWT
|
|
16
|
+
application.security.jwt.secret-key={{jwtSecretKey}}
|
|
17
|
+
application.security.jwt.expiration=86400000
|
|
18
|
+
application.security.jwt.refresh-token.expiration=604800000
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
package {{packageName}}.
|
|
1
|
+
package {{packageName}}.adapters.inbound.web;
|
|
2
2
|
|
|
3
|
-
import {{packageName}}.
|
|
3
|
+
import {{packageName}}.core.service.AuthService;
|
|
4
4
|
import org.springframework.http.ResponseEntity;
|
|
5
5
|
import org.springframework.web.bind.annotation.*;
|
|
6
6
|
|
|
@@ -19,8 +19,7 @@ public class AuthController {
|
|
|
19
19
|
public record AuthRequest(String email, String name, String password) {}
|
|
20
20
|
|
|
21
21
|
@PostMapping("/register")
|
|
22
|
-
public ResponseEntity
|
|
23
|
-
<?> register(@RequestBody AuthRequest request) {
|
|
22
|
+
public ResponseEntity<?> register(@RequestBody AuthRequest request) {
|
|
24
23
|
try {
|
|
25
24
|
var result = authService.register(request.email(), request.name(), request.password());
|
|
26
25
|
return ResponseEntity.ok(result);
|
|
@@ -38,4 +37,4 @@ public class AuthController {
|
|
|
38
37
|
return ResponseEntity.status(401).body(Map.of("error", e.getMessage()));
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
|
-
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package {{packageName}}.adapters.outbound.persistence;
|
|
2
|
+
|
|
3
|
+
import {{packageName}}.core.domain.User;
|
|
4
|
+
import {{packageName}}.core.ports.outbound.UserRepository;
|
|
5
|
+
import {{packageName}}.adapters.outbound.persistence.entity.UserEntity;
|
|
6
|
+
import {{packageName}}.adapters.outbound.persistence.repository.JpaUserRepository;
|
|
7
|
+
import org.springframework.stereotype.Component;
|
|
8
|
+
|
|
9
|
+
import java.util.Optional;
|
|
10
|
+
|
|
11
|
+
@Component
|
|
12
|
+
public class JpaUserAdapter implements UserRepository {
|
|
13
|
+
|
|
14
|
+
private final JpaUserRepository jpaUserRepository;
|
|
15
|
+
|
|
16
|
+
public JpaUserAdapter(JpaUserRepository jpaUserRepository) {
|
|
17
|
+
this.jpaUserRepository = jpaUserRepository;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Override
|
|
21
|
+
public Optional<User> findById(String id) {
|
|
22
|
+
return jpaUserRepository.findById(id).map(UserEntity::toDomain);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public Optional<User> findByEmail(String email) {
|
|
27
|
+
return jpaUserRepository.findByEmail(email).map(UserEntity::toDomain);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Override
|
|
31
|
+
public User save(User user) {
|
|
32
|
+
UserEntity entity = UserEntity.fromDomain(user);
|
|
33
|
+
return jpaUserRepository.save(entity).toDomain();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Override
|
|
37
|
+
public void delete(String id) {
|
|
38
|
+
jpaUserRepository.deleteById(id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package {{packageName}}.adapters.outbound.persistence.entity;
|
|
2
|
+
|
|
3
|
+
import jakarta.persistence.Entity;
|
|
4
|
+
import jakarta.persistence.Id;
|
|
5
|
+
import jakarta.persistence.Table;
|
|
6
|
+
import jakarta.persistence.Column;
|
|
7
|
+
import lombok.AllArgsConstructor;
|
|
8
|
+
import lombok.Builder;
|
|
9
|
+
import lombok.Data;
|
|
10
|
+
import lombok.NoArgsConstructor;
|
|
11
|
+
import java.time.LocalDateTime;
|
|
12
|
+
import org.hibernate.annotations.CreationTimestamp;
|
|
13
|
+
import org.hibernate.annotations.UpdateTimestamp;
|
|
14
|
+
import {{packageName}}.core.domain.User;
|
|
15
|
+
|
|
16
|
+
@Data
|
|
17
|
+
@Builder
|
|
18
|
+
@NoArgsConstructor
|
|
19
|
+
@AllArgsConstructor
|
|
20
|
+
@Entity
|
|
21
|
+
@Table(name = "users")
|
|
22
|
+
public class UserEntity {
|
|
23
|
+
|
|
24
|
+
@Id
|
|
25
|
+
private String id;
|
|
26
|
+
|
|
27
|
+
@Column(unique = true, nullable = false)
|
|
28
|
+
private String email;
|
|
29
|
+
|
|
30
|
+
@Column(nullable = false)
|
|
31
|
+
private String name;
|
|
32
|
+
|
|
33
|
+
@Column(nullable = false)
|
|
34
|
+
private String password;
|
|
35
|
+
|
|
36
|
+
@Column(name = "stripe_customer_id")
|
|
37
|
+
private String stripeCustomerId;
|
|
38
|
+
|
|
39
|
+
@CreationTimestamp
|
|
40
|
+
@Column(name = "created_at", updatable = false)
|
|
41
|
+
private LocalDateTime createdAt;
|
|
42
|
+
|
|
43
|
+
@UpdateTimestamp
|
|
44
|
+
@Column(name = "updated_at")
|
|
45
|
+
private LocalDateTime updatedAt;
|
|
46
|
+
|
|
47
|
+
public User toDomain() {
|
|
48
|
+
return User.restore(this.id, this.email, this.name, this.password, this.stripeCustomerId, this.createdAt);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public static UserEntity fromDomain(User user) {
|
|
52
|
+
return UserEntity.builder()
|
|
53
|
+
.id(user.getId())
|
|
54
|
+
.email(user.getEmail())
|
|
55
|
+
.name(user.getName())
|
|
56
|
+
.password(user.getPassword())
|
|
57
|
+
.stripeCustomerId(user.getStripeCustomerId())
|
|
58
|
+
.createdAt(user.getCreatedAt())
|
|
59
|
+
.build();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package {{packageName}}.adapters.outbound.persistence.repository;
|
|
2
|
+
|
|
3
|
+
import org.springframework.data.jpa.repository.JpaRepository;
|
|
4
|
+
import org.springframework.stereotype.Repository;
|
|
5
|
+
import {{packageName}}.adapters.outbound.persistence.entity.UserEntity;
|
|
6
|
+
import java.util.Optional;
|
|
7
|
+
|
|
8
|
+
@Repository
|
|
9
|
+
public interface JpaUserRepository extends JpaRepository<UserEntity, String> {
|
|
10
|
+
Optional<UserEntity> findByEmail(String email);
|
|
11
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
package {{packageName}}.
|
|
1
|
+
package {{packageName}}.adapters.outbound.security;
|
|
2
2
|
|
|
3
|
-
import {{packageName}}.
|
|
3
|
+
import {{packageName}}.core.ports.inbound.RegisterUserUseCase;
|
|
4
4
|
import io.jsonwebtoken.Jwts;
|
|
5
5
|
import io.jsonwebtoken.security.Keys;
|
|
6
6
|
import org.springframework.beans.factory.annotation.Value;
|
|
@@ -12,18 +12,18 @@ import java.nio.charset.StandardCharsets;
|
|
|
12
12
|
import java.util.Date;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Security
|
|
16
|
-
* Implements domain port interfaces
|
|
15
|
+
* Security Adapter - Outbound Adapter
|
|
16
|
+
* Implements domain port interfaces for Password Encoding and Token Generation
|
|
17
17
|
*/
|
|
18
18
|
@Component
|
|
19
|
-
public class
|
|
19
|
+
public class SecurityAdapter implements RegisterUserUseCase.PasswordEncoder, RegisterUserUseCase.TokenGenerator {
|
|
20
20
|
|
|
21
21
|
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
|
22
22
|
|
|
23
|
-
@Value("${jwt.secret
|
|
23
|
+
@Value("${application.security.jwt.secret-key}")
|
|
24
24
|
private String jwtSecret;
|
|
25
25
|
|
|
26
|
-
@Value("${jwt.expiration:
|
|
26
|
+
@Value("${application.security.jwt.expiration:86400000}")
|
|
27
27
|
private long jwtExpiration;
|
|
28
28
|
|
|
29
29
|
private SecretKey getSigningKey() {
|
|
@@ -43,11 +43,11 @@ public class SecurityAdapters implements RegisterUserUseCase.PasswordEncoder, Re
|
|
|
43
43
|
@Override
|
|
44
44
|
public String generate(String userId, String email) {
|
|
45
45
|
return Jwts.builder()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
.subject(userId)
|
|
47
|
+
.claim("email", email)
|
|
48
|
+
.issuedAt(new Date())
|
|
49
|
+
.expiration(new Date(System.currentTimeMillis() + jwtExpiration))
|
|
50
|
+
.signWith(getSigningKey())
|
|
51
|
+
.compact();
|
|
52
52
|
}
|
|
53
|
-
}
|
|
53
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
package {{packageName}}.domain
|
|
1
|
+
package {{packageName}}.core.domain;
|
|
2
2
|
|
|
3
3
|
import java.time.LocalDateTime;
|
|
4
4
|
import java.util.UUID;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* User Entity - Domain Layer (
|
|
7
|
+
* User Entity - Domain Layer (Hexagonal Core)
|
|
8
8
|
* Pure business object with no framework dependencies
|
|
9
9
|
*/
|
|
10
10
|
public class User {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
package {{packageName}}.
|
|
1
|
+
package {{packageName}}.core.ports.inbound;
|
|
2
2
|
|
|
3
|
-
import {{packageName}}.domain.
|
|
4
|
-
import {{packageName}}.
|
|
3
|
+
import {{packageName}}.core.domain.User;
|
|
4
|
+
import {{packageName}}.core.ports.outbound.UserRepository;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Login User Use Case -
|
|
7
|
+
* Login User Use Case - Inbound Port
|
|
8
8
|
*/
|
|
9
9
|
public class LoginUserUseCase {
|
|
10
10
|
private final UserRepository userRepository;
|
|
@@ -12,8 +12,8 @@ public class LoginUserUseCase {
|
|
|
12
12
|
private final RegisterUserUseCase.TokenGenerator tokenGenerator;
|
|
13
13
|
|
|
14
14
|
public LoginUserUseCase(UserRepository userRepository,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
RegisterUserUseCase.PasswordEncoder passwordEncoder,
|
|
16
|
+
RegisterUserUseCase.TokenGenerator tokenGenerator) {
|
|
17
17
|
this.userRepository = userRepository;
|
|
18
18
|
this.passwordEncoder = passwordEncoder;
|
|
19
19
|
this.tokenGenerator = tokenGenerator;
|
|
@@ -24,7 +24,7 @@ public class LoginUserUseCase {
|
|
|
24
24
|
|
|
25
25
|
public Output execute(Input input) {
|
|
26
26
|
User user = userRepository.findByEmail(input.email())
|
|
27
|
-
|
|
27
|
+
.orElseThrow(() -> new IllegalArgumentException("Invalid credentials"));
|
|
28
28
|
|
|
29
29
|
if (!passwordEncoder.matches(input.password(), user.getPassword())) {
|
|
30
30
|
throw new IllegalArgumentException("Invalid credentials");
|
|
@@ -33,4 +33,4 @@ public class LoginUserUseCase {
|
|
|
33
33
|
String token = tokenGenerator.generate(user.getId(), user.getEmail());
|
|
34
34
|
return new Output(user, token);
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
package {{packageName}}.
|
|
1
|
+
package {{packageName}}.core.ports.inbound;
|
|
2
2
|
|
|
3
|
-
import {{packageName}}.domain.
|
|
4
|
-
import {{packageName}}.
|
|
3
|
+
import {{packageName}}.core.domain.User;
|
|
4
|
+
import {{packageName}}.core.ports.outbound.UserRepository;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Register User Use Case -
|
|
7
|
+
* Register User Use Case - Inbound Port
|
|
8
8
|
*/
|
|
9
9
|
public class RegisterUserUseCase {
|
|
10
10
|
private final UserRepository userRepository;
|
|
11
11
|
private final PasswordEncoder passwordEncoder;
|
|
12
12
|
private final TokenGenerator tokenGenerator;
|
|
13
13
|
|
|
14
|
-
public RegisterUserUseCase(UserRepository userRepository, PasswordEncoder passwordEncoder, TokenGenerator
|
|
15
|
-
tokenGenerator) {
|
|
14
|
+
public RegisterUserUseCase(UserRepository userRepository, PasswordEncoder passwordEncoder, TokenGenerator tokenGenerator) {
|
|
16
15
|
this.userRepository = userRepository;
|
|
17
16
|
this.passwordEncoder = passwordEncoder;
|
|
18
17
|
this.tokenGenerator = tokenGenerator;
|
|
@@ -34,7 +33,7 @@ public class RegisterUserUseCase {
|
|
|
34
33
|
return new Output(savedUser, token);
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
// Port interfaces
|
|
36
|
+
// Port interfaces for needed services
|
|
38
37
|
public interface PasswordEncoder {
|
|
39
38
|
String encode(String rawPassword);
|
|
40
39
|
boolean matches(String rawPassword, String encodedPassword);
|
|
@@ -43,4 +42,4 @@ public class RegisterUserUseCase {
|
|
|
43
42
|
public interface TokenGenerator {
|
|
44
43
|
String generate(String userId, String email);
|
|
45
44
|
}
|
|
46
|
-
}
|
|
45
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
package {{packageName}}.
|
|
1
|
+
package {{packageName}}.core.ports.outbound;
|
|
2
2
|
|
|
3
|
-
import {{packageName}}.domain.
|
|
3
|
+
import {{packageName}}.core.domain.User;
|
|
4
4
|
import java.util.Optional;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* User Repository
|
|
7
|
+
* User Repository Port - Outbound
|
|
8
8
|
* Defines the contract for user persistence
|
|
9
9
|
*/
|
|
10
10
|
public interface UserRepository {
|
|
@@ -12,4 +12,4 @@ public interface UserRepository {
|
|
|
12
12
|
Optional<User> findByEmail(String email);
|
|
13
13
|
User save(User user);
|
|
14
14
|
void delete(String id);
|
|
15
|
-
}
|
|
15
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
package {{packageName}}.
|
|
1
|
+
package {{packageName}}.core.service;
|
|
2
2
|
|
|
3
|
-
import {{packageName}}.domain.
|
|
4
|
-
import {{packageName}}.
|
|
5
|
-
import {{packageName}}.
|
|
6
|
-
import {{packageName}}.
|
|
3
|
+
import {{packageName}}.core.domain.User;
|
|
4
|
+
import {{packageName}}.core.ports.outbound.UserRepository;
|
|
5
|
+
import {{packageName}}.core.ports.inbound.LoginUserUseCase;
|
|
6
|
+
import {{packageName}}.core.ports.inbound.RegisterUserUseCase;
|
|
7
7
|
import org.springframework.stereotype.Service;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Auth Service - Application Layer
|
|
10
|
+
* Auth Service - Application Layer / Service
|
|
11
11
|
* Orchestrates use cases
|
|
12
12
|
*/
|
|
13
13
|
@Service
|
|
@@ -16,8 +16,8 @@ public class AuthService {
|
|
|
16
16
|
private final LoginUserUseCase loginUserUseCase;
|
|
17
17
|
|
|
18
18
|
public AuthService(UserRepository userRepository,
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
RegisterUserUseCase.PasswordEncoder passwordEncoder,
|
|
20
|
+
RegisterUserUseCase.TokenGenerator tokenGenerator) {
|
|
21
21
|
this.registerUserUseCase = new RegisterUserUseCase(userRepository, passwordEncoder, tokenGenerator);
|
|
22
22
|
this.loginUserUseCase = new LoginUserUseCase(userRepository, passwordEncoder, tokenGenerator);
|
|
23
23
|
}
|
|
@@ -33,4 +33,4 @@ public class AuthService {
|
|
|
33
33
|
var result = loginUserUseCase.execute(new LoginUserUseCase.Input(email, password));
|
|
34
34
|
return new AuthResponse(result.token(), result.user().getId(), result.user().getEmail(), result.user().getName());
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|
|
@@ -9,6 +9,6 @@ public class {{projectNamePascalCase}}Application {
|
|
|
9
9
|
public static void main(String[] args) {
|
|
10
10
|
SpringApplication.run({{projectNamePascalCase}}Application.class, args);
|
|
11
11
|
System.out.println("🚀 {{projectNamePascalCase}} running!");
|
|
12
|
-
System.out.println("
|
|
12
|
+
System.out.println("🔷 Architecture: Hexagonal Architecture");
|
|
13
13
|
}
|
|
14
|
-
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
spring.application.name={{projectNameKebabCase}}
|
|
2
|
+
|
|
3
|
+
# Database Configuration
|
|
4
|
+
spring.datasource.url=jdbc:postgresql://postgres:5432/{{projectNameKebabCase}}
|
|
5
|
+
spring.datasource.username=postgres
|
|
6
|
+
spring.datasource.password=postgres
|
|
7
|
+
spring.datasource.driver-class-name=org.postgresql.Driver
|
|
8
|
+
|
|
9
|
+
# JPA / Hibernate
|
|
10
|
+
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
|
11
|
+
spring.jpa.hibernate.ddl-auto=update
|
|
12
|
+
spring.jpa.show-sql=true
|
|
13
|
+
spring.jpa.properties.hibernate.format_sql=true
|
|
14
|
+
|
|
15
|
+
# JWT
|
|
16
|
+
application.security.jwt.secret-key={{jwtSecretKey}}
|
|
17
|
+
application.security.jwt.expiration=86400000
|
|
18
|
+
application.security.jwt.refresh-token.expiration=604800000
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
"build": "nest build",
|
|
8
8
|
"start": "node dist/main.js",
|
|
9
9
|
"lint": "eslint \"{src,test}/**/*.ts\"",
|
|
10
|
-
"test": "jest"
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"migrate:dev": "prisma migrate dev",
|
|
12
|
+
"migrate:deploy": "prisma migrate deploy",
|
|
13
|
+
"generate": "prisma generate"
|
|
11
14
|
},
|
|
12
15
|
"dependencies": {
|
|
13
16
|
"@nestjs/common": "^10.3.0",
|
|
@@ -16,6 +19,7 @@
|
|
|
16
19
|
"@nestjs/jwt": "^10.2.0",
|
|
17
20
|
"@nestjs/passport": "^10.0.3",
|
|
18
21
|
"@nestjs/platform-express": "^10.3.0",
|
|
22
|
+
"@prisma/client": "^5.10.2",
|
|
19
23
|
"bcryptjs": "^2.4.3",
|
|
20
24
|
"class-transformer": "^0.5.1",
|
|
21
25
|
"class-validator": "^0.14.1",
|
|
@@ -32,6 +36,8 @@
|
|
|
32
36
|
"@types/node": "^20.11.0",
|
|
33
37
|
"@types/passport-jwt": "^4.0.0",
|
|
34
38
|
"@types/uuid": "^9.0.7",
|
|
35
|
-
"
|
|
39
|
+
"prisma": "^5.10.2",
|
|
40
|
+
"typescript": "^5.3.3",
|
|
41
|
+
"eslint": "^8.56.0"
|
|
36
42
|
}
|
|
37
|
-
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model User {
|
|
11
|
+
id String @id @default(uuid())
|
|
12
|
+
email String @unique
|
|
13
|
+
name String
|
|
14
|
+
password String
|
|
15
|
+
stripeCustomerId String? @map("stripe_customer_id")
|
|
16
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
17
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
18
|
+
|
|
19
|
+
@@map("users")
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { ConfigModule } from '@nestjs/config';
|
|
3
|
+
import { AuthModule } from './auth.module';
|
|
4
|
+
import { PrismaService } from './infrastructure/database/prisma.service';
|
|
5
|
+
|
|
6
|
+
@Module({
|
|
7
|
+
imports: [
|
|
8
|
+
ConfigModule.forRoot({
|
|
9
|
+
isGlobal: true,
|
|
10
|
+
}),
|
|
11
|
+
AuthModule,
|
|
12
|
+
],
|
|
13
|
+
controllers: [],
|
|
14
|
+
providers: [PrismaService],
|
|
15
|
+
exports: [PrismaService], // Export PrismaService if other modules need it
|
|
16
|
+
})
|
|
17
|
+
export class AppModule {}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
import { RegisterUseCase } from './domain/use-cases/register.use-case';
|
|
3
3
|
import { IUserRepository } from './domain/repositories/user.repository';
|
|
4
|
-
import {
|
|
4
|
+
import { PrismaUserRepository } from './infrastructure/database/repositories/prisma.user.repository';
|
|
5
|
+
import { PrismaService } from './infrastructure/database/prisma.service';
|
|
5
6
|
import { AuthController } from './infrastructure/http/auth.controller';
|
|
6
7
|
|
|
7
8
|
@Module({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
providers: [
|
|
10
|
+
PrismaService,
|
|
11
|
+
RegisterUseCase,
|
|
12
|
+
{
|
|
13
|
+
provide: IUserRepository,
|
|
14
|
+
useClass: PrismaUserRepository,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
controllers: [AuthController],
|
|
16
18
|
})
|
|
17
|
-
export class AuthModule {}
|
|
19
|
+
export class AuthModule {}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { PrismaClient } from '@prisma/client';
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
|
6
|
+
async onModuleInit() {
|
|
7
|
+
await this.$connect();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async onModuleDestroy() {
|
|
11
|
+
await this.$disconnect();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { IUserRepository } from '../../../../domain/repositories/user.repository';
|
|
3
|
+
import { User } from '../../../../domain/entities/user.entity';
|
|
4
|
+
import { PrismaService } from '../prisma.service';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class PrismaUserRepository implements IUserRepository {
|
|
8
|
+
constructor(private prisma: PrismaService) {}
|
|
9
|
+
|
|
10
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
11
|
+
const user = await this.prisma.user.findUnique({ where: { email } });
|
|
12
|
+
if (!user) return null;
|
|
13
|
+
return new User(user.id, user.name, user.email, user.password);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async save(user: User): Promise<User> {
|
|
17
|
+
const savedUser = await this.prisma.user.upsert({
|
|
18
|
+
where: { email: user.email },
|
|
19
|
+
update: {
|
|
20
|
+
name: user.name,
|
|
21
|
+
password: user.password,
|
|
22
|
+
},
|
|
23
|
+
create: {
|
|
24
|
+
id: user.id,
|
|
25
|
+
name: user.name,
|
|
26
|
+
email: user.email,
|
|
27
|
+
password: user.password,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return new User(savedUser.id, savedUser.name, savedUser.email, savedUser.password);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NestFactory } from '@nestjs/core';
|
|
2
|
+
import { AppModule } from './app.module';
|
|
3
|
+
import { ValidationPipe } from '@nestjs/common';
|
|
4
|
+
|
|
5
|
+
async function bootstrap() {
|
|
6
|
+
const app = await NestFactory.create(AppModule);
|
|
7
|
+
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
|
|
8
|
+
app.enableCors();
|
|
9
|
+
await app.listen(process.env.PORT || 3000);
|
|
10
|
+
}
|
|
11
|
+
bootstrap();
|