Created
May 5, 2025 15:19
-
-
Save mwibutsa/24173b7587825a9021233c48325f2d0c to your computer and use it in GitHub Desktop.
Model, Repository, Service, Controller, Routers, and Docs file creation script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from pathlib import Path | |
import subprocess | |
INDEX_FILE: str = 'index.ts' | |
def write_file(path: Path, content: str): | |
path.parent.mkdir(parents=True, exist_ok=True) | |
with path.open("w", encoding="utf-8") as f: | |
f.write(content.strip() + "\n") | |
def pascal_case(name: str): | |
return ''.join(part.capitalize() for part in name.split('_')) | |
def update_database_models(model_name: str): | |
models_file_path = Path('src/api/v1/database/models.ts') | |
import_line = f"export type T{model_name.capitalize()}Model = typeof prismaClient.{model_name};" | |
export_line = f"export const {model_name.capitalize()} = prismaClient.{model_name};" | |
lines = models_file_path.read_text(encoding='utf-8').splitlines() | |
if import_line not in lines: | |
lines.append(import_line) | |
if export_line not in lines: | |
lines.append(export_line) | |
models_file_path.write_text('\n'.join(lines) + '\n') | |
def update_router_index(router_dir: Path, name: str): | |
index_path = router_dir / INDEX_FILE | |
import_line = f"import {name}Router from './{name}.router';" | |
use_line = f"v1Router.use('/{name}s', {name}Router);" | |
lines = index_path.read_text(encoding="utf-8").splitlines() | |
if import_line not in lines: | |
lines.insert(0, import_line) | |
for i, line in enumerate(lines): | |
if "export default v1Router" in line: | |
if use_line not in lines: | |
lines.insert(i, use_line) | |
break | |
index_path.write_text('\n'.join(lines) + '\n') | |
def update_docs_index(docs_index: Path, name: str): | |
import_line = f"import {{ {name}Documentation }} from './{name}';" | |
documentation_entry = f"{name}Documentation," | |
with docs_index.open("r", encoding="utf-8") as f: | |
lines = f.readlines() | |
# Add import if not present | |
if import_line + "\n" not in lines: | |
# Find the last import to insert after it | |
last_import_idx = 0 | |
for i, line in enumerate(lines): | |
if line.strip().startswith("import "): | |
last_import_idx = i | |
lines.insert(last_import_idx + 1, import_line + "\n") | |
# Insert documentation entry above the closing array bracket | |
for i, line in enumerate(lines): | |
if "buildOpenApiDocument([" in line: | |
start_idx = i | |
break | |
else: | |
raise FileNotFoundError("Could not find buildOpenApiDocument([") | |
# Find closing ]) | |
for j in range(start_idx, len(lines)): | |
if lines[j].strip() == "]);": | |
# Check if entry already exists | |
if documentation_entry not in lines[start_idx:j]: | |
lines.insert(j, f" {documentation_entry}\n") | |
break | |
docs_index.write_text(''.join(lines)) | |
def main(): | |
if len(sys.argv) < 2: | |
print("Usage: python new_feature.py <FeatureName>") | |
sys.exit(1) | |
raw_name = sys.argv[1] | |
name = raw_name.lower() | |
class_name = pascal_case(raw_name) | |
base_dir = Path("src/api/v1") | |
# Repositories | |
repo_path = base_dir / "repositories" / f"{name}.repository.ts" | |
repo_code = f""" | |
import {{T{class_name}Model,{class_name}}} from '@/api/v1/database/models' | |
export class {class_name}Repository {{ | |
constructor(private readonly model: T{class_name}Model) {{}} | |
}} | |
export const {name}Repository = new {class_name}Repository({class_name}); | |
export default {name}Repository; | |
""" | |
model_name = f"{class_name[0].lower()}{class_name[1:]}" | |
update_database_models(model_name) | |
write_file(repo_path, repo_code) | |
# Services | |
service_path = base_dir / "services" / f"{name}.service.ts" | |
service_code = f"""export class {class_name}Service {{ | |
}} | |
export const {name}Service = new {class_name}Service(); | |
export default {name}Service; | |
""" | |
write_file(service_path, service_code) | |
# Controllers | |
controller_path = base_dir / "controllers" / f"{name}.controller.ts" | |
controller_code = f"""export class {class_name}Controller {{ | |
}} | |
export const {name}Controller = new {class_name}Controller(); | |
export default {name}Controller; | |
""" | |
write_file(controller_path, controller_code) | |
# Router | |
router_path = base_dir / "router" / f"{name}.router.ts" | |
router_code = f"""import {{ Router }} from 'express'; | |
const {name}Router = Router(); | |
export default {name}Router; | |
""" | |
write_file(router_path, router_code) | |
update_router_index(base_dir / "router", name) | |
# Validations | |
validation_path = base_dir / "validations" / f"{name}.validations.ts" | |
validation_code = """import { Joi, celebrate } from 'celebrate'; | |
""" | |
write_file(validation_path, validation_code) | |
# OpenAPI Docs | |
docs_module_dir = base_dir / "docs" / name | |
paths_code = f"""import {{ OpenAPIV3 }} from 'openapi-types'; | |
export const {name}Paths: OpenAPIV3.PathsObject = {{ | |
'/api/v1/{name}': {{}} | |
}}; | |
""" | |
schemas_code = f"""import {{ OpenAPIV3 }} from 'openapi-types'; | |
export const {name}Schemas: Record<string, OpenAPIV3.SchemaObject> = {{}}; | |
""" | |
docs_index_code = f"""import {{ {name}Schemas }} from './{name}.schemas'; | |
import {{ {name}Paths }} from './{name}.paths'; | |
import {{ ModuleDocumentation }} from '@/config/swagger'; | |
export const {name}Documentation: ModuleDocumentation = {{ | |
paths: {name}Paths, | |
schemas: {name}Schemas, | |
}}; | |
""" | |
write_file(docs_module_dir / f"{name}.paths.ts", paths_code) | |
write_file(docs_module_dir / f"{name}.schemas.ts", schemas_code) | |
write_file(docs_module_dir / INDEX_FILE, docs_index_code) | |
update_docs_index(base_dir / "docs" / INDEX_FILE, name) | |
# Format with yarn | |
subprocess.run(["yarn", "format"]) | |
if __name__ == "__main__": | |
main() |
Comments are disabled for this gist.