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.
Files changed (77) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/commands/init.js +1 -1
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/package.json +2 -2
  5. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/PostgresUserRepository.java.hbs +40 -0
  6. package/templates/java-spring/clean/src/main/resources/application.properties.hbs +18 -0
  7. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/web/controller → adapters/inbound/web}/AuthController.java.hbs +4 -5
  8. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/JpaUserAdapter.java.hbs +40 -0
  9. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/entity/UserEntity.java.hbs +61 -0
  10. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/repository/JpaUserRepository.java.hbs +11 -0
  11. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/security/SecurityAdapters.java.hbs → adapters/outbound/security/SecurityAdapter.java.hbs} +14 -14
  12. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/entity → core/domain}/User.java.hbs +2 -2
  13. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/LoginUserUseCase.java.hbs +8 -8
  14. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/RegisterUserUseCase.java.hbs +7 -8
  15. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/repository → core/ports/outbound}/UserRepository.java.hbs +4 -4
  16. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{application → core}/service/AuthService.java.hbs +9 -9
  17. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{{projectNamePascalCase}}Application.java.hbs +2 -2
  18. package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +18 -0
  19. package/templates/nestjs/clean/package.json.hbs +9 -3
  20. package/templates/nestjs/clean/prisma/schema.prisma.hbs +20 -0
  21. package/templates/nestjs/clean/src/app.module.ts.hbs +17 -0
  22. package/templates/nestjs/clean/src/auth.module.ts.hbs +12 -10
  23. package/templates/nestjs/clean/src/infrastructure/database/prisma.service.ts.hbs +13 -0
  24. package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +32 -0
  25. package/templates/nestjs/clean/src/main.ts.hbs +11 -0
  26. package/templates/nestjs/hexagonal/package.json.hbs +9 -3
  27. package/templates/nestjs/hexagonal/prisma/schema.prisma +20 -0
  28. package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.service.ts.hbs +13 -0
  29. package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.user.adapter.ts.hbs +32 -0
  30. package/templates/nestjs/hexagonal/src/app.module.ts.hbs +17 -0
  31. package/templates/nestjs/hexagonal/src/auth.module.ts.hbs +15 -13
  32. package/templates/nestjs/hexagonal/src/main.ts.hbs +11 -0
  33. package/templates/nextjs/mvc/package.json.hbs +35 -32
  34. package/templates/nextjs/mvc/prisma/schema.prisma.hbs +12 -9
  35. package/templates/nextjs/mvc/src/lib/db.ts +15 -0
  36. package/templates/nodejs-express/clean/docker-compose.yml.hbs +5 -6
  37. package/templates/nodejs-express/clean/package.json.hbs +14 -8
  38. package/templates/nodejs-express/clean/prisma/schema.prisma +20 -0
  39. package/templates/nodejs-express/clean/src/config/index.ts +27 -0
  40. package/templates/nodejs-express/clean/src/index.ts.hbs +20 -24
  41. package/templates/nodejs-express/clean/src/infrastructure/database/PrismaUserRepository.ts.hbs +61 -0
  42. package/templates/nodejs-express/clean/src/infrastructure/database/prisma.ts.hbs +5 -0
  43. package/templates/nodejs-express/clean/src/infrastructure/http/controllers/AuthController.ts.hbs +24 -40
  44. package/templates/nodejs-express/clean/src/infrastructure/http/middlewares/errorHandler.ts +24 -0
  45. package/templates/nodejs-express/clean/tsconfig.json.hbs +8 -17
  46. package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +5 -6
  47. package/templates/nodejs-express/hexagonal/package.json.hbs +14 -8
  48. package/templates/nodejs-express/hexagonal/prisma/schema.prisma +20 -0
  49. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/AuthController.ts.hbs +29 -44
  50. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/middlewares/errorHandler.ts +24 -0
  51. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/PrismaUserAdapter.ts.hbs +61 -0
  52. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +5 -0
  53. package/templates/nodejs-express/hexagonal/src/config/index.ts +27 -0
  54. package/templates/nodejs-express/hexagonal/src/index.ts.hbs +24 -27
  55. package/templates/nodejs-express/hexagonal/tsconfig.json.hbs +8 -17
  56. package/templates/python-fastapi/clean/app/application/services/__init__.py +0 -0
  57. package/templates/python-fastapi/clean/app/application/services/user_service.py.hbs +20 -0
  58. package/templates/python-fastapi/clean/app/config.py.hbs +24 -0
  59. package/templates/python-fastapi/clean/app/infrastructure/database/models.py.hbs +24 -0
  60. package/templates/python-fastapi/clean/app/infrastructure/database/postgres_repository.py.hbs +62 -0
  61. package/templates/python-fastapi/clean/app/infrastructure/database/session.py.hbs +27 -0
  62. package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +14 -8
  63. package/templates/python-fastapi/clean/app/main.py.hbs +25 -3
  64. package/templates/python-fastapi/clean/requirements.txt.hbs +3 -1
  65. package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +41 -17
  66. package/templates/python-fastapi/hexagonal/app/adapters/outbound/postgres_user_repository.py.hbs +50 -0
  67. package/templates/python-fastapi/hexagonal/app/config.py.hbs +20 -0
  68. package/templates/python-fastapi/hexagonal/app/infrastructure/database/models.py.hbs +24 -0
  69. package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +20 -0
  70. package/templates/python-fastapi/hexagonal/app/main.py.hbs +22 -14
  71. package/templates/python-fastapi/hexagonal/requirements.txt.hbs +3 -1
  72. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
  73. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
  74. package/templates/nestjs/clean/src/infrastructure/database/in-memory.repository.ts.hbs +0 -17
  75. package/templates/nodejs-express/clean/src/infrastructure/database/InMemoryUserRepository.ts.hbs +0 -46
  76. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/InMemoryUserAdapter.ts.hbs +0 -38
  77. /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
- npm install -g kybernus
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;QACjC,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"}
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.9",
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}}.infrastructure.web.controller;
1
+ package {{packageName}}.adapters.inbound.web;
2
2
 
3
- import {{packageName}}.application.service.AuthService;
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}}.infrastructure.security;
1
+ package {{packageName}}.adapters.outbound.security;
2
2
 
3
- import {{packageName}}.domain.usecase.RegisterUserUseCase;
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 Adapters - Infrastructure Layer
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 SecurityAdapters implements RegisterUserUseCase.PasswordEncoder, RegisterUserUseCase.TokenGenerator {
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:your-super-secret-jwt-key-change-in-production}")
23
+ @Value("${application.security.jwt.secret-key}")
24
24
  private String jwtSecret;
25
25
 
26
- @Value("${jwt.expiration:604800000}")
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
- .subject(userId)
47
- .claim("email", email)
48
- .issuedAt(new Date())
49
- .expiration(new Date(System.currentTimeMillis() + jwtExpiration))
50
- .signWith(getSigningKey())
51
- .compact();
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.entity;
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 (Clean Architecture)
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}}.domain.usecase;
1
+ package {{packageName}}.core.ports.inbound;
2
2
 
3
- import {{packageName}}.domain.entity.User;
4
- import {{packageName}}.domain.repository.UserRepository;
3
+ import {{packageName}}.core.domain.User;
4
+ import {{packageName}}.core.ports.outbound.UserRepository;
5
5
 
6
6
  /**
7
- * Login User Use Case - Domain Layer
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
- RegisterUserUseCase.PasswordEncoder passwordEncoder,
16
- RegisterUserUseCase.TokenGenerator tokenGenerator) {
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
- .orElseThrow(() -> new IllegalArgumentException("Invalid credentials"));
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}}.domain.usecase;
1
+ package {{packageName}}.core.ports.inbound;
2
2
 
3
- import {{packageName}}.domain.entity.User;
4
- import {{packageName}}.domain.repository.UserRepository;
3
+ import {{packageName}}.core.domain.User;
4
+ import {{packageName}}.core.ports.outbound.UserRepository;
5
5
 
6
6
  /**
7
- * Register User Use Case - Domain Layer
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}}.domain.repository;
1
+ package {{packageName}}.core.ports.outbound;
2
2
 
3
- import {{packageName}}.domain.entity.User;
3
+ import {{packageName}}.core.domain.User;
4
4
  import java.util.Optional;
5
5
 
6
6
  /**
7
- * User Repository Interface - Domain Layer
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}}.application.service;
1
+ package {{packageName}}.core.service;
2
2
 
3
- import {{packageName}}.domain.entity.User;
4
- import {{packageName}}.domain.repository.UserRepository;
5
- import {{packageName}}.domain.usecase.LoginUserUseCase;
6
- import {{packageName}}.domain.usecase.RegisterUserUseCase;
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
- RegisterUserUseCase.PasswordEncoder passwordEncoder,
20
- RegisterUserUseCase.TokenGenerator tokenGenerator) {
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("📐 Architecture: Clean Architecture");
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
- "typescript": "^5.3.3"
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 { InMemoryUserRepository } from './infrastructure/database/in-memory.repository';
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
- providers: [
9
- RegisterUseCase,
10
- {
11
- provide: IUserRepository,
12
- useClass: InMemoryUserRepository,
13
- },
14
- ],
15
- controllers: [AuthController],
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();