kybernus 2.0.10 → 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 (74) hide show
  1. package/package.json +1 -1
  2. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/PostgresUserRepository.java.hbs +40 -0
  3. package/templates/java-spring/clean/src/main/resources/application.properties.hbs +18 -0
  4. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/web/controller → adapters/inbound/web}/AuthController.java.hbs +4 -5
  5. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/JpaUserAdapter.java.hbs +40 -0
  6. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/entity/UserEntity.java.hbs +61 -0
  7. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/persistence/repository/JpaUserRepository.java.hbs +11 -0
  8. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{infrastructure/security/SecurityAdapters.java.hbs → adapters/outbound/security/SecurityAdapter.java.hbs} +14 -14
  9. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/entity → core/domain}/User.java.hbs +2 -2
  10. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/LoginUserUseCase.java.hbs +8 -8
  11. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/usecase → core/ports/inbound}/RegisterUserUseCase.java.hbs +7 -8
  12. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{domain/repository → core/ports/outbound}/UserRepository.java.hbs +4 -4
  13. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{application → core}/service/AuthService.java.hbs +9 -9
  14. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/{{projectNamePascalCase}}Application.java.hbs +2 -2
  15. package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +18 -0
  16. package/templates/nestjs/clean/package.json.hbs +9 -3
  17. package/templates/nestjs/clean/prisma/schema.prisma.hbs +20 -0
  18. package/templates/nestjs/clean/src/app.module.ts.hbs +17 -0
  19. package/templates/nestjs/clean/src/auth.module.ts.hbs +12 -10
  20. package/templates/nestjs/clean/src/infrastructure/database/prisma.service.ts.hbs +13 -0
  21. package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +32 -0
  22. package/templates/nestjs/clean/src/main.ts.hbs +11 -0
  23. package/templates/nestjs/hexagonal/package.json.hbs +9 -3
  24. package/templates/nestjs/hexagonal/prisma/schema.prisma +20 -0
  25. package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.service.ts.hbs +13 -0
  26. package/templates/nestjs/hexagonal/src/adapters/outbound/persistence/prisma.user.adapter.ts.hbs +32 -0
  27. package/templates/nestjs/hexagonal/src/app.module.ts.hbs +17 -0
  28. package/templates/nestjs/hexagonal/src/auth.module.ts.hbs +15 -13
  29. package/templates/nestjs/hexagonal/src/main.ts.hbs +11 -0
  30. package/templates/nextjs/mvc/package.json.hbs +35 -32
  31. package/templates/nextjs/mvc/prisma/schema.prisma.hbs +12 -9
  32. package/templates/nextjs/mvc/src/lib/db.ts +15 -0
  33. package/templates/nodejs-express/clean/docker-compose.yml.hbs +5 -6
  34. package/templates/nodejs-express/clean/package.json.hbs +14 -8
  35. package/templates/nodejs-express/clean/prisma/schema.prisma +20 -0
  36. package/templates/nodejs-express/clean/src/config/index.ts +27 -0
  37. package/templates/nodejs-express/clean/src/index.ts.hbs +20 -24
  38. package/templates/nodejs-express/clean/src/infrastructure/database/PrismaUserRepository.ts.hbs +61 -0
  39. package/templates/nodejs-express/clean/src/infrastructure/database/prisma.ts.hbs +5 -0
  40. package/templates/nodejs-express/clean/src/infrastructure/http/controllers/AuthController.ts.hbs +24 -40
  41. package/templates/nodejs-express/clean/src/infrastructure/http/middlewares/errorHandler.ts +24 -0
  42. package/templates/nodejs-express/clean/tsconfig.json.hbs +8 -17
  43. package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +5 -6
  44. package/templates/nodejs-express/hexagonal/package.json.hbs +14 -8
  45. package/templates/nodejs-express/hexagonal/prisma/schema.prisma +20 -0
  46. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/AuthController.ts.hbs +29 -44
  47. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/middlewares/errorHandler.ts +24 -0
  48. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/PrismaUserAdapter.ts.hbs +61 -0
  49. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +5 -0
  50. package/templates/nodejs-express/hexagonal/src/config/index.ts +27 -0
  51. package/templates/nodejs-express/hexagonal/src/index.ts.hbs +24 -27
  52. package/templates/nodejs-express/hexagonal/tsconfig.json.hbs +8 -17
  53. package/templates/python-fastapi/clean/app/application/services/__init__.py +0 -0
  54. package/templates/python-fastapi/clean/app/application/services/user_service.py.hbs +20 -0
  55. package/templates/python-fastapi/clean/app/config.py.hbs +24 -0
  56. package/templates/python-fastapi/clean/app/infrastructure/database/models.py.hbs +24 -0
  57. package/templates/python-fastapi/clean/app/infrastructure/database/postgres_repository.py.hbs +62 -0
  58. package/templates/python-fastapi/clean/app/infrastructure/database/session.py.hbs +27 -0
  59. package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +14 -8
  60. package/templates/python-fastapi/clean/app/main.py.hbs +25 -3
  61. package/templates/python-fastapi/clean/requirements.txt.hbs +3 -1
  62. package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +41 -17
  63. package/templates/python-fastapi/hexagonal/app/adapters/outbound/postgres_user_repository.py.hbs +50 -0
  64. package/templates/python-fastapi/hexagonal/app/config.py.hbs +20 -0
  65. package/templates/python-fastapi/hexagonal/app/infrastructure/database/models.py.hbs +24 -0
  66. package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +20 -0
  67. package/templates/python-fastapi/hexagonal/app/main.py.hbs +22 -14
  68. package/templates/python-fastapi/hexagonal/requirements.txt.hbs +3 -1
  69. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
  70. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/infrastructure/persistence/InMemoryUserRepository.java.hbs +0 -41
  71. package/templates/nestjs/clean/src/infrastructure/database/in-memory.repository.ts.hbs +0 -17
  72. package/templates/nodejs-express/clean/src/infrastructure/database/InMemoryUserRepository.ts.hbs +0 -46
  73. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/InMemoryUserAdapter.ts.hbs +0 -38
  74. /package/templates/python-fastapi/hexagonal/app/core/{ports.py.hbs → ports/ports.py.hbs} +0 -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,41 +0,0 @@
1
- package {{packageName}}.infrastructure.persistence;
2
-
3
- import {{packageName}}.domain.entity.User;
4
- import {{packageName}}.domain.repository.UserRepository;
5
- import org.springframework.stereotype.Repository;
6
-
7
- import java.util.Map;
8
- import java.util.Optional;
9
- import java.util.concurrent.ConcurrentHashMap;
10
-
11
- /**
12
- * In-Memory User Repository - Infrastructure Layer
13
- * Replace with JPA implementation for production
14
- */
15
- @Repository
16
- public class InMemoryUserRepository implements UserRepository {
17
- private final Map<String, User> users = new ConcurrentHashMap<>();
18
-
19
- @Override
20
- public Optional<User> findById(String id) {
21
- return Optional.ofNullable(users.get(id));
22
- }
23
-
24
- @Override
25
- public Optional<User> findByEmail(String email) {
26
- return users.values().stream()
27
- .filter(user -> user.getEmail().equals(email))
28
- .findFirst();
29
- }
30
-
31
- @Override
32
- public User save(User user) {
33
- users.put(user.getId(), user);
34
- return user;
35
- }
36
-
37
- @Override
38
- public void delete(String id) {
39
- users.remove(id);
40
- }
41
- }
@@ -1,41 +0,0 @@
1
- package {{packageName}}.infrastructure.persistence;
2
-
3
- import {{packageName}}.domain.entity.User;
4
- import {{packageName}}.domain.repository.UserRepository;
5
- import org.springframework.stereotype.Repository;
6
-
7
- import java.util.Map;
8
- import java.util.Optional;
9
- import java.util.concurrent.ConcurrentHashMap;
10
-
11
- /**
12
- * In-Memory User Repository - Infrastructure Layer
13
- * Replace with JPA implementation for production
14
- */
15
- @Repository
16
- public class InMemoryUserRepository implements UserRepository {
17
- private final Map<String, User> users = new ConcurrentHashMap<>();
18
-
19
- @Override
20
- public Optional<User> findById(String id) {
21
- return Optional.ofNullable(users.get(id));
22
- }
23
-
24
- @Override
25
- public Optional<User> findByEmail(String email) {
26
- return users.values().stream()
27
- .filter(user -> user.getEmail().equals(email))
28
- .findFirst();
29
- }
30
-
31
- @Override
32
- public User save(User user) {
33
- users.put(user.getId(), user);
34
- return user;
35
- }
36
-
37
- @Override
38
- public void delete(String id) {
39
- users.remove(id);
40
- }
41
- }
@@ -1,17 +0,0 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { IUserRepository } from '../../domain/repositories/user.repository';
3
- import { User } from '../../domain/entities/user.entity';
4
-
5
- @Injectable()
6
- export class InMemoryUserRepository extends IUserRepository {
7
- private users: User[] = [];
8
-
9
- async findByEmail(email: string): Promise<User | null> {
10
- return this.users.find((u) => u.email === email) || null;
11
- }
12
-
13
- async save(user: User): Promise<User> {
14
- this.users.push(user);
15
- return user;
16
- }
17
- }
@@ -1,46 +0,0 @@
1
- import { User } from '../../domain/entities/User';
2
- import { IUserRepository } from '../../domain/repositories/IUserRepository';
3
-
4
- /**
5
- * In-Memory User Repository - Infrastructure Layer
6
- * Replace with actual database implementation (Prisma, TypeORM, etc.)
7
- */
8
- export class InMemoryUserRepository implements IUserRepository {
9
- private users: Map<string, User> = new Map();
10
-
11
- async findById(id: string): Promise<User | null> {
12
- return this.users.get(id) || null;
13
- }
14
-
15
- async findByEmail(email: string): Promise<User | null> {
16
- for (const user of this.users.values()) {
17
- if (user.email === email) {
18
- return user;
19
- }
20
- }
21
- return null;
22
- }
23
-
24
- async save(user: User): Promise<User> {
25
- const id = Date.now().toString();
26
- const savedUser = User.restore({
27
- id,
28
- email: user.email,
29
- name: user.name,
30
- password: user.password,
31
- stripeCustomerId: user.stripeCustomerId,
32
- });
33
- this.users.set(id, savedUser);
34
- return savedUser;
35
- }
36
-
37
- async update(user: User): Promise<User> {
38
- if (!user.id) throw new Error('User must have an ID to update');
39
- this.users.set(user.id, user);
40
- return user;
41
- }
42
-
43
- async delete(id: string): Promise<void> {
44
- this.users.delete(id);
45
- }
46
- }
@@ -1,38 +0,0 @@
1
- import { User } from '../../core/domain/entities/User';
2
- import { IUserRepositoryPort } from '../../core/ports/outbound/IUserRepositoryPort';
3
-
4
- /**
5
- * In-Memory User Repository Adapter
6
- * Implements the outbound port
7
- */
8
- export class InMemoryUserAdapter implements IUserRepositoryPort {
9
- private users: Map<string, User> = new Map();
10
-
11
- async findById(id: string): Promise<User | null> {
12
- return this.users.get(id) || null;
13
- }
14
-
15
- async findByEmail(email: string): Promise<User | null> {
16
- for (const user of this.users.values()) {
17
- if (user.email === email) return user;
18
- }
19
- return null;
20
- }
21
-
22
- async save(user: User): Promise<User> {
23
- const id = Date.now().toString();
24
- const savedUser = user.withId(id);
25
- this.users.set(id, savedUser);
26
- return savedUser;
27
- }
28
-
29
- async update(user: User): Promise<User> {
30
- if (!user.id) throw new Error('User must have an ID');
31
- this.users.set(user.id, user);
32
- return user;
33
- }
34
-
35
- async delete(id: string): Promise<void> {
36
- this.users.delete(id);
37
- }
38
- }