@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,287 lines (1,125 loc) • 38 kB
JavaScript
"use strict";
/**
* Oak Framework Template Generator
* A middleware framework for Deno's native HTTP server
*/
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.OakGenerator = void 0;
const deno_base_generator_1 = require("./deno-base-generator");
const fs_1 = require("fs");
const path = __importStar(require("path"));
class OakGenerator extends deno_base_generator_1.DenoBackendGenerator {
constructor() {
super('Oak');
}
async generateFrameworkFiles(projectPath, options) {
// Generate main application
await this.generateMainApp(projectPath, options);
// Generate app setup
await this.generateApp(projectPath);
// Generate routes
await this.generateRoutes(projectPath);
// Generate controllers
await this.generateControllers(projectPath);
// Generate middleware
await this.generateMiddleware(projectPath);
// Generate services
await this.generateServices(projectPath);
// Generate models
await this.generateModels(projectPath);
// Generate database
await this.generateDatabase(projectPath);
// Generate config
await this.generateConfig(projectPath, options);
// Generate utilities
await this.generateUtilities(projectPath);
// Generate types
await this.generateTypes(projectPath);
// Update deps.ts with Oak specific imports
await this.updateDeps(projectPath);
}
async generateMainApp(projectPath, options) {
const mainContent = `import { Application } from "https://deno.land/x/oak@v12.6.2/mod.ts";
import { config } from "https://deno.land/x/dotenv@v3.2.2/mod.ts";
import { log } from "./src/utils/logger.ts";
import { errorHandler } from "./src/middleware/error.ts";
import { corsMiddleware } from "./src/middleware/cors.ts";
import { loggerMiddleware } from "./src/middleware/logger.ts";
import { rateLimitMiddleware } from "./src/middleware/rateLimit.ts";
import { router } from "./src/routes/index.ts";
import { connectDB, closeDB } from "./src/config/database.ts";
import { connectRedis } from "./src/config/redis.ts";
// Load environment variables
const env = config();
// Create Oak application
const app = new Application();
// Get port from environment
const PORT = parseInt(Deno.env.get("PORT") || "8000");
const HOST = Deno.env.get("HOST") || "0.0.0.0";
// Apply global error handler
app.use(errorHandler);
// Apply middleware
app.use(corsMiddleware);
app.use(loggerMiddleware);
app.use(rateLimitMiddleware);
// Apply routes
app.use(router.routes());
app.use(router.allowedMethods());
// Handle 404
app.use((ctx) => {
ctx.response.status = 404;
ctx.response.body = {
error: {
code: "NOT_FOUND",
message: "The requested resource was not found",
},
};
});
// Database connection
let dbClient;
let redisClient;
// Startup
app.addEventListener("listen", ({ hostname, port, secure }) => {
log.info(
\`Server listening on \${secure ? "https://" : "http://"}\${hostname ?? "localhost"}:\${port}\`
);
});
// Graceful shutdown
const abortController = new AbortController();
Deno.addSignalListener("SIGINT", async () => {
log.info("Shutting down server...");
abortController.abort();
// Close database connections
if (dbClient) {
await closeDB(dbClient);
}
if (redisClient) {
redisClient.close();
}
Deno.exit(0);
});
// Initialize connections and start server
async function start() {
try {
// Connect to database
dbClient = await connectDB();
log.info("Database connected successfully");
// Connect to Redis
redisClient = await connectRedis();
log.info("Redis connected successfully");
// Start server
await app.listen({
hostname: HOST,
port: PORT,
signal: abortController.signal
});
} catch (error) {
log.error("Failed to start server:", error);
Deno.exit(1);
}
}
// Start the application
if (import.meta.main) {
start();
}
export { app };
`;
await fs_1.promises.writeFile(path.join(projectPath, 'main.ts'), mainContent);
}
async generateApp(projectPath) {
const appContent = `import { Application, Context, State } from "https://deno.land/x/oak@v12.6.2/mod.ts";
import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
import { Redis } from "https://deno.land/x/redis@v0.32.1/mod.ts";
// Extended state interface
export interface AppState extends State {
db: Pool;
redis: Redis;
user?: {
id: string;
email: string;
role: string;
};
}
// Extended context
export type AppContext = Context<AppState>;
// Helper to create typed middleware
export type AppMiddleware = (
ctx: AppContext,
next: () => Promise<unknown>
) => Promise<unknown> | unknown;
// Create application instance
export function createApp(): Application<AppState> {
return new Application<AppState>();
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'app.ts'), appContent);
}
async generateRoutes(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'routes'), { recursive: true });
// Main router
const indexRouterContent = `import { Router } from "https://deno.land/x/oak@v12.6.2/mod.ts";
import { AppState } from "../app.ts";
import authRouter from "./auth.ts";
import userRouter from "./users.ts";
import healthRouter from "./health.ts";
const router = new Router<AppState>();
// Mount sub-routers
router.use("/health", healthRouter.routes());
router.use("/api/v1/auth", authRouter.routes());
router.use("/api/v1/users", userRouter.routes());
// Root route
router.get("/", (ctx) => {
ctx.response.body = {
message: "Welcome to ${this.config.framework} API",
version: "1.0.0",
endpoints: {
health: "/health",
auth: "/api/v1/auth",
users: "/api/v1/users",
},
};
});
export { router };
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'routes', 'index.ts'), indexRouterContent);
// Health router
const healthRouterContent = `import { Router } from "https://deno.land/x/oak@v12.6.2/mod.ts";
import { AppState } from "../app.ts";
import * as healthController from "../controllers/health.ts";
const router = new Router<AppState>();
router.get("/", healthController.checkHealth);
router.get("/ready", healthController.checkReadiness);
router.get("/live", healthController.checkLiveness);
export default router;
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'routes', 'health.ts'), healthRouterContent);
// Auth router
const authRouterContent = `import { Router } from "https://deno.land/x/oak@v12.6.2/mod.ts";
import { AppState } from "../app.ts";
import * as authController from "../controllers/auth.ts";
import { validateRequest } from "../middleware/validation.ts";
import { registerSchema, loginSchema } from "../models/auth.ts";
const router = new Router<AppState>();
router.post("/register", validateRequest(registerSchema), authController.register);
router.post("/login", validateRequest(loginSchema), authController.login);
router.post("/refresh", authController.refreshToken);
router.post("/logout", authController.logout);
export default router;
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'routes', 'auth.ts'), authRouterContent);
// User router
const userRouterContent = `import { Router } from "https://deno.land/x/oak@v12.6.2/mod.ts";
import { AppState } from "../app.ts";
import * as userController from "../controllers/users.ts";
import { authenticate } from "../middleware/auth.ts";
import { authorize } from "../middleware/authorize.ts";
import { validateRequest } from "../middleware/validation.ts";
import { updateUserSchema } from "../models/user.ts";
const router = new Router<AppState>();
// Protected routes
router.use(authenticate);
router.get("/me", userController.getCurrentUser);
router.put("/me", validateRequest(updateUserSchema), userController.updateCurrentUser);
router.get("/", authorize(["admin"]), userController.listUsers);
router.get("/:id", userController.getUser);
router.put("/:id", authorize(["admin"]), validateRequest(updateUserSchema), userController.updateUser);
router.delete("/:id", authorize(["admin"]), userController.deleteUser);
export default router;
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'routes', 'users.ts'), userRouterContent);
}
async generateControllers(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'controllers'), { recursive: true });
// Health controller
const healthControllerContent = `import { AppContext } from "../app.ts";
export async function checkHealth(ctx: AppContext) {
ctx.response.body = {
status: "healthy",
timestamp: new Date().toISOString(),
service: "oak-api",
version: "1.0.0",
};
}
export async function checkReadiness(ctx: AppContext) {
const checks: Record<string, string> = {};
// Check database
try {
await ctx.state.db.connect();
checks.database = "ready";
} catch {
checks.database = "not ready";
}
// Check Redis
try {
await ctx.state.redis.ping();
checks.redis = "ready";
} catch {
checks.redis = "not ready";
}
const allReady = Object.values(checks).every((status) => status === "ready");
ctx.response.status = allReady ? 200 : 503;
ctx.response.body = {
status: allReady ? "ready" : "not ready",
checks,
};
}
export async function checkLiveness(ctx: AppContext) {
ctx.response.body = {
status: "alive",
timestamp: new Date().toISOString(),
};
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'controllers', 'health.ts'), healthControllerContent);
// Auth controller
const authControllerContent = `import { AppContext } from "../app.ts";
import * as authService from "../services/auth.ts";
import { CreateUserDto, LoginDto } from "../models/auth.ts";
import { log } from "../utils/logger.ts";
export async function register(ctx: AppContext) {
try {
const body = await ctx.request.body({ type: "json" }).value as CreateUserDto;
const result = await authService.register(body, ctx.state.db);
ctx.response.status = 201;
ctx.response.body = {
message: "User registered successfully",
data: result,
};
} catch (error) {
log.error("Registration error:", error);
if (error.message.includes("already exists")) {
ctx.response.status = 409;
ctx.response.body = {
error: {
code: "USER_EXISTS",
message: "User with this email already exists",
},
};
} else {
throw error;
}
}
}
export async function login(ctx: AppContext) {
try {
const body = await ctx.request.body({ type: "json" }).value as LoginDto;
const result = await authService.login(body, ctx.state.db);
ctx.response.body = {
message: "Login successful",
data: result,
};
} catch (error) {
log.error("Login error:", error);
ctx.response.status = 401;
ctx.response.body = {
error: {
code: "INVALID_CREDENTIALS",
message: "Invalid email or password",
},
};
}
}
export async function refreshToken(ctx: AppContext) {
try {
const { refreshToken } = await ctx.request.body({ type: "json" }).value;
const result = await authService.refreshToken(refreshToken);
ctx.response.body = {
message: "Token refreshed successfully",
data: result,
};
} catch (error) {
log.error("Token refresh error:", error);
ctx.response.status = 401;
ctx.response.body = {
error: {
code: "INVALID_TOKEN",
message: "Invalid or expired refresh token",
},
};
}
}
export async function logout(ctx: AppContext) {
// In a real app, you might want to blacklist the token
ctx.response.body = {
message: "Logged out successfully",
};
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'controllers', 'auth.ts'), authControllerContent);
// User controller
const userControllerContent = `import { AppContext } from "../app.ts";
import * as userService from "../services/user.ts";
import { UpdateUserDto } from "../models/user.ts";
import { log } from "../utils/logger.ts";
export async function getCurrentUser(ctx: AppContext) {
const userId = ctx.state.user!.id;
const user = await userService.getUserById(userId, ctx.state.db);
ctx.response.body = {
data: user,
};
}
export async function updateCurrentUser(ctx: AppContext) {
const userId = ctx.state.user!.id;
const body = await ctx.request.body({ type: "json" }).value as UpdateUserDto;
const updatedUser = await userService.updateUser(userId, body, ctx.state.db);
ctx.response.body = {
message: "User updated successfully",
data: updatedUser,
};
}
export async function listUsers(ctx: AppContext) {
const page = parseInt(ctx.request.url.searchParams.get("page") || "1");
const limit = parseInt(ctx.request.url.searchParams.get("limit") || "10");
const search = ctx.request.url.searchParams.get("search") || undefined;
const result = await userService.listUsers({ page, limit, search }, ctx.state.db);
ctx.response.body = {
data: result.users,
pagination: {
page,
limit,
total: result.total,
pages: Math.ceil(result.total / limit),
},
};
}
export async function getUser(ctx: AppContext) {
const userId = ctx.params.id;
const user = await userService.getUserById(userId, ctx.state.db);
if (!user) {
ctx.response.status = 404;
ctx.response.body = {
error: {
code: "USER_NOT_FOUND",
message: "User not found",
},
};
return;
}
ctx.response.body = {
data: user,
};
}
export async function updateUser(ctx: AppContext) {
const userId = ctx.params.id;
const body = await ctx.request.body({ type: "json" }).value as UpdateUserDto;
const updatedUser = await userService.updateUser(userId, body, ctx.state.db);
if (!updatedUser) {
ctx.response.status = 404;
ctx.response.body = {
error: {
code: "USER_NOT_FOUND",
message: "User not found",
},
};
return;
}
ctx.response.body = {
message: "User updated successfully",
data: updatedUser,
};
}
export async function deleteUser(ctx: AppContext) {
const userId = ctx.params.id;
const deleted = await userService.deleteUser(userId, ctx.state.db);
if (!deleted) {
ctx.response.status = 404;
ctx.response.body = {
error: {
code: "USER_NOT_FOUND",
message: "User not found",
},
};
return;
}
ctx.response.status = 204;
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'controllers', 'users.ts'), userControllerContent);
}
async generateMiddleware(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'middleware'), { recursive: true });
// Error handling middleware
const errorMiddlewareContent = `import { AppContext } from "../app.ts";
import { log } from "../utils/logger.ts";
export async function errorHandler(ctx: AppContext, next: () => Promise<unknown>) {
try {
await next();
} catch (error) {
log.error("Unhandled error:", error);
const status = error.status || 500;
const message = error.message || "Internal server error";
ctx.response.status = status;
ctx.response.body = {
error: {
code: "INTERNAL_ERROR",
message,
...(Deno.env.get("ENV") === "development" && { stack: error.stack }),
},
};
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'middleware', 'error.ts'), errorMiddlewareContent);
// CORS middleware
const corsMiddlewareContent = `import { AppContext } from "../app.ts";
export async function corsMiddleware(ctx: AppContext, next: () => Promise<unknown>) {
const origin = ctx.request.headers.get("Origin") || "*";
const allowedOrigins = Deno.env.get("CORS_ORIGIN")?.split(",") || ["*"];
if (allowedOrigins.includes("*") || allowedOrigins.includes(origin)) {
ctx.response.headers.set("Access-Control-Allow-Origin", origin);
}
ctx.response.headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS"
);
ctx.response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
);
ctx.response.headers.set("Access-Control-Allow-Credentials", "true");
if (ctx.request.method === "OPTIONS") {
ctx.response.status = 204;
return;
}
await next();
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'middleware', 'cors.ts'), corsMiddlewareContent);
// Logger middleware
const loggerMiddlewareContent = `import { AppContext } from "../app.ts";
import { log } from "../utils/logger.ts";
export async function loggerMiddleware(ctx: AppContext, next: () => Promise<unknown>) {
const start = Date.now();
const { method, url } = ctx.request;
await next();
const ms = Date.now() - start;
const status = ctx.response.status;
log.info(\`\${method} \${url.pathname} - \${status} - \${ms}ms\`);
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'middleware', 'logger.ts'), loggerMiddlewareContent);
// Rate limit middleware
const rateLimitMiddlewareContent = `import { AppContext } from "../app.ts";
const requests = new Map<string, { count: number; resetTime: number }>();
export async function rateLimitMiddleware(ctx: AppContext, next: () => Promise<unknown>) {
const ip = ctx.request.ip;
const now = Date.now();
const windowMs = 60 * 1000; // 1 minute
const maxRequests = 100;
const userRequests = requests.get(ip);
if (!userRequests || now > userRequests.resetTime) {
requests.set(ip, {
count: 1,
resetTime: now + windowMs,
});
} else {
userRequests.count++;
if (userRequests.count > maxRequests) {
ctx.response.status = 429;
ctx.response.body = {
error: {
code: "RATE_LIMIT_EXCEEDED",
message: "Too many requests, please try again later",
},
};
return;
}
}
await next();
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'middleware', 'rateLimit.ts'), rateLimitMiddlewareContent);
// Auth middleware
const authMiddlewareContent = `import { AppContext } from "../app.ts";
import { verifyJWT } from "../utils/jwt.ts";
import { log } from "../utils/logger.ts";
export async function authenticate(ctx: AppContext, next: () => Promise<unknown>) {
const authorization = ctx.request.headers.get("Authorization");
if (!authorization || !authorization.startsWith("Bearer ")) {
ctx.response.status = 401;
ctx.response.body = {
error: {
code: "UNAUTHORIZED",
message: "Missing or invalid authorization header",
},
};
return;
}
const token = authorization.substring(7);
try {
const payload = await verifyJWT(token);
ctx.state.user = {
id: payload.sub as string,
email: payload.email as string,
role: payload.role as string,
};
await next();
} catch (error) {
log.error("Authentication error:", error);
ctx.response.status = 401;
ctx.response.body = {
error: {
code: "INVALID_TOKEN",
message: "Invalid or expired token",
},
};
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'middleware', 'auth.ts'), authMiddlewareContent);
// Authorization middleware
const authorizeMiddlewareContent = `import { AppContext } from "../app.ts";
export function authorize(roles: string[]) {
return async (ctx: AppContext, next: () => Promise<unknown>) => {
const userRole = ctx.state.user?.role;
if (!userRole || !roles.includes(userRole)) {
ctx.response.status = 403;
ctx.response.body = {
error: {
code: "FORBIDDEN",
message: "Insufficient permissions",
},
};
return;
}
await next();
};
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'middleware', 'authorize.ts'), authorizeMiddlewareContent);
// Validation middleware
const validationMiddlewareContent = `import { AppContext } from "../app.ts";
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
export function validateRequest<T extends z.ZodSchema>(
schema: T
) {
return async (ctx: AppContext, next: () => Promise<unknown>) => {
try {
const body = await ctx.request.body({ type: "json" }).value;
const validated = schema.parse(body);
ctx.request.body = () => ({ type: "json", value: Promise.resolve(validated) });
await next();
} catch (error) {
if (error instanceof z.ZodError) {
ctx.response.status = 400;
ctx.response.body = {
error: {
code: "VALIDATION_ERROR",
message: "Invalid request data",
details: error.errors,
},
};
} else {
throw error;
}
}
};
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'middleware', 'validation.ts'), validationMiddlewareContent);
}
async generateServices(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'services'), { recursive: true });
// Auth service
const authServiceContent = `import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts";
import { CreateUserDto, LoginDto } from "../models/auth.ts";
import { User } from "../models/user.ts";
import { generateJWT, generateRefreshToken } from "../utils/jwt.ts";
import { createUser, getUserByEmail } from "./user.ts";
export async function register(data: CreateUserDto, db: Pool) {
// Check if user exists
const existingUser = await getUserByEmail(data.email, db);
if (existingUser) {
throw new Error("User already exists");
}
// Hash password
const hashedPassword = await bcrypt.hash(data.password);
// Create user
const user = await createUser(
{
...data,
password: hashedPassword,
},
db
);
// Generate tokens
const accessToken = await generateJWT(user);
const refreshToken = await generateRefreshToken(user);
return {
user: {
id: user.id,
email: user.email,
name: user.name,
createdAt: user.createdAt,
},
tokens: {
accessToken,
refreshToken,
},
};
}
export async function login(data: LoginDto, db: Pool) {
// Get user
const user = await getUserByEmail(data.email, db);
if (!user) {
throw new Error("Invalid credentials");
}
// Verify password
const validPassword = await bcrypt.compare(data.password, user.password);
if (!validPassword) {
throw new Error("Invalid credentials");
}
// Generate tokens
const accessToken = await generateJWT(user);
const refreshToken = await generateRefreshToken(user);
return {
user: {
id: user.id,
email: user.email,
name: user.name,
createdAt: user.createdAt,
},
tokens: {
accessToken,
refreshToken,
},
};
}
export async function refreshToken(refreshToken: string) {
// Verify refresh token and get new access token
// Implementation depends on your token strategy
throw new Error("Not implemented");
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'services', 'auth.ts'), authServiceContent);
// User service
const userServiceContent = `import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
import { User, UpdateUserDto } from "../models/user.ts";
import { v4 as uuid } from "https://deno.land/std@0.212.0/uuid/mod.ts";
export async function createUser(
data: { email: string; password: string; name: string },
db: Pool
): Promise<User> {
const client = await db.connect();
try {
const result = await client.queryObject<User>(
\`INSERT INTO users (id, email, password, name, role, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, email, name, role, created_at, updated_at\`,
[
uuid.generate(),
data.email,
data.password,
data.name,
"user",
new Date(),
new Date(),
]
);
return result.rows[0];
} finally {
client.release();
}
}
export async function getUserById(id: string, db: Pool): Promise<User | null> {
const client = await db.connect();
try {
const result = await client.queryObject<User>(
"SELECT id, email, name, role, created_at, updated_at FROM users WHERE id = $1",
[id]
);
return result.rows[0] || null;
} finally {
client.release();
}
}
export async function getUserByEmail(email: string, db: Pool): Promise<User | null> {
const client = await db.connect();
try {
const result = await client.queryObject<User>(
"SELECT * FROM users WHERE email = $1",
[email]
);
return result.rows[0] || null;
} finally {
client.release();
}
}
export async function updateUser(
id: string,
data: UpdateUserDto,
db: Pool
): Promise<User | null> {
const client = await db.connect();
try {
const updates: string[] = [];
const values: any[] = [];
let paramCount = 1;
if (data.name !== undefined) {
updates.push(\`name = $\${paramCount++}\`);
values.push(data.name);
}
if (data.email !== undefined) {
updates.push(\`email = $\${paramCount++}\`);
values.push(data.email);
}
updates.push(\`updated_at = $\${paramCount++}\`);
values.push(new Date());
values.push(id);
const result = await client.queryObject<User>(
\`UPDATE users SET \${updates.join(", ")} WHERE id = $\${paramCount}
RETURNING id, email, name, role, created_at, updated_at\`,
values
);
return result.rows[0] || null;
} finally {
client.release();
}
}
export async function deleteUser(id: string, db: Pool): Promise<boolean> {
const client = await db.connect();
try {
const result = await client.queryObject(
"DELETE FROM users WHERE id = $1",
[id]
);
return result.rowCount > 0;
} finally {
client.release();
}
}
export async function listUsers(
params: { page: number; limit: number; search?: string },
db: Pool
): Promise<{ users: User[]; total: number }> {
const client = await db.connect();
try {
const offset = (params.page - 1) * params.limit;
let query = "SELECT id, email, name, role, created_at, updated_at FROM users";
let countQuery = "SELECT COUNT(*) FROM users";
const queryParams: any[] = [];
const countParams: any[] = [];
if (params.search) {
query += " WHERE name ILIKE $1 OR email ILIKE $1";
countQuery += " WHERE name ILIKE $1 OR email ILIKE $1";
queryParams.push(\`%\${params.search}%\`);
countParams.push(\`%\${params.search}%\`);
}
query += \` ORDER BY created_at DESC LIMIT \${params.limit} OFFSET \${offset}\`;
const [users, count] = await Promise.all([
client.queryObject<User>(query, queryParams),
client.queryObject<{ count: number }>(countQuery, countParams),
]);
return {
users: users.rows,
total: parseInt(count.rows[0].count.toString()),
};
} finally {
client.release();
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'services', 'user.ts'), userServiceContent);
}
async generateModels(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'models'), { recursive: true });
// User model
const userModelContent = `import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
export interface User {
id: string;
email: string;
password: string;
name: string;
role: string;
createdAt: Date;
updatedAt: Date;
}
export const updateUserSchema = z.object({
email: z.string().email().optional(),
name: z.string().min(1).max(100).optional(),
});
export type UpdateUserDto = z.infer<typeof updateUserSchema>;
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'models', 'user.ts'), userModelContent);
// Auth model
const authModelContent = `import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
export const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(100),
name: z.string().min(1).max(100),
});
export const loginSchema = z.object({
email: z.string().email(),
password: z.string(),
});
export type CreateUserDto = z.infer<typeof registerSchema>;
export type LoginDto = z.infer<typeof loginSchema>;
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'models', 'auth.ts'), authModelContent);
}
async generateDatabase(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'config'), { recursive: true });
// Database config
const databaseContent = `import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
import { log } from "../utils/logger.ts";
let pool: Pool | null = null;
export async function connectDB(): Promise<Pool> {
if (pool) {
return pool;
}
const databaseUrl = Deno.env.get("DATABASE_URL");
if (!databaseUrl) {
throw new Error("DATABASE_URL environment variable is not set");
}
// Parse database URL
const url = new URL(databaseUrl);
pool = new Pool({
user: url.username,
password: url.password,
database: url.pathname.slice(1),
hostname: url.hostname,
port: url.port ? parseInt(url.port) : 5432,
}, 10); // Max 10 connections
// Test connection
try {
const client = await pool.connect();
await client.queryArray("SELECT 1");
client.release();
log.info("Database connected successfully");
} catch (error) {
log.error("Failed to connect to database:", error);
throw error;
}
return pool;
}
export async function closeDB(pool: Pool): Promise<void> {
await pool.end();
log.info("Database connection closed");
}
// Database migrations
export async function runMigrations(pool: Pool): Promise<void> {
const client = await pool.connect();
try {
// Create users table
await client.queryArray(\`
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
role VARCHAR(50) DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
\`);
// Create indexes
await client.queryArray(\`
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
\`);
log.info("Database migrations completed");
} finally {
client.release();
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'config', 'database.ts'), databaseContent);
// Redis config
const redisContent = `import { connect } from "https://deno.land/x/redis@v0.32.1/mod.ts";
import { log } from "../utils/logger.ts";
export async function connectRedis() {
const redisUrl = Deno.env.get("REDIS_URL") || "redis://localhost:6379";
try {
const redis = await connect({
hostname: new URL(redisUrl).hostname,
port: parseInt(new URL(redisUrl).port || "6379"),
});
// Test connection
await redis.ping();
log.info("Redis connected successfully");
return redis;
} catch (error) {
log.error("Failed to connect to Redis:", error);
throw error;
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'config', 'redis.ts'), redisContent);
}
async generateConfig(projectPath, options) {
// Environment config is handled by deps.ts and dotenv
const envExampleContent = `# Server
PORT=8000
HOST=0.0.0.0
ENV=development
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/${options.name}_db
# Redis
REDIS_URL=redis://localhost:6379
# JWT
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_EXPIRES_IN=15m
REFRESH_TOKEN_EXPIRES_IN=7d
# CORS
CORS_ORIGIN=*
# Logging
LOG_LEVEL=info
`;
await fs_1.promises.writeFile(path.join(projectPath, '.env.example'), envExampleContent);
}
async generateUtilities(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'utils'), { recursive: true });
// Logger utility
const loggerContent = `import * as log from "https://deno.land/std@0.212.0/log/mod.ts";
const logLevel = Deno.env.get("LOG_LEVEL") || "INFO";
await log.setup({
handlers: {
console: new log.handlers.ConsoleHandler(logLevel as log.LevelName, {
formatter: (logRecord) => {
const timestamp = new Date().toISOString();
return \`[\${timestamp}] [\${logRecord.levelName}] \${logRecord.msg}\`;
},
}),
},
loggers: {
default: {
level: logLevel as log.LevelName,
handlers: ["console"],
},
},
});
export { log };
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'utils', 'logger.ts'), loggerContent);
// JWT utility
const jwtContent = `import { create, verify, getNumericDate } from "https://deno.land/x/djwt@v3.0.1/mod.ts";
import { User } from "../models/user.ts";
const JWT_SECRET = Deno.env.get("JWT_SECRET") || "your-secret-key";
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(JWT_SECRET),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"]
);
export async function generateJWT(user: User): Promise<string> {
const payload = {
sub: user.id,
email: user.email,
role: user.role,
exp: getNumericDate(15 * 60), // 15 minutes
};
return await create({ alg: "HS256", typ: "JWT" }, payload, key);
}
export async function generateRefreshToken(user: User): Promise<string> {
const payload = {
sub: user.id,
type: "refresh",
exp: getNumericDate(7 * 24 * 60 * 60), // 7 days
};
return await create({ alg: "HS256", typ: "JWT" }, payload, key);
}
export async function verifyJWT(token: string) {
return await verify(token, key);
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'utils', 'jwt.ts'), jwtContent);
// Validation utility
const validationContent = `import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
export const paginationSchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(10),
search: z.string().optional(),
});
export const idSchema = z.string().uuid();
export const emailSchema = z.string().email();
export const passwordSchema = z
.string()
.min(8)
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
.regex(/[0-9]/, "Password must contain at least one number");
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'utils', 'validation.ts'), validationContent);
}
async generateTypes(projectPath) {
await fs_1.promises.mkdir(path.join(projectPath, 'src', 'types'), { recursive: true });
// Global types
const typesContent = `// Global type definitions
export interface ApiResponse<T = any> {
message?: string;
data?: T;
error?: {
code: string;
message: string;
details?: any;
};
}
export interface PaginationParams {
page: number;
limit: number;
search?: string;
}
export interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
pages: number;
};
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'types', 'index.ts'), typesContent);
}
async updateDeps(projectPath) {
const depsContent = await fs_1.promises.readFile(path.join(projectPath, 'deps.ts'), 'utf-8');
const oakDeps = `
// Oak framework
export {
Application,
Router,
Context,
State,
helpers,
Status,
} from "https://deno.land/x/oak@v12.6.2/mod.ts";
export type { Middleware, Next } from "https://deno.land/x/oak@v12.6.2/mod.ts";
`;
await fs_1.promises.writeFile(path.join(projectPath, 'deps.ts'), depsContent + oakDeps);
}
}
exports.OakGenerator = OakGenerator;