kybernus 2.0.10 → 2.1.1

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 (208) hide show
  1. package/node_modules/{@isaacs/balanced-match → balanced-match}/README.md +7 -10
  2. package/node_modules/{@isaacs/balanced-match → balanced-match}/package.json +5 -16
  3. package/node_modules/{@isaacs/brace-expansion → brace-expansion}/README.md +2 -5
  4. package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/commonjs/index.js +1 -1
  5. package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/commonjs/index.js.map +1 -1
  6. package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/esm/index.js +1 -1
  7. package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/esm/index.js.map +1 -1
  8. package/node_modules/{@isaacs/brace-expansion → brace-expansion}/package.json +9 -5
  9. package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
  10. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
  11. package/node_modules/glob/dist/commonjs/glob.js +2 -1
  12. package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
  13. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  14. package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
  15. package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
  16. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
  17. package/node_modules/glob/dist/commonjs/pattern.js +4 -0
  18. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
  19. package/node_modules/glob/dist/esm/glob.d.ts +8 -0
  20. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
  21. package/node_modules/glob/dist/esm/glob.js +2 -1
  22. package/node_modules/glob/dist/esm/glob.js.map +1 -1
  23. package/node_modules/glob/dist/esm/index.min.js +4 -0
  24. package/node_modules/glob/dist/esm/index.min.js.map +7 -0
  25. package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
  26. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
  27. package/node_modules/glob/dist/esm/pattern.js +4 -0
  28. package/node_modules/glob/dist/esm/pattern.js.map +1 -1
  29. package/node_modules/glob/package.json +30 -10
  30. package/node_modules/lru-cache/package.json +7 -7
  31. package/node_modules/minimatch/README.md +3 -1
  32. package/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
  33. package/node_modules/minimatch/dist/commonjs/ast.js +37 -27
  34. package/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
  35. package/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
  36. package/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
  37. package/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
  38. package/node_modules/minimatch/dist/commonjs/escape.js +4 -4
  39. package/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
  40. package/node_modules/minimatch/dist/commonjs/index.d.ts +50 -0
  41. package/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
  42. package/node_modules/minimatch/dist/commonjs/index.js +37 -31
  43. package/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
  44. package/node_modules/minimatch/dist/commonjs/unescape.js +4 -4
  45. package/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
  46. package/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
  47. package/node_modules/minimatch/dist/esm/ast.js +37 -27
  48. package/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  49. package/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
  50. package/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
  51. package/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  52. package/node_modules/minimatch/dist/esm/escape.js +4 -4
  53. package/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  54. package/node_modules/minimatch/dist/esm/index.d.ts +50 -0
  55. package/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
  56. package/node_modules/minimatch/dist/esm/index.js +37 -31
  57. package/node_modules/minimatch/dist/esm/index.js.map +1 -1
  58. package/node_modules/minimatch/dist/esm/unescape.js +4 -4
  59. package/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  60. package/node_modules/minimatch/package.json +3 -3
  61. package/node_modules/minipass/LICENSE.md +55 -0
  62. package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
  63. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
  64. package/node_modules/minipass/dist/commonjs/index.js +13 -3
  65. package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
  66. package/node_modules/minipass/dist/esm/index.d.ts +12 -16
  67. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
  68. package/node_modules/minipass/dist/esm/index.js +3 -1
  69. package/node_modules/minipass/dist/esm/index.js.map +1 -1
  70. package/node_modules/minipass/package.json +9 -14
  71. package/node_modules/rimraf/README.md +29 -0
  72. package/node_modules/rimraf/dist/commonjs/fs.d.ts +9 -8
  73. package/node_modules/rimraf/dist/commonjs/fs.d.ts.map +1 -1
  74. package/node_modules/rimraf/dist/commonjs/index.d.ts.map +1 -1
  75. package/node_modules/rimraf/dist/commonjs/index.js +9 -3
  76. package/node_modules/rimraf/dist/commonjs/index.js.map +1 -1
  77. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts.map +1 -1
  78. package/node_modules/rimraf/dist/commonjs/opt-arg.js +2 -1
  79. package/node_modules/rimraf/dist/commonjs/opt-arg.js.map +1 -1
  80. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts.map +1 -1
  81. package/node_modules/rimraf/dist/commonjs/path-arg.js +2 -1
  82. package/node_modules/rimraf/dist/commonjs/path-arg.js.map +1 -1
  83. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts +2 -2
  84. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts.map +1 -1
  85. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts.map +1 -1
  86. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js.map +1 -1
  87. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts.map +1 -1
  88. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js +1 -2
  89. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js.map +1 -1
  90. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts.map +1 -1
  91. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js.map +1 -1
  92. package/node_modules/rimraf/dist/esm/bin.mjs.map +1 -1
  93. package/node_modules/rimraf/dist/esm/fs.d.ts +9 -8
  94. package/node_modules/rimraf/dist/esm/fs.d.ts.map +1 -1
  95. package/node_modules/rimraf/dist/esm/index.d.ts.map +1 -1
  96. package/node_modules/rimraf/dist/esm/index.js +10 -4
  97. package/node_modules/rimraf/dist/esm/index.js.map +1 -1
  98. package/node_modules/rimraf/dist/esm/opt-arg.d.ts.map +1 -1
  99. package/node_modules/rimraf/dist/esm/opt-arg.js +2 -1
  100. package/node_modules/rimraf/dist/esm/opt-arg.js.map +1 -1
  101. package/node_modules/rimraf/dist/esm/path-arg.d.ts.map +1 -1
  102. package/node_modules/rimraf/dist/esm/path-arg.js +2 -1
  103. package/node_modules/rimraf/dist/esm/path-arg.js.map +1 -1
  104. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts +2 -2
  105. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts.map +1 -1
  106. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts.map +1 -1
  107. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js +1 -1
  108. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js.map +1 -1
  109. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts.map +1 -1
  110. package/node_modules/rimraf/dist/esm/rimraf-posix.js +1 -2
  111. package/node_modules/rimraf/dist/esm/rimraf-posix.js.map +1 -1
  112. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts.map +1 -1
  113. package/node_modules/rimraf/dist/esm/rimraf-windows.js +1 -1
  114. package/node_modules/rimraf/dist/esm/rimraf-windows.js.map +1 -1
  115. package/node_modules/rimraf/package.json +4 -19
  116. package/package.json +1 -1
  117. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/PostgresUserRepository.java.hbs +40 -0
  118. package/templates/java-spring/clean/src/main/resources/application.properties.hbs +18 -0
  119. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/web/controller → adapters/inbound/web}/AuthController.java.hbs +4 -5
  120. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/JpaUserAdapter.java.hbs +40 -0
  121. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/entity/UserEntity.java.hbs +61 -0
  122. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/repository/JpaUserRepository.java.hbs +11 -0
  123. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/security/SecurityAdapters.java.hbs → adapters/outbound/security/SecurityAdapter.java.hbs} +14 -14
  124. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/entity → core/domain}/User.java.hbs +2 -2
  125. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/LoginUserUseCase.java.hbs +8 -8
  126. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/RegisterUserUseCase.java.hbs +7 -8
  127. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/repository → core/ports/outbound}/UserRepository.java.hbs +4 -4
  128. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{application → core}/service/AuthService.java.hbs +9 -9
  129. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{{projectNamePascalCase}}Application.java.hbs +2 -2
  130. package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +18 -0
  131. package/templates/nestjs/clean/package.json.hbs +9 -3
  132. package/templates/nestjs/clean/prisma/schema.prisma.hbs +20 -0
  133. package/templates/nestjs/clean/src/app.module.ts.hbs +17 -0
  134. package/templates/nestjs/clean/src/auth.module.ts.hbs +12 -10
  135. package/templates/nestjs/clean/src/infrastructure/database/prisma.service.ts.hbs +13 -0
  136. package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +32 -0
  137. package/templates/nestjs/clean/src/main.ts.hbs +11 -0
  138. package/templates/nestjs/hexagonal/package.json.hbs +9 -3
  139. package/templates/nestjs/hexagonal/prisma/schema.prisma +20 -0
  140. package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.service.ts.hbs +13 -0
  141. package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.user.adapter.ts.hbs +32 -0
  142. package/templates/nestjs/hexagonal/src/app.module.ts.hbs +17 -0
  143. package/templates/nestjs/hexagonal/src/auth.module.ts.hbs +15 -13
  144. package/templates/nestjs/hexagonal/src/main.ts.hbs +11 -0
  145. package/templates/nextjs/mvc/package.json.hbs +35 -32
  146. package/templates/nextjs/mvc/prisma/schema.prisma.hbs +12 -9
  147. package/templates/nextjs/mvc/src/lib/db.ts +15 -0
  148. package/templates/nodejs-express/clean/docker-compose.yml.hbs +5 -6
  149. package/templates/nodejs-express/clean/package.json.hbs +14 -8
  150. package/templates/nodejs-express/clean/prisma/schema.prisma +20 -0
  151. package/templates/nodejs-express/clean/src/config/index.ts +27 -0
  152. package/templates/nodejs-express/clean/src/index.ts.hbs +20 -24
  153. package/templates/nodejs-express/clean/src/infrastructure/database/PrismaUserRepository.ts.hbs +61 -0
  154. package/templates/nodejs-express/clean/src/infrastructure/database/prisma.ts.hbs +5 -0
  155. package/templates/nodejs-express/clean/src/infrastructure/http/controllers/AuthController.ts.hbs +24 -40
  156. package/templates/nodejs-express/clean/src/infrastructure/http/middlewares/errorHandler.ts +24 -0
  157. package/templates/nodejs-express/clean/tsconfig.json.hbs +8 -17
  158. package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +5 -6
  159. package/templates/nodejs-express/hexagonal/package.json.hbs +14 -8
  160. package/templates/nodejs-express/hexagonal/prisma/schema.prisma +20 -0
  161. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/AuthController.ts.hbs +29 -44
  162. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/middlewares/errorHandler.ts +24 -0
  163. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/PrismaUserAdapter.ts.hbs +61 -0
  164. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +5 -0
  165. package/templates/nodejs-express/hexagonal/src/config/index.ts +27 -0
  166. package/templates/nodejs-express/hexagonal/src/index.ts.hbs +24 -27
  167. package/templates/nodejs-express/hexagonal/tsconfig.json.hbs +8 -17
  168. package/templates/python-fastapi/clean/app/application/services/__init__.py +0 -0
  169. package/templates/python-fastapi/clean/app/application/services/user_service.py.hbs +20 -0
  170. package/templates/python-fastapi/clean/app/config.py.hbs +24 -0
  171. package/templates/python-fastapi/clean/app/infrastructure/database/models.py.hbs +24 -0
  172. package/templates/python-fastapi/clean/app/infrastructure/database/postgres_repository.py.hbs +62 -0
  173. package/templates/python-fastapi/clean/app/infrastructure/database/session.py.hbs +27 -0
  174. package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +14 -8
  175. package/templates/python-fastapi/clean/app/main.py.hbs +25 -3
  176. package/templates/python-fastapi/clean/requirements.txt.hbs +3 -1
  177. package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +41 -17
  178. package/templates/python-fastapi/hexagonal/app/adapters/outbound/postgres_user_repository.py.hbs +50 -0
  179. package/templates/python-fastapi/hexagonal/app/config.py.hbs +20 -0
  180. package/templates/python-fastapi/hexagonal/app/infrastructure/database/models.py.hbs +24 -0
  181. package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +20 -0
  182. package/templates/python-fastapi/hexagonal/app/main.py.hbs +22 -14
  183. package/templates/python-fastapi/hexagonal/requirements.txt.hbs +3 -1
  184. package/node_modules/minipass/LICENSE +0 -15
  185. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
  186. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
  187. package/templates/nestjs/clean/src/infrastructure/database/in-memory.repository.ts.hbs +0 -17
  188. package/templates/nodejs-express/clean/src/infrastructure/database/InMemoryUserRepository.ts.hbs +0 -46
  189. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/InMemoryUserAdapter.ts.hbs +0 -38
  190. /package/node_modules/{@isaacs/balanced-match → balanced-match}/LICENSE.md +0 -0
  191. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/commonjs/index.d.ts +0 -0
  192. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/commonjs/index.d.ts.map +0 -0
  193. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/commonjs/index.js +0 -0
  194. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/commonjs/index.js.map +0 -0
  195. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/commonjs/package.json +0 -0
  196. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/esm/index.d.ts +0 -0
  197. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/esm/index.d.ts.map +0 -0
  198. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/esm/index.js +0 -0
  199. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/esm/index.js.map +0 -0
  200. /package/node_modules/{@isaacs/balanced-match → balanced-match}/dist/esm/package.json +0 -0
  201. /package/node_modules/{@isaacs/brace-expansion → brace-expansion}/LICENSE +0 -0
  202. /package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/commonjs/index.d.ts +0 -0
  203. /package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/commonjs/index.d.ts.map +0 -0
  204. /package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/commonjs/package.json +0 -0
  205. /package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/esm/index.d.ts +0 -0
  206. /package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/esm/index.d.ts.map +0 -0
  207. /package/node_modules/{@isaacs/brace-expansion → brace-expansion}/dist/esm/package.json +0 -0
  208. /package/templates/python-fastapi/hexagonal/app/core/{ports.py.hbs → ports/ports.py.hbs} +0 -0
@@ -1,41 +1,38 @@
1
+ import 'express-async-errors';
1
2
  import express from 'express';
2
3
  import cors from 'cors';
3
4
  import helmet from 'helmet';
4
5
  import morgan from 'morgan';
5
- import dotenv from 'dotenv';
6
-
7
- // Core
8
- import { AuthService } from './core/AuthService';
9
-
10
- // Adapters
11
- import { InMemoryUserAdapter } from './adapters/outbound/persistence/InMemoryUserAdapter';
12
- import { bcryptAdapter, jwtAdapter } from './adapters/outbound/SecurityAdapters';
13
- import { createAuthController } from './adapters/inbound/http/AuthController';
14
-
15
- dotenv.config();
6
+ import { config } from './config';
7
+ import { PrismaUserAdapter } from './adapters/outbound/persistence/PrismaUserAdapter';
8
+ import { AuthService } from './core/services/AuthService';
9
+ import { AuthController } from './adapters/inbound/http/AuthController';
10
+ import { errorHandler } from './adapters/inbound/http/middlewares/errorHandler';
16
11
 
17
12
  const app = express();
18
13
 
19
- // Middleware
20
- app.use(helmet());
14
+ // Middlewares
15
+ app.use(express.json());
21
16
  app.use(cors());
17
+ app.use(helmet());
22
18
  app.use(morgan('dev'));
23
- app.use(express.json());
24
19
 
25
- // Dependency Injection - Wire up the hexagon
26
- const userRepository = new InMemoryUserAdapter();
27
- const authService = new AuthService(userRepository, bcryptAdapter, jwtAdapter);
20
+ // Dependency Injection
21
+ const userRepository = new PrismaUserAdapter();
22
+ const authService = new AuthService(userRepository);
23
+ const authController = new AuthController(authService);
28
24
 
29
- // Mount adapters
30
- app.use('/api/auth', createAuthController(authService));
25
+ // Routes
26
+ app.post('/api/auth/register', (req, res, next) => authController.register(req, res, next));
27
+ app.post('/api/auth/login', (req, res, next) => authController.login(req, res, next));
31
28
 
32
- // Health check
33
- app.get('/health', (_, res) => res.json({ status: 'ok' }));
29
+ app.get('/health', (req, res) => {
30
+ res.json({ status: 'ok', architecture: 'hexagonal' });
31
+ });
34
32
 
35
- const PORT = process.env.PORT || 3000;
33
+ // Error Handler
34
+ app.use(errorHandler);
36
35
 
37
- app.listen(PORT, () => {
38
- console.log(`🚀 {{pascalCase projectName}} running on port ${PORT}`);
39
- console.log(`⬡ Architecture: Hexagonal (Ports & Adapters)`);
40
- console.log(`🔗 Health check: http://localhost:${PORT}/health`);
41
- });
36
+ app.listen(config.port, () => {
37
+ console.log(`🚀 Server running on port ${config.port}`);
38
+ });
@@ -1,27 +1,18 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2022",
3
+ "target": "es2020",
4
4
  "module": "commonjs",
5
- "lib": [
6
- "ES2022"
7
- ],
5
+ "lib": ["es2020"],
8
6
  "outDir": "./dist",
9
7
  "rootDir": "./src",
10
8
  "strict": true,
11
9
  "esModuleInterop": true,
12
10
  "skipLibCheck": true,
13
11
  "forceConsistentCasingInFileNames": true,
14
- "resolveJsonModule": true,
15
- "moduleResolution": "node",
16
- "types": [
17
- "node"
18
- ]
12
+ "experimentalDecorators": true,
13
+ "emitDecoratorMetadata": true,
14
+ "resolveJsonModule": true
19
15
  },
20
- "include": [
21
- "src/**/*"
22
- ],
23
- "exclude": [
24
- "node_modules",
25
- "dist"
26
- ]
27
- }
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"]
18
+ }
@@ -0,0 +1,20 @@
1
+ from typing import Optional
2
+ from app.domain.entities.user import User
3
+ from app.domain.repositories.user_repository import IUserRepository
4
+ from app.domain.usecases.register_user import RegisterUserUseCase, IPasswordHasher, ITokenGenerator
5
+
6
+ # In Clean Architecture, Services usually orchestrate Use Cases or coordinate cross-domain logic.
7
+ # For simple CRUD, Use Cases might be sufficient, but here is a Service example.
8
+
9
+ class UserService:
10
+ def __init__(self, repository: IUserRepository):
11
+ self.repository = repository
12
+
13
+ async def get_user(self, user_id: str) -> Optional[User]:
14
+ return await self.repository.find_by_id(user_id)
15
+
16
+ async def get_user_by_email(self, email: str) -> Optional[User]:
17
+ return await self.repository.find_by_email(email)
18
+
19
+ # Note: Registration logic is handled by RegisterUserUseCase, aligning with Clean Architecture
20
+ # where specific complex actions are Use Cases.
@@ -0,0 +1,24 @@
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+ from functools import lru_cache
3
+
4
+ class Settings(BaseSettings):
5
+ PROJECT_NAME: str = "{{projectName}}"
6
+ API_V1_STR: str = "/api/v1"
7
+
8
+ # Database
9
+ # Use asyncpg for async SQLAlchemy
10
+ DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/{{projectName}}_db"
11
+
12
+ # Security
13
+ SECRET_KEY: str = "change_this_to_a_secure_random_key"
14
+ ALGORITHM: str = "HS256"
15
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
16
+
17
+ # Stripe
18
+ STRIPE_API_KEY: str | None = None
19
+
20
+ model_config = SettingsConfigDict(env_file=".env", case_sensitive=True)
21
+
22
+ @lru_cache
23
+ def get_settings():
24
+ return Settings()
@@ -0,0 +1,24 @@
1
+ from sqlalchemy import Column, String, DateTime, Boolean
2
+ from sqlalchemy.sql import func
3
+ from app.infrastructure.database.session import Base
4
+ import uuid
5
+
6
+ class UserModel(Base):
7
+ __tablename__ = "users"
8
+
9
+ id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4()))
10
+ email = Column(String, unique=True, index=True, nullable=False)
11
+ name = Column(String, nullable=False)
12
+ password = Column(String, nullable=False)
13
+ stripe_customer_id = Column(String, nullable=True)
14
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
15
+ is_active = Column(Boolean, default=True)
16
+
17
+ def to_dict(self):
18
+ return {
19
+ "id": self.id,
20
+ "email": self.email,
21
+ "name": self.name,
22
+ "stripe_customer_id": self.stripe_customer_id,
23
+ "created_at": self.created_at
24
+ }
@@ -0,0 +1,62 @@
1
+ from typing import Optional
2
+ from sqlalchemy.ext.asyncio import AsyncSession
3
+ from sqlalchemy import select, delete
4
+ from app.domain.repositories.user_repository import IUserRepository
5
+ from app.domain.entities.user import User
6
+ from app.infrastructure.database.models import UserModel
7
+
8
+ class PostgresUserRepository(IUserRepository):
9
+ def __init__(self, session: AsyncSession):
10
+ self.session = session
11
+
12
+ def _to_entity(self, model: UserModel) -> User:
13
+ return User(
14
+ id=model.id,
15
+ email=model.email,
16
+ name=model.name,
17
+ password=model.password,
18
+ stripe_customer_id=model.stripe_customer_id,
19
+ created_at=model.created_at
20
+ )
21
+
22
+ async def find_by_id(self, user_id: str) -> Optional[User]:
23
+ result = await self.session.execute(select(UserModel).where(UserModel.id == user_id))
24
+ model = result.scalars().first()
25
+ return self._to_entity(model) if model else None
26
+
27
+ async def find_by_email(self, email: str) -> Optional[User]:
28
+ result = await self.session.execute(select(UserModel).where(UserModel.email == email))
29
+ model = result.scalars().first()
30
+ return self._to_entity(model) if model else None
31
+
32
+ async def save(self, user: User) -> User:
33
+ # Check if exists to decide update vs insert (simplistic approach)
34
+ # In a real app, optimize this or use merge
35
+ existing = await self.find_by_id(user.id)
36
+
37
+ if existing:
38
+ stmt = select(UserModel).where(UserModel.id == user.id)
39
+ result = await self.session.execute(stmt)
40
+ model = result.scalars().first()
41
+ model.email = user.email
42
+ model.name = user.name
43
+ model.password = user.password
44
+ model.stripe_customer_id = user.stripe_customer_id
45
+ else:
46
+ model = UserModel(
47
+ id=user.id,
48
+ email=user.email,
49
+ name=user.name,
50
+ password=user.password,
51
+ stripe_customer_id=user.stripe_customer_id,
52
+ created_at=user.created_at
53
+ )
54
+ self.session.add(model)
55
+
56
+ await self.session.commit()
57
+ await self.session.refresh(model)
58
+ return self._to_entity(model)
59
+
60
+ async def delete(self, user_id: str) -> None:
61
+ await self.session.execute(delete(UserModel).where(UserModel.id == user_id))
62
+ await self.session.commit()
@@ -0,0 +1,27 @@
1
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
2
+ from sqlalchemy.orm import DeclarativeBase
3
+ from app.config import get_settings
4
+
5
+ settings = get_settings()
6
+
7
+ engine = create_async_engine(
8
+ settings.DATABASE_URL,
9
+ echo=True, # Set to False in production
10
+ )
11
+
12
+ AsyncSessionLocal = async_sessionmaker(
13
+ bind=engine,
14
+ class_=AsyncSession,
15
+ expire_on_commit=False,
16
+ autoflush=False,
17
+ )
18
+
19
+ class Base(DeclarativeBase):
20
+ pass
21
+
22
+ async def get_db():
23
+ async with AsyncSessionLocal() as session:
24
+ try:
25
+ yield session
26
+ finally:
27
+ await session.close()
@@ -1,16 +1,19 @@
1
1
  from fastapi import APIRouter, HTTPException, Depends
2
2
  from pydantic import BaseModel, EmailStr
3
+ from sqlalchemy.ext.asyncio import AsyncSession
3
4
  from app.domain.usecases.register_user import RegisterUserUseCase
4
- from app.infrastructure.database.in_memory_repository import InMemoryUserRepository
5
+ from app.infrastructure.database.postgres_repository import PostgresUserRepository
5
6
  from app.infrastructure.security.adapters import BcryptHasher, JwtTokenGenerator
7
+ from app.infrastructure.database.session import get_db
6
8
 
7
9
  router = APIRouter()
8
10
 
9
- # Dependency Injection
10
- repo = InMemoryUserRepository()
11
- hasher = BcryptHasher()
12
- token_gen = JwtTokenGenerator()
13
- register_usecase = RegisterUserUseCase(repo, hasher, token_gen)
11
+ # Dependency Injection Factory
12
+ def get_register_usecase(db: AsyncSession = Depends(get_db)) -> RegisterUserUseCase:
13
+ repo = PostgresUserRepository(db)
14
+ hasher = BcryptHasher()
15
+ token_gen = JwtTokenGenerator()
16
+ return RegisterUserUseCase(repo, hasher, token_gen)
14
17
 
15
18
  class RegisterRequest(BaseModel):
16
19
  email: EmailStr
@@ -18,9 +21,12 @@ class RegisterRequest(BaseModel):
18
21
  password: str
19
22
 
20
23
  @router.post("/register")
21
- async def register(req: RegisterRequest):
24
+ async def register(
25
+ req: RegisterRequest,
26
+ usecase: RegisterUserUseCase = Depends(get_register_usecase)
27
+ ):
22
28
  try:
23
- result = await register_usecase.execute(req.email, req.name, req.password)
29
+ result = await usecase.execute(req.email, req.name, req.password)
24
30
  return result
25
31
  except ValueError as e:
26
32
  raise HTTPException(status_code=400, detail=str(e))
@@ -1,10 +1,32 @@
1
+ from contextlib import asynccontextmanager
1
2
  from fastapi import FastAPI
2
3
  from app.infrastructure.http import auth_controller
4
+ from app.config import get_settings
5
+ from app.infrastructure.database.session import engine, Base
3
6
 
4
- app = FastAPI(title="{{projectName}} - Clean Architecture")
7
+ settings = get_settings()
5
8
 
6
- app.include_router(auth_controller.router, prefix="/api/auth", tags=["Auth"])
9
+ @asynccontextmanager
10
+ async def lifespan(app: FastAPI):
11
+ # Create tables on startup (for development)
12
+ # In production, use Alembic migrations
13
+ async with engine.begin() as conn:
14
+ await conn.run_sync(Base.metadata.create_all)
15
+ yield
16
+ # Cleanup
17
+ await engine.dispose()
18
+
19
+ app = FastAPI(
20
+ title="{{projectName}} - Clean Architecture",
21
+ lifespan=lifespan
22
+ )
23
+
24
+ app.include_router(auth_controller.router, prefix=settings.API_V1_STR + "/auth", tags=["Auth"])
7
25
 
8
26
  @app.get("/health")
9
27
  def health():
10
- return {"status": "ok", "architecture": "clean"}
28
+ return {
29
+ "status": "ok",
30
+ "architecture": "clean",
31
+ "project": settings.PROJECT_NAME
32
+ }
@@ -1,12 +1,14 @@
1
1
  fastapi>=0.109.0
2
2
  uvicorn[standard]>=0.27.0
3
3
  pydantic>=2.5.0
4
+ pydantic-settings>=2.1.0
4
5
  python-dotenv>=1.0.0
5
6
  python-jose[cryptography]>=3.3.0
6
7
  passlib[bcrypt]>=1.7.4
7
8
  stripe>=8.0.0
8
9
  sqlalchemy>=2.0.0
9
10
  alembic>=1.13.0
11
+ asyncpg>=0.29.0
10
12
  psycopg2-binary>=2.9.9
11
13
  pytest>=8.0.0
12
- httpx>=0.26.0
14
+ httpx>=0.26.0
@@ -1,6 +1,13 @@
1
- from fastapi import APIRouter, HTTPException
1
+ from fastapi import APIRouter, HTTPException, Depends
2
2
  from pydantic import BaseModel, EmailStr
3
+ from sqlalchemy.ext.asyncio import AsyncSession
3
4
  from app.core.ports import IAuthPort
5
+ from app.core.domain.user import User as DomainUser
6
+ from app.infrastructure.database.session import get_db
7
+ from app.infrastructure.database.models import UserModel
8
+ from app.adapters.outbound.postgres_user_repository import PostgresUserRepository
9
+ from app.core.service import AuthService
10
+ from app.infrastructure.security.adapters import BcryptHasher, JwtTokenGenerator
4
11
 
5
12
  router = APIRouter()
6
13
 
@@ -9,21 +16,38 @@ class RegisterRequest(BaseModel):
9
16
  name: str
10
17
  password: str
11
18
 
19
+ # Dependency Injection Factory
20
+ def get_auth_service(db: AsyncSession = Depends(get_db)) -> IAuthPort:
21
+ repo = PostgresUserRepository(db)
22
+ hasher = BcryptHasher()
23
+ token_gen = JwtTokenGenerator()
24
+ return AuthService(repo, hasher, token_gen)
25
+
26
+ @router.post("/register")
27
+ async def register(
28
+ req: RegisterRequest,
29
+ service: IAuthPort = Depends(get_auth_service)
30
+ ):
31
+ try:
32
+ result = await service.register(req.email, req.name, req.password)
33
+ return result
34
+ except ValueError as e:
35
+ raise HTTPException(status_code=400, detail=str(e))
36
+
37
+ @router.post("/login")
38
+ async def login(
39
+ req: RegisterRequest,
40
+ service: IAuthPort = Depends(get_auth_service)
41
+ ):
42
+ try:
43
+ result = await service.login(req.email, req.password)
44
+ return result
45
+ except ValueError as e:
46
+ raise HTTPException(status_code=401, detail=str(e))
47
+
48
+ # Setup function for backward compatibility or direct usage
49
+ # though router is preferred
12
50
  def setup_auth_routes(auth_service: IAuthPort):
13
- @router.post("/register")
14
- async def register(req: RegisterRequest):
15
- try:
16
- result = await auth_service.register(req.email, req.name, req.password)
17
- return result
18
- except ValueError as e:
19
- raise HTTPException(status_code=400, detail=str(e))
20
-
21
- @router.post("/login")
22
- async def login(req: RegisterRequest):
23
- try:
24
- result = await auth_service.login(req.email, req.password)
25
- return result
26
- except ValueError as e:
27
- raise HTTPException(status_code=401, detail=str(e))
28
-
51
+ # This pattern is tricky with FastAPIs dependency injection system
52
+ # It's better to rely on Depends() as implemented above
29
53
  return router
@@ -0,0 +1,50 @@
1
+ from typing import Optional
2
+ from sqlalchemy.ext.asyncio import AsyncSession
3
+ from sqlalchemy import select
4
+ from app.core.ports import IUserRepositoryPort
5
+ from app.core.domain.user import User
6
+ from app.infrastructure.database.models import UserModel
7
+
8
+ class PostgresUserRepository(IUserRepositoryPort):
9
+ def __init__(self, session: AsyncSession):
10
+ self.session = session
11
+
12
+ def _to_entity(self, model: UserModel) -> User:
13
+ return User(
14
+ id=model.id,
15
+ email=model.email,
16
+ name=model.name,
17
+ password=model.password,
18
+ stripe_customer_id=model.stripe_customer_id,
19
+ created_at=model.created_at
20
+ )
21
+
22
+ async def find_by_email(self, email: str) -> Optional[User]:
23
+ result = await self.session.execute(select(UserModel).where(UserModel.email == email))
24
+ model = result.scalars().first()
25
+ return self._to_entity(model) if model else None
26
+
27
+ async def save(self, user: User) -> User:
28
+ # Simplistic save/update logic
29
+ result = await self.session.execute(select(UserModel).where(UserModel.id == user.id))
30
+ model = result.scalars().first()
31
+
32
+ if model:
33
+ model.email = user.email
34
+ model.name = user.name
35
+ model.password = user.password
36
+ model.stripe_customer_id = user.stripe_customer_id
37
+ else:
38
+ model = UserModel(
39
+ id=user.id,
40
+ email=user.email,
41
+ name=user.name,
42
+ password=user.password,
43
+ stripe_customer_id=user.stripe_customer_id,
44
+ created_at=user.created_at
45
+ )
46
+ self.session.add(model)
47
+
48
+ await self.session.commit()
49
+ await self.session.refresh(model)
50
+ return self._to_entity(model)
@@ -0,0 +1,20 @@
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+ from functools import lru_cache
3
+
4
+ class Settings(BaseSettings):
5
+ PROJECT_NAME: str = "{{projectName}}"
6
+ API_V1_STR: str = "/api/v1"
7
+
8
+ # Database
9
+ DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/{{projectName}}_db"
10
+
11
+ # Security
12
+ SECRET_KEY: str = "change_this_to_a_secure_random_key"
13
+ ALGORITHM: str = "HS256"
14
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
15
+
16
+ model_config = SettingsConfigDict(env_file=".env", case_sensitive=True)
17
+
18
+ @lru_cache
19
+ def get_settings():
20
+ return Settings()
@@ -0,0 +1,24 @@
1
+ from sqlalchemy import Column, String, DateTime, Boolean
2
+ from sqlalchemy.sql import func
3
+ from app.infrastructure.database.session import Base
4
+ import uuid
5
+
6
+ class UserModel(Base):
7
+ __tablename__ = "users"
8
+
9
+ id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4()))
10
+ email = Column(String, unique=True, index=True, nullable=False)
11
+ name = Column(String, nullable=False)
12
+ password = Column(String, nullable=False)
13
+ stripe_customer_id = Column(String, nullable=True)
14
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
15
+ is_active = Column(Boolean, default=True)
16
+
17
+ def to_dict(self):
18
+ return {
19
+ "id": self.id,
20
+ "email": self.email,
21
+ "name": self.name,
22
+ "stripe_customer_id": self.stripe_customer_id,
23
+ "created_at": self.created_at
24
+ }
@@ -0,0 +1,20 @@
1
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
2
+ from sqlalchemy.orm import DeclarativeBase
3
+ from app.config import get_settings
4
+
5
+ settings = get_settings()
6
+
7
+ engine = create_async_engine(
8
+ settings.DATABASE_URL,
9
+ echo=True,
10
+ )
11
+
12
+ AsyncSessionLocal = async_sessionmaker(
13
+ bind=engine,
14
+ class_=AsyncSession,
15
+ expire_on_commit=False,
16
+ autoflush=False,
17
+ )
18
+
19
+ class Base(DeclarativeBase):
20
+ pass
@@ -1,22 +1,30 @@
1
+ from contextlib import asynccontextmanager
1
2
  from fastapi import FastAPI
2
- from app.core.service import AuthService
3
- from app.adapters.inbound.http_adapter import setup_auth_routes
3
+ from app.adapters.inbound.http_adapter import router as auth_router
4
+ from app.infrastructure.database.session import engine, Base
5
+ from app.config import get_settings
4
6
 
5
- app = FastAPI(title="{{projectName}} - Hexagonal Architecture")
7
+ settings = get_settings()
6
8
 
7
- # Setup dependencies (in production use DI container)
8
- from app.infrastructure.database import InMemoryUserRepository
9
- from app.infrastructure.security import BcryptHasher, JwtGenerator
9
+ @asynccontextmanager
10
+ async def lifespan(app: FastAPI):
11
+ # Create tables on startup (for development)
12
+ async with engine.begin() as conn:
13
+ await conn.run_sync(Base.metadata.create_all)
14
+ yield
15
+ await engine.dispose()
10
16
 
11
- repo = InMemoryUserRepository()
12
- hasher = BcryptHasher()
13
- token_gen = JwtGenerator()
14
- auth_service = AuthService(repo, hasher, token_gen)
17
+ app = FastAPI(
18
+ title="{{projectName}} - Hexagonal Architecture",
19
+ lifespan=lifespan
20
+ )
15
21
 
16
- # Setup routes
17
- auth_router = setup_auth_routes(auth_service)
18
- app.include_router(auth_router, prefix="/api/auth", tags=["Auth"])
22
+ app.include_router(auth_router, prefix=settings.API_V1_STR + "/auth", tags=["Auth"])
19
23
 
20
24
  @app.get("/health")
21
25
  def health():
22
- return {"status": "ok", "architecture": "hexagonal"}
26
+ return {
27
+ "status": "ok",
28
+ "architecture": "hexagonal",
29
+ "project": settings.PROJECT_NAME
30
+ }
@@ -1,12 +1,14 @@
1
1
  fastapi>=0.109.0
2
2
  uvicorn[standard]>=0.27.0
3
3
  pydantic>=2.5.0
4
+ pydantic-settings>=2.1.0
4
5
  python-dotenv>=1.0.0
5
6
  python-jose[cryptography]>=3.3.0
6
7
  passlib[bcrypt]>=1.7.4
7
8
  stripe>=8.0.0
8
9
  sqlalchemy>=2.0.0
9
10
  alembic>=1.13.0
11
+ asyncpg>=0.29.0
10
12
  psycopg2-binary>=2.9.9
11
13
  pytest>=8.0.0
12
- httpx>=0.26.0
14
+ httpx>=0.26.0
@@ -1,15 +0,0 @@
1
- The ISC License
2
-
3
- Copyright (c) 2017-2023 npm, Inc., Isaac Z. Schlueter, and Contributors
4
-
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
15
- IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.