@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,134 lines (983 loc) • 38 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlayGenerator = void 0;
const scala_base_generator_1 = require("./scala-base-generator");
const fs_1 = require("fs");
const path = __importStar(require("path"));
class PlayGenerator extends scala_base_generator_1.ScalaBackendGenerator {
constructor() {
super('Play Framework');
}
getFrameworkSettings() {
return `lazy val root = (project in file("."))
.enablePlugins(PlayScala)
.settings(
PlayKeys.playDefaultPort := ${options => options.port || 9000}
)`;
}
getFrameworkPlugins() {
return `addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.9.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
addSbtPlugin("com.github.sbt" % "sbt-digest" % "2.0.0")
addSbtPlugin("com.github.sbt" % "sbt-gzip" % "2.0.0")`;
}
getFrameworkDependencies() {
return `// Play Framework
"com.typesafe.play" %% "play" % "2.9.0",
"com.typesafe.play" %% "play-json" % "2.10.3",
"com.typesafe.play" %% "play-ws" % "2.9.0",
"com.typesafe.play" %% "play-cache" % "2.9.0",
"com.typesafe.play" %% "play-cache-caffeine" % "2.9.0",
"com.typesafe.play" %% "filters-helpers" % "2.9.0",
// Database
"com.typesafe.play" %% "play-slick" % "5.1.0",
"com.typesafe.play" %% "play-slick-evolutions" % "5.1.0",
"com.typesafe.slick" %% "slick" % slickVersion,
"com.typesafe.slick" %% "slick-hikaricp" % slickVersion,
postgresql,
// Redis
"com.github.karelcemus" %% "play-redis" % "2.7.0",
jedis,
// JWT
"com.pauldijou" %% "jwt-play" % "5.0.0",
jwtScala,
// Swagger
"com.iheart" %% "play-swagger" % "0.10.14",
"org.webjars" % "swagger-ui" % "5.10.3",
// Validation
"com.typesafe.play" %% "play-json-joda" % "2.10.0-RC9",
// Metrics
"com.kenshoo" %% "metrics-play" % "2.7.3_0.8.2",
prometheusClient,
prometheusHotspot,
// Testing
"com.typesafe.play" %% "play-test" % "2.9.0" % Test,
"org.scalatestplus.play" %% "scalatestplus-play" % "6.0.0" % Test,
scalaTest,
scalaCheck,
// Logging
logback,
scalaLogging,
// Guice DI
"com.typesafe.play" %% "play-guice" % "2.9.0"`;
}
async generateFrameworkFiles(projectPath, options) {
const appDir = path.join(projectPath, 'app');
await fs_1.promises.mkdir(appDir, { recursive: true });
await this.generateControllers(appDir, options);
await this.generateServices(appDir);
await this.generateRepositories(appDir);
await this.generateModels(appDir);
await this.generateFilters(appDir);
await this.generateUtils(appDir);
await this.generateViews(appDir);
await this.generateConfig(projectPath);
await this.generateRoutes(projectPath);
await this.generateEvolutions(projectPath);
await this.generatePublic(projectPath);
await this.generateTests(projectPath);
}
async generateControllers(appDir, options) {
const controllersDir = path.join(appDir, 'controllers');
await fs_1.promises.mkdir(controllersDir, { recursive: true });
// HomeController.scala
const homeControllerContent = `package controllers
import javax.inject._
import play.api.mvc._
import play.api.libs.json.Json
class HomeController extends BaseController {
def index(): Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
Ok(Json.obj(
"message" -> "Welcome to ${options.name} API",
"version" -> "1.0.0",
"timestamp" -> System.currentTimeMillis()
))
}
def health(): Action[AnyContent] = Action {
Ok(Json.obj(
"status" -> "UP",
"timestamp" -> System.currentTimeMillis()
))
}
}`;
await fs_1.promises.writeFile(path.join(controllersDir, 'HomeController.scala'), homeControllerContent);
// AuthController.scala
const authControllerContent = `package controllers
import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import services.{AuthService, UserService}
import models._
import utils.JsonFormats._
import scala.concurrent.{ExecutionContext, Future}
class AuthController extends BaseController {
def register(): Action[JsValue] = Action.async(parse.json) { implicit request =>
request.body.validate[RegisterRequest].fold(
errors => Future.successful(BadRequest(Json.obj("errors" -> JsError.toJson(errors)))),
registerRequest => {
userService.createUser(registerRequest).map { user =>
val token = authService.generateToken(user)
Created(Json.obj(
"token" -> token,
"user" -> user
))
}.recover {
case _: IllegalArgumentException =>
BadRequest(Json.obj("error" -> "Email already exists"))
case ex =>
InternalServerError(Json.obj("error" -> ex.getMessage))
}
}
)
}
def login(): Action[JsValue] = Action.async(parse.json) { implicit request =>
request.body.validate[LoginRequest].fold(
errors => Future.successful(BadRequest(Json.obj("errors" -> JsError.toJson(errors)))),
loginRequest => {
userService.authenticate(loginRequest.email, loginRequest.password).map {
case Some(user) =>
val token = authService.generateToken(user)
Ok(Json.obj(
"token" -> token,
"user" -> user
))
case None =>
Unauthorized(Json.obj("error" -> "Invalid credentials"))
}
}
)
}
def refreshToken(): Action[JsValue] = Action.async(parse.json) { implicit request =>
request.body.validate[RefreshTokenRequest].fold(
errors => Future.successful(BadRequest(Json.obj("errors" -> JsError.toJson(errors)))),
refreshRequest => {
authService.validateToken(refreshRequest.refreshToken) match {
case Some(userId) =>
userService.findById(userId).map {
case Some(user) =>
val token = authService.generateToken(user)
Ok(Json.obj(
"token" -> token,
"user" -> user
))
case None =>
Unauthorized(Json.obj("error" -> "User not found"))
}
case None =>
Future.successful(Unauthorized(Json.obj("error" -> "Invalid token")))
}
}
)
}
def logout(): Action[AnyContent] = authAction { implicit request =>
// In a real app, you might want to blacklist the token
Ok(Json.obj("message" -> "Logged out successfully"))
}
}`;
await fs_1.promises.writeFile(path.join(controllersDir, 'AuthController.scala'), authControllerContent);
// UserController.scala
const userControllerContent = `package controllers
import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import services.UserService
import models._
import utils.JsonFormats._
import actions.AuthAction
import scala.concurrent.{ExecutionContext, Future}
class UserController extends BaseController {
def getUsers(page: Int, size: Int): Action[AnyContent] = authAction.async { implicit request =>
userService.listUsers(page, size).map { users =>
Ok(Json.toJson(users))
}
}
def getUser(id: Long): Action[AnyContent] = authAction.async { implicit request =>
userService.findById(id).map {
case Some(user) => Ok(Json.toJson(user))
case None => NotFound(Json.obj("error" -> "User not found"))
}
}
def getCurrentUser(): Action[AnyContent] = authAction { implicit request =>
Ok(Json.toJson(request.user))
}
def updateUser(): Action[JsValue] = authAction.async(parse.json) { implicit request =>
request.body.validate[UpdateUserRequest].fold(
errors => Future.successful(BadRequest(Json.obj("errors" -> JsError.toJson(errors)))),
updateRequest => {
userService.updateUser(request.user.id, updateRequest).map { updated =>
Ok(Json.toJson(updated))
}
}
)
}
def deleteUser(id: Long): Action[AnyContent] = authAction.async { implicit request =>
if (request.user.id != id && !request.user.isAdmin) {
Future.successful(Forbidden(Json.obj("error" -> "Insufficient permissions")))
} else {
userService.deleteUser(id).map { _ =>
NoContent
}
}
}
}`;
await fs_1.promises.writeFile(path.join(controllersDir, 'UserController.scala'), userControllerContent);
// WebSocketController.scala
const wsControllerContent = `package controllers
import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.streams.ActorFlow
import akka.actor.ActorSystem
import akka.stream.Materializer
import actors.WebSocketActor
import services.AuthService
class WebSocketController extends BaseController {
def socket: WebSocket = WebSocket.acceptOrResult[JsValue, JsValue] { request =>
request.headers.get("Authorization").flatMap { auth =>
val token = auth.replace("Bearer ", "")
authService.validateToken(token)
} match {
case Some(userId) =>
Right(ActorFlow.actorRef { out =>
WebSocketActor.props(out, userId)
})
case None =>
Left(Unauthorized("Invalid token"))
}
}
}`;
await fs_1.promises.writeFile(path.join(controllersDir, 'WebSocketController.scala'), wsControllerContent);
}
async generateServices(appDir) {
const servicesDir = path.join(appDir, 'services');
await fs_1.promises.mkdir(servicesDir, { recursive: true });
// UserService.scala
const userServiceContent = `package services
import javax.inject._
import models._
import repositories.UserRepository
import org.mindrot.jbcrypt.BCrypt
import scala.concurrent.{ExecutionContext, Future}
class UserService {
def createUser(request: RegisterRequest): Future[User] = {
val hashedPassword = BCrypt.hashpw(request.password, BCrypt.gensalt())
val user = User(
id = 0,
email = request.email,
name = request.name,
passwordHash = hashedPassword,
isAdmin = false,
createdAt = System.currentTimeMillis(),
updatedAt = System.currentTimeMillis()
)
userRepository.create(user)
}
def authenticate(email: String, password: String): Future[Option[User]] = {
userRepository.findByEmail(email).map {
case Some(user) if BCrypt.checkpw(password, user.passwordHash) => Some(user)
case _ => None
}
}
def findById(id: Long): Future[Option[User]] = {
userRepository.findById(id)
}
def findByEmail(email: String): Future[Option[User]] = {
userRepository.findByEmail(email)
}
def listUsers(page: Int, size: Int): Future[PagedResult[User]] = {
for {
users <- userRepository.list(offset = (page - 1) * size, limit = size)
total <- userRepository.count()
} yield PagedResult(users, page, size, total)
}
def updateUser(id: Long, request: UpdateUserRequest): Future[User] = {
userRepository.update(id, request)
}
def deleteUser(id: Long): Future[Unit] = {
userRepository.delete(id)
}
}`;
await fs_1.promises.writeFile(path.join(servicesDir, 'UserService.scala'), userServiceContent);
// AuthService.scala
const authServiceContent = `package services
import javax.inject._
import models.User
import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim}
import play.api.Configuration
import play.api.libs.json._
import scala.util.Try
import java.time.Clock
class AuthService {
private val secret = config.get[String]("jwt.secret")
private val expiration = config.get[Long]("jwt.expirationInSeconds")
private implicit val clock: Clock = Clock.systemUTC
def generateToken(user: User): String = {
val claim = JwtClaim(
subject = Some(user.id.toString),
expiration = Some(System.currentTimeMillis() / 1000 + expiration),
issuedAt = Some(System.currentTimeMillis() / 1000),
content = Json.obj(
"email" -> user.email,
"name" -> user.name,
"isAdmin" -> user.isAdmin
).toString()
)
Jwt.encode(claim, secret, JwtAlgorithm.HS256)
}
def validateToken(token: String): Option[Long] = {
Try {
Jwt.decode(token, secret, Seq(JwtAlgorithm.HS256)).map { claim =>
claim.subject.flatMap(s => Try(s.toLong).toOption)
}.get
}.toOption.flatten
}
def extractUserId(token: String): Option[Long] = {
validateToken(token)
}
}`;
await fs_1.promises.writeFile(path.join(servicesDir, 'AuthService.scala'), authServiceContent);
// CacheService.scala
const cacheServiceContent = `package services
import javax.inject._
import play.api.cache.AsyncCacheApi
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
import scala.reflect.ClassTag
class CacheService {
def get[T: ClassTag](key: String): Future[Option[T]] = {
cache.get[T](key)
}
def set[T](key: String, value: T, expiration: Duration = 1.hour): Future[Unit] = {
cache.set(key, value, expiration)
}
def remove(key: String): Future[Unit] = {
cache.remove(key)
}
def getOrElseUpdate[T: ClassTag](key: String, expiration: Duration = 1.hour)(orElse: => Future[T]): Future[T] = {
cache.get[T](key).flatMap {
case Some(value) => Future.successful(value)
case None =>
orElse.flatMap { value =>
cache.set(key, value, expiration).map(_ => value)
}
}
}
}`;
await fs_1.promises.writeFile(path.join(servicesDir, 'CacheService.scala'), cacheServiceContent);
}
async generateRepositories(appDir) {
const reposDir = path.join(appDir, 'repositories');
await fs_1.promises.mkdir(reposDir, { recursive: true });
// UserRepository.scala
const userRepoContent = `package repositories
import javax.inject._
import models._
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.jdbc.JdbcProfile
import scala.concurrent.{ExecutionContext, Future}
class UserRepository extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
private class UsersTable(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def email = column[String]("email", O.Unique)
def name = column[String]("name")
def passwordHash = column[String]("password_hash")
def isAdmin = column[Boolean]("is_admin", O.Default(false))
def createdAt = column[Long]("created_at")
def updatedAt = column[Long]("updated_at")
def * = (id, email, name, passwordHash, isAdmin, createdAt, updatedAt).mapTo[User]
}
private val users = TableQuery[UsersTable]
def create(user: User): Future[User] = {
val insertQuery = users returning users.map(_.id) into ((user, id) => user.copy(id = id))
db.run(insertQuery += user)
}
def findById(id: Long): Future[Option[User]] = {
db.run(users.filter(_.id === id).result.headOption)
}
def findByEmail(email: String): Future[Option[User]] = {
db.run(users.filter(_.email === email).result.headOption)
}
def list(offset: Int, limit: Int): Future[Seq[User]] = {
db.run(users.drop(offset).take(limit).result)
}
def count(): Future[Long] = {
db.run(users.length.result.map(_.toLong))
}
def update(id: Long, request: UpdateUserRequest): Future[User] = {
val updateQuery = for {
userOpt <- users.filter(_.id === id).result.headOption
updated = userOpt.map { user =>
user.copy(
name = request.name.getOrElse(user.name),
updatedAt = System.currentTimeMillis()
)
}
_ <- users.filter(_.id === id).update(updated.get) if updated.isDefined
} yield updated
db.run(updateQuery.transactionally).map(_.get)
}
def delete(id: Long): Future[Unit] = {
db.run(users.filter(_.id === id).delete).map(_ => ())
}
}`;
await fs_1.promises.writeFile(path.join(reposDir, 'UserRepository.scala'), userRepoContent);
}
async generateModels(appDir) {
const modelsDir = path.join(appDir, 'models');
await fs_1.promises.mkdir(modelsDir, { recursive: true });
const modelsContent = `package models
// Domain models
case class User(
id: Long,
email: String,
name: String,
passwordHash: String,
isAdmin: Boolean = false,
createdAt: Long,
updatedAt: Long
)
// Request models
case class RegisterRequest(
email: String,
name: String,
password: String
)
case class LoginRequest(
email: String,
password: String
)
case class UpdateUserRequest(
name: Option[String] = None
)
case class RefreshTokenRequest(
refreshToken: String
)
// Response models
case class AuthResponse(
token: String,
user: User
)
case class ErrorResponse(
error: String,
timestamp: Long = System.currentTimeMillis()
)
case class PagedResult[T](
data: Seq[T],
page: Int,
size: Int,
total: Long
) {
def totalPages: Int = Math.ceil(total.toDouble / size).toInt
def hasNext: Boolean = page < totalPages
def hasPrevious: Boolean = page > 1
}
// WebSocket models
case class WebSocketMessage(
messageType: String,
payload: play.api.libs.json.JsValue,
timestamp: Long = System.currentTimeMillis()
)`;
await fs_1.promises.writeFile(path.join(modelsDir, 'Models.scala'), modelsContent);
}
async generateFilters(appDir) {
const filtersDir = path.join(appDir, 'filters');
await fs_1.promises.mkdir(filtersDir, { recursive: true });
// LoggingFilter.scala
const loggingFilterContent = `package filters
import javax.inject._
import play.api.mvc._
import play.api.Logging
import scala.concurrent.{ExecutionContext, Future}
class LoggingFilter
extends Filter with Logging {
def apply(nextFilter: RequestHeader => Future[Result])(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
nextFilter(requestHeader).map { result =>
val requestTime = System.currentTimeMillis - startTime
logger.info(s"\${requestHeader.method} \${requestHeader.uri} \${result.header.status} \${requestTime}ms")
result.withHeaders("X-Response-Time" -> requestTime.toString)
}
}
}`;
await fs_1.promises.writeFile(path.join(filtersDir, 'LoggingFilter.scala'), loggingFilterContent);
// Filters.scala
const filtersContent = `package filters
import javax.inject._
import play.api._
import play.api.http.HttpFilters
import play.api.mvc._
import play.filters.cors.CORSFilter
import play.filters.csrf.CSRFFilter
import play.filters.headers.SecurityHeadersFilter
import play.filters.gzip.GzipFilter
class Filters extends HttpFilters {
override val filters: Seq[EssentialFilter] = {
Seq(corsFilter, securityHeadersFilter, gzipFilter, loggingFilter) ++
(if (env.mode == Mode.Prod) Seq(csrfFilter) else Seq.empty)
}
}`;
await fs_1.promises.writeFile(path.join(filtersDir, 'Filters.scala'), filtersContent);
}
async generateUtils(appDir) {
const utilsDir = path.join(appDir, 'utils');
await fs_1.promises.mkdir(utilsDir, { recursive: true });
// JsonFormats.scala
const jsonFormatsContent = `package utils
import play.api.libs.json._
import models._
object JsonFormats {
// User formats
implicit val userFormat: Format[User] = Json.format[User]
implicit val registerRequestFormat: Format[RegisterRequest] = Json.format[RegisterRequest]
implicit val loginRequestFormat: Format[LoginRequest] = Json.format[LoginRequest]
implicit val updateUserRequestFormat: Format[UpdateUserRequest] = Json.format[UpdateUserRequest]
implicit val refreshTokenRequestFormat: Format[RefreshTokenRequest] = Json.format[RefreshTokenRequest]
// Response formats
implicit val authResponseFormat: Format[AuthResponse] = Json.format[AuthResponse]
implicit val errorResponseFormat: Format[ErrorResponse] = Json.format[ErrorResponse]
implicit def pagedResultFormat[T: Format]: Format[PagedResult[T]] = Json.format[PagedResult[T]]
// WebSocket formats
implicit val webSocketMessageFormat: Format[WebSocketMessage] = Json.format[WebSocketMessage]
}`;
await fs_1.promises.writeFile(path.join(utilsDir, 'JsonFormats.scala'), jsonFormatsContent);
// Validators.scala
const validatorsContent = `package utils
import play.api.libs.json._
object Validators {
val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$""".r
def validateEmail(email: String): Boolean = {
emailRegex.findFirstIn(email).isDefined
}
def validatePassword(password: String): Boolean = {
password.length >= 8
}
val emailValidator: Reads[String] = Reads.StringReads.filter(JsonValidationError("Invalid email"))(validateEmail)
val passwordValidator: Reads[String] = Reads.StringReads.filter(JsonValidationError("Password must be at least 8 characters"))(validatePassword)
}`;
await fs_1.promises.writeFile(path.join(utilsDir, 'Validators.scala'), validatorsContent);
}
async generateViews(appDir) {
const viewsDir = path.join(appDir, 'views');
await fs_1.promises.mkdir(viewsDir, { recursive: true });
// swagger.scala.html
const swaggerViewContent = `@()
<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.10.3/swagger-ui.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.10.3/swagger-ui-bundle.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.10.3/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
window.ui = SwaggerUIBundle({
url: "/api-docs/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout"
});
}
</script>
</body>
</html>`;
await fs_1.promises.writeFile(path.join(viewsDir, 'swagger.scala.html'), swaggerViewContent);
}
async generateConfig(projectPath) {
const confDir = path.join(projectPath, 'conf');
await fs_1.promises.mkdir(confDir, { recursive: true });
// application.conf
const appConf = `# This is the main configuration file for the application.
# https://www.playframework.com/documentation/latest/ConfigFile
play {
http.secret.key = "changeme"
http.secret.key = \${?APPLICATION_SECRET}
i18n.langs = ["en"]
modules {
enabled += "play.api.db.DBModule"
enabled += "play.api.db.slick.SlickModule"
enabled += "play.modules.swagger.SwaggerModule"
enabled += "modules.AppModule"
}
filters {
enabled += "play.filters.cors.CORSFilter"
enabled += "play.filters.csrf.CSRFFilter"
enabled += "play.filters.headers.SecurityHeadersFilter"
enabled += "play.filters.gzip.GzipFilter"
enabled += "filters.Filters"
cors {
allowedOrigins = ["http://localhost:3000", "http://localhost:4200"]
allowedOrigins = \${?CORS_ALLOWED_ORIGINS}
allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allowedHttpHeaders = ["Accept", "Content-Type", "Authorization"]
}
csrf {
header.bypassHeaders {
Authorization = "*"
}
}
}
evolutions {
db.default.enabled = true
db.default.autoApply = true
}
}
slick.dbs.default {
profile = "slick.jdbc.PostgresProfile$"
db {
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost:5432/app_db"
url = \${?DATABASE_URL}
user = "postgres"
user = \${?DB_USER}
password = "postgres"
password = \${?DB_PASSWORD}
numThreads = 10
maxConnections = 10
}
}
jwt {
secret = "your-secret-key-here"
secret = \${?JWT_SECRET}
expirationInSeconds = 86400
expirationInSeconds = \${?JWT_EXPIRATION}
}
play.cache.redis {
host = localhost
host = \${?REDIS_HOST}
port = 6379
port = \${?REDIS_PORT}
database = 0
password = null
password = \${?REDIS_PASSWORD}
}
swagger.api {
basepath = "/api"
host = "localhost:9000"
schemes = ["http", "https"]
info {
title = "Play Framework API"
version = "1.0.0"
description = "REST API Documentation"
}
}`;
await fs_1.promises.writeFile(path.join(confDir, 'application.conf'), appConf);
// logback.xml
const logbackXml = `<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
<logger name="play" level="INFO" />
<logger name="application" level="DEBUG" />
<logger name="slick" level="INFO" />
<logger name="com.zaxxer.hikari" level="INFO" />
<root level="WARN">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>`;
await fs_1.promises.writeFile(path.join(confDir, 'logback.xml'), logbackXml);
}
async generateRoutes(projectPath) {
const confDir = path.join(projectPath, 'conf');
const routesContent = `# Routes
# This file defines all application routes (Higher priority routes first)
# https://www.playframework.com/documentation/latest/ScalaRouting
# Home page
GET / controllers.HomeController.index()
GET /health controllers.HomeController.health()
# Authentication
POST /api/auth/register controllers.AuthController.register()
POST /api/auth/login controllers.AuthController.login()
POST /api/auth/refresh controllers.AuthController.refreshToken()
POST /api/auth/logout controllers.AuthController.logout()
# Users
GET /api/users controllers.UserController.getUsers(page: Int ?= 1, size: Int ?= 10)
GET /api/users/me controllers.UserController.getCurrentUser()
GET /api/users/:id controllers.UserController.getUser(id: Long)
PUT /api/users/me controllers.UserController.updateUser()
DELETE /api/users/:id controllers.UserController.deleteUser(id: Long)
# WebSocket
GET /ws controllers.WebSocketController.socket
# Swagger
GET /docs controllers.Assets.at(path="/public", file="swagger-ui/index.html")
GET /api-docs/swagger.json controllers.ApiHelpController.getResources
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)`;
await fs_1.promises.writeFile(path.join(confDir, 'routes'), routesContent);
}
async generateEvolutions(projectPath) {
const evolutionsDir = path.join(projectPath, 'conf/evolutions/default');
await fs_1.promises.mkdir(evolutionsDir, { recursive: true });
const evolution1 = `# Users schema
# --- !Ups
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
is_admin BOOLEAN NOT NULL DEFAULT FALSE,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
# --- !Downs
DROP TABLE IF EXISTS users;`;
await fs_1.promises.writeFile(path.join(evolutionsDir, '1.sql'), evolution1);
}
async generatePublic(projectPath) {
const publicDir = path.join(projectPath, 'public');
await fs_1.promises.mkdir(publicDir, { recursive: true });
// Create directories for static assets
await fs_1.promises.mkdir(path.join(publicDir, 'stylesheets'), { recursive: true });
await fs_1.promises.mkdir(path.join(publicDir, 'javascripts'), { recursive: true });
await fs_1.promises.mkdir(path.join(publicDir, 'images'), { recursive: true });
}
async generateTests(projectPath) {
const testDir = path.join(projectPath, 'test');
await fs_1.promises.mkdir(testDir, { recursive: true });
// ApplicationSpec.scala
const appSpecContent = `import org.scalatestplus.play._
import org.scalatestplus.play.guice._
import play.api.test._
import play.api.test.Helpers._
import play.api.libs.json._
import models._
import utils.JsonFormats._
class ApplicationSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
"Application" should {
"send 404 on a bad request" in {
val request = FakeRequest(GET, "/boum")
val result = route(app, request).get
status(result) mustBe NOT_FOUND
}
"render the index page" in {
val request = FakeRequest(GET, "/")
val result = route(app, request).get
status(result) mustBe OK
contentType(result) mustBe Some("application/json")
}
"return health status" in {
val request = FakeRequest(GET, "/health")
val result = route(app, request).get
status(result) mustBe OK
val json = contentAsJson(result)
(json \\ "status").head.as[String] mustBe "UP"
}
}
"AuthController" should {
"register a new user" in {
val registerRequest = RegisterRequest("test@example.com", "Test User", "password123")
val request = FakeRequest(POST, "/api/auth/register")
.withHeaders("Content-Type" -> "application/json")
.withJsonBody(Json.toJson(registerRequest))
val result = route(app, request).get
status(result) mustBe CREATED
val json = contentAsJson(result)
(json \\ "user" \\ "email").head.as[String] mustBe "test@example.com"
(json \\ "token").head.as[String] must not be empty
}
"login with valid credentials" in {
val loginRequest = LoginRequest("test@example.com", "password123")
val request = FakeRequest(POST, "/api/auth/login")
.withHeaders("Content-Type" -> "application/json")
.withJsonBody(Json.toJson(loginRequest))
val result = route(app, request).get
status(result) mustBe OK
val json = contentAsJson(result)
(json \\ "token").head.as[String] must not be empty
}
"reject invalid credentials" in {
val loginRequest = LoginRequest("test@example.com", "wrongpassword")
val request = FakeRequest(POST, "/api/auth/login")
.withHeaders("Content-Type" -> "application/json")
.withJsonBody(Json.toJson(loginRequest))
val result = route(app, request).get
status(result) mustBe UNAUTHORIZED
}
}
}`;
await fs_1.promises.writeFile(path.join(testDir, 'ApplicationSpec.scala'), appSpecContent);
// Create actions directory
const actionsDir = path.join(projectPath, 'app/actions');
await fs_1.promises.mkdir(actionsDir, { recursive: true });
// AuthAction.scala
const authActionContent = `package actions
import javax.inject._
import play.api.mvc._
import services.{AuthService, UserService}
import models.User
import scala.concurrent.{ExecutionContext, Future}
case class AuthRequest[A](user: User, request: Request[A]) extends WrappedRequest[A](request)
@Singleton
class AuthAction @Inject()(
val parser: BodyParsers.Default,
authService: AuthService,
userService: UserService
)(implicit val executionContext: ExecutionContext) extends ActionBuilder[AuthRequest, AnyContent] {
override def invokeBlock[A](request: Request[A], block: AuthRequest[A] => Future[Result]): Future[Result] = {
request.headers.get("Authorization").flatMap { authHeader =>
val token = authHeader.replace("Bearer ", "")
authService.extractUserId(token)
} match {
case Some(userId) =>
userService.findById(userId).flatMap {
case Some(user) => block(AuthRequest(user, request))
case None => Future.successful(Results.Unauthorized("User not found"))
}
case None =>
Future.successful(Results.Unauthorized("Invalid or missing token"))
}
}
}`;
await fs_1.promises.writeFile(path.join(actionsDir, 'AuthAction.scala'), authActionContent);
// Create actors directory
const actorsDir = path.join(projectPath, 'app/actors');
await fs_1.promises.mkdir(actorsDir, { recursive: true });
// WebSocketActor.scala
const wsActorContent = `package actors
import akka.actor._
import play.api.libs.json._
import models.WebSocketMessage
import utils.JsonFormats._
object WebSocketActor {
def props(out: ActorRef, userId: Long) = Props(new WebSocketActor(out, userId))
}
class WebSocketActor(out: ActorRef, userId: Long) extends Actor {
override def preStart(): Unit = {
context.system.eventStream.subscribe(self, classOf[WebSocketMessage])
sendMessage("connected", Json.obj("userId" -> userId))
}
override def postStop(): Unit = {
context.system.eventStream.unsubscribe(self)
}
def receive: Receive = {
case msg: JsValue =>
handleClientMessage(msg)
case msg: WebSocketMessage =>
out ! Json.toJson(msg)
}
private def handleClientMessage(msg: JsValue): Unit = {
(msg \\ "type").headOption.map(_.as[String]) match {
case Some("ping") =>
sendMessage("pong", Json.obj("timestamp" -> System.currentTimeMillis()))
case Some("broadcast") =>
val payload = (msg \\ "payload").headOption.getOrElse(JsNull)
context.system.eventStream.publish(
WebSocketMessage("broadcast", payload)
)
case _ =>
sendMessage("error", Json.obj("message" -> "Unknown message type"))
}
}
private def sendMessage(messageType: String, payload: JsValue): Unit = {
out ! Json.toJson(WebSocketMessage(messageType, payload))
}
}`;
await fs_1.promises.writeFile(path.join(actorsDir, 'WebSocketActor.scala'), wsActorContent);
// Create modules directory
const modulesDir = path.join(projectPath, 'app/modules');
await fs_1.promises.mkdir(modulesDir, { recursive: true });
// AppModule.scala
const appModuleContent = `package modules
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
class AppModule extends AbstractModule with AkkaGuiceSupport {
override def configure(): Unit = {
// Bind any custom implementations here
}
}`;
await fs_1.promises.writeFile(path.join(modulesDir, 'AppModule.scala'), appModuleContent);
}
}
exports.PlayGenerator = PlayGenerator;
exports.default = PlayGenerator;