Resumen del proyecto
3
Capas (Routes/Services/Repos)
JWT
Auth con rutas protegidas
Arquitectura de la petición
HTTP Request
Next.js / cURL
→
Netty Engine
embeddedServer
→
Plugins Pipeline
CORS · Auth · StatusPages · ContentNeg
→
Routes DSL
/auth · /campaigns · /health
Services
CampaignService · AuthService
←
Repositories
CampaignRepository · UserRepository
←
ContentNegotiation + JSON
Authentication JWT
StatusPages (5 handlers)
CORS (2 hosts)
Koin DI
WebSockets
fun Application.module() {
configureSerialization()
configureAuthentication()
configureStatusPages()
configureCORS()
configureDI()
configureRouting()
}
GET /health devuelve status ok
PASS
POST /auth/register crea un usuario nuevo
PASS
POST /campaigns sin JWT devuelve 401
PASS
POST /campaigns con JWT crea campaña (201)
PASS
Presupuesto < 100€ devuelve 400
PASS
// testApplication — sin levantar puerto
testApplication {
application { module() }
val res = client.post("/campaigns") {
bearerAuth(token)
setBody(CreateCampaignRequest(...))
}
res.status shouldBe HttpStatusCode.Created
}
API Endpoints
| Método |
Ruta |
Descripción |
Auth |
| GET |
/health |
Estado del servidor + versión |
— |
| POST |
/auth/login |
Login → JWT token |
— |
| POST |
/auth/register |
Registro de nuevo usuario |
— |
| GET |
/auth/me |
Perfil del usuario autenticado |
JWT |
| GET |
/campaigns?page=&limit= |
Listar campañas (paginado) |
— |
| POST |
/campaigns |
Crear campaña nueva (mín. 100€) |
JWT |
| GET |
/campaigns/{id} |
Obtener campaña por ID |
— |
| PATCH |
/campaigns/{id}/status |
Cambiar estado (DRAFT→ACTIVE→PAUSED) |
JWT |
| DELETE |
/campaigns/{id} |
Archivar / eliminar campaña |
JWT |
Modelos de datos — @Serializable
Campaign
idString
nameString
channelCampaignChannel
budgetEurDouble
statusCampaignStatus
createdAtInstant
ownerIdString
ApiResponse<T>
successBoolean
dataT?
errorString?
ApiResponse.ok(data)
ApiResponse.error(msg)
PaginatedResponse<T>
dataList<T>
totalLong
pageInt
limitInt
Enums
CampaignChannel
EMAIL · SOCIAL · SEARCH · DISPLAY
CampaignStatus
DRAFT · ACTIVE · PAUSED · ARCHIVED
Inyección de dependencias — Koin
val appModule = module {
// Repositorios — interfaces → implementaciones
single<CampaignRepository> { InMemoryCampaignRepository() }
single<UserRepository> { InMemoryUserRepository() }
// Servicios — inyectan repositorios automáticamente
single { CampaignService(get()) }
single { AuthService(get(), get<AppConfig>()) }
single { AppConfig(jwtSecret = "super-secret-key-prod") }
}
// En routes — inyección lazy
fun Route.campaignRoutes() {
val campaignService by inject<CampaignService>()
// ...
}
Manejo de errores — StatusPages
| Excepción |
HTTP Status |
Mensaje |
| AuthenticationException |
401 |
Autenticación requerida |
| AuthorizationException |
403 |
Acceso denegado |
| NotFoundException |
404 |
Recurso no encontrado |
| IllegalArgumentException |
400 |
cause.message (validación) |
| Throwable (catch-all) |
500 |
Error interno del servidor |