stackforge-cli 0.1.4__tar.gz → 0.1.5__tar.gz
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.
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/PKG-INFO +3 -1
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/pyproject.toml +13 -2
- stackforge_cli-0.1.5/src/stackforge/engines/dotnet/api_generator.py +39 -0
- stackforge_cli-0.1.5/src/stackforge/engines/dotnet/config_generator.py +20 -0
- stackforge_cli-0.1.5/src/stackforge/engines/dotnet/docker_generator.py +15 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/engines/dotnet/git_generator.py +7 -56
- stackforge_cli-0.1.5/src/stackforge/engines/dotnet/layers_generator.py +103 -0
- stackforge_cli-0.1.5/src/stackforge/engines/dotnet/program_generator.py +21 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Configurations/Swagger/ApiRoles.cs.tmpl +9 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/ConfigureServices.cs.tmpl +120 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Controllers/BaseApiController.cs.tmpl +24 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Controllers/Main/AccountController.cs.tmpl +68 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/DTOs/AccountDTOs.cs.tmpl +23 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Interface/IJwtService.cs.tmpl +7 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Options/JwtOptions.cs.tmpl +10 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Program.cs.tmpl +45 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Properties/launchSettings.json.tmpl +31 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Services/JwtService.cs.tmpl +41 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Swagger/EnumMemberSchemaFilter.cs.tmpl +25 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/appsettings.Development.json.tmpl +17 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/appsettings.json.tmpl +18 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Behaviors/ValidationBehavior.cs.tmpl +29 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Constants/ErrorMessages.cs.tmpl +10 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Exceptions/AppExceptions.cs.tmpl +12 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Exceptions/NotFoundException.cs.tmpl +10 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Models/ApiResponse.cs.tmpl +15 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Models/PagedList.cs.tmpl +30 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Models/PagedResponse.cs.tmpl +13 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Models/PaginatedResult.cs.tmpl +13 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Models/PagingParams.cs.tmpl +8 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Models/Result.cs.tmpl +30 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/Common/Models/Serialization/IsoDateTime.cs.tmpl +14 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Application/ConfigureServices.cs.tmpl +22 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Dockerfile.tmpl +8 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Domain/Common/BaseEntity.cs.tmpl +9 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Domain/Common/EnumMemberExtensions.cs.tmpl +16 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Domain/Entities/Identity/Role.cs.tmpl +9 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Domain/Entities/Identity/User.cs.tmpl +13 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Domain/Entities/Identity/UserRole.cs.tmpl +10 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Domain/GlobalUsings.cs.tmpl +2 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Infrastructure/ConfigureServices.cs.tmpl +14 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Persistence/Configurations/UserRoleConfiguration.cs.tmpl +24 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Persistence/ConfigureServices.cs.tmpl +18 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Persistence/DataContext.cs.tmpl +36 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Persistence/Seeders/RoleSeeder.cs.tmpl +19 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/Persistence/Seeders/UserSeeder.cs.tmpl +28 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/docker-compose.yml.tmpl +36 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/dot_env.tmpl +10 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/dot_github/workflows/sync-branches.yml.tmpl +29 -0
- stackforge_cli-0.1.5/src/stackforge/templates/dotnet/dot_gitignore.tmpl +15 -0
- stackforge_cli-0.1.5/src/stackforge/templating.py +39 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge_cli.egg-info/PKG-INFO +3 -1
- stackforge_cli-0.1.5/src/stackforge_cli.egg-info/SOURCES.txt +68 -0
- stackforge_cli-0.1.5/src/stackforge_cli.egg-info/requires.txt +4 -0
- stackforge_cli-0.1.5/tests/test_dotnet_scaffold.py +104 -0
- stackforge_cli-0.1.5/tests/test_project_name.py +31 -0
- stackforge_cli-0.1.5/tests/test_templating.py +27 -0
- stackforge_cli-0.1.4/src/stackforge/engines/dotnet/api_generator.py +0 -314
- stackforge_cli-0.1.4/src/stackforge/engines/dotnet/config_generator.py +0 -77
- stackforge_cli-0.1.4/src/stackforge/engines/dotnet/docker_generator.py +0 -64
- stackforge_cli-0.1.4/src/stackforge/engines/dotnet/layers_generator.py +0 -544
- stackforge_cli-0.1.4/src/stackforge/engines/dotnet/program_generator.py +0 -245
- stackforge_cli-0.1.4/src/stackforge_cli.egg-info/SOURCES.txt +0 -22
- stackforge_cli-0.1.4/src/stackforge_cli.egg-info/requires.txt +0 -1
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/README.md +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/setup.cfg +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/__init__.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/engines/dotnet/__init__.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/engines/dotnet/csproj_generator.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/engines/dotnet/scaffolder.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/project_name.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/scaffold.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/ui.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/version.py +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge_cli.egg-info/dependency_links.txt +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge_cli.egg-info/entry_points.txt +0 -0
- {stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stackforge-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A CLI scaffolder for .NET Clean Architecture projects
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: scaffolder,dotnet,clean-architecture,cli
|
|
@@ -11,6 +11,8 @@ Classifier: Environment :: Console
|
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
Requires-Dist: colorama>=0.4.6
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
14
16
|
|
|
15
17
|
# StackForge
|
|
16
18
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "stackforge-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "A CLI scaffolder for .NET Clean Architecture projects"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -20,8 +20,19 @@ dependencies = [
|
|
|
20
20
|
"colorama>=0.4.6",
|
|
21
21
|
]
|
|
22
22
|
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
test = ["pytest>=8"]
|
|
25
|
+
|
|
23
26
|
[project.scripts]
|
|
24
27
|
stackforge = "stackforge.scaffold:main"
|
|
25
28
|
|
|
26
29
|
[tool.setuptools.packages.find]
|
|
27
|
-
where = ["src"]
|
|
30
|
+
where = ["src"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.package-data]
|
|
33
|
+
stackforge = ["templates/**/*.tmpl"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
pythonpath = ["src", "tests"]
|
|
38
|
+
markers = ["dotnet: builds the generated solution with the .NET SDK (slow)"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
3
|
+
from stackforge.templating import render_file
|
|
4
|
+
|
|
5
|
+
ENGINE = "dotnet"
|
|
6
|
+
|
|
7
|
+
API_FOLDERS = [
|
|
8
|
+
"API/Configurations/Swagger",
|
|
9
|
+
"API/Controllers/Main",
|
|
10
|
+
"API/Helpers",
|
|
11
|
+
"API/Interface",
|
|
12
|
+
"API/Options",
|
|
13
|
+
"API/Properties",
|
|
14
|
+
"API/Services",
|
|
15
|
+
"API/Swagger",
|
|
16
|
+
"API/DTOs",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
API_TEMPLATES = [
|
|
20
|
+
"API/Controllers/BaseApiController.cs",
|
|
21
|
+
"API/Options/JwtOptions.cs",
|
|
22
|
+
"API/Interface/IJwtService.cs",
|
|
23
|
+
"API/Services/JwtService.cs",
|
|
24
|
+
"API/Swagger/EnumMemberSchemaFilter.cs",
|
|
25
|
+
"API/Configurations/Swagger/ApiRoles.cs",
|
|
26
|
+
"API/Properties/launchSettings.json",
|
|
27
|
+
"API/DTOs/AccountDTOs.cs",
|
|
28
|
+
"API/Controllers/Main/AccountController.cs",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def generate_api_structure(base: Path, project: str):
|
|
33
|
+
for folder in API_FOLDERS:
|
|
34
|
+
(base / folder).mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
for template in API_TEMPLATES:
|
|
37
|
+
render_file(ENGINE, base, template, project)
|
|
38
|
+
|
|
39
|
+
ui.file_created("API/ structure and files")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
3
|
+
from stackforge.templating import render_file
|
|
4
|
+
|
|
5
|
+
ENGINE = "dotnet"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_env(base: Path, project: str):
|
|
9
|
+
render_file(ENGINE, base, ".env", project)
|
|
10
|
+
ui.file_created(".env")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_appsettings(base: Path, project: str):
|
|
14
|
+
render_file(ENGINE, base, "API/appsettings.json", project)
|
|
15
|
+
ui.file_created("API/appsettings.json")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_appsettings_development(base: Path, project: str):
|
|
19
|
+
render_file(ENGINE, base, "API/appsettings.Development.json", project)
|
|
20
|
+
ui.file_created("API/appsettings.Development.json")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
3
|
+
from stackforge.templating import render_file
|
|
4
|
+
|
|
5
|
+
ENGINE = "dotnet"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_dockerfile(base: Path, project: str = ""):
|
|
9
|
+
render_file(ENGINE, base, "Dockerfile", project)
|
|
10
|
+
ui.file_created("Dockerfile")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_docker_compose(base: Path, project: str):
|
|
14
|
+
render_file(ENGINE, base, "docker-compose.yml", project)
|
|
15
|
+
ui.file_created("docker-compose.yml")
|
{stackforge_cli-0.1.4 → stackforge_cli-0.1.5}/src/stackforge/engines/dotnet/git_generator.py
RENAMED
|
@@ -2,6 +2,9 @@ import uuid
|
|
|
2
2
|
import subprocess
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from stackforge import ui
|
|
5
|
+
from stackforge.templating import render_file
|
|
6
|
+
|
|
7
|
+
ENGINE = "dotnet"
|
|
5
8
|
|
|
6
9
|
def generate_sln(base: Path, project: str):
|
|
7
10
|
layers = ["API", "Application", "Domain", "Infrastructure", "Persistence"]
|
|
@@ -37,65 +40,13 @@ EndGlobal
|
|
|
37
40
|
ui.file_created(f"{project}.sln")
|
|
38
41
|
|
|
39
42
|
|
|
40
|
-
def generate_gitignore(base: Path):
|
|
41
|
-
|
|
42
|
-
bin/
|
|
43
|
-
obj/
|
|
44
|
-
out/
|
|
45
|
-
|
|
46
|
-
# Environment
|
|
47
|
-
.env
|
|
48
|
-
|
|
49
|
-
# VS / Rider
|
|
50
|
-
.vs/
|
|
51
|
-
.idea/
|
|
52
|
-
*.user
|
|
53
|
-
|
|
54
|
-
# Logs
|
|
55
|
-
*.log
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
file_path = base / ".gitignore"
|
|
59
|
-
file_path.write_text(content)
|
|
43
|
+
def generate_gitignore(base: Path, project: str = ""):
|
|
44
|
+
render_file(ENGINE, base, ".gitignore", project)
|
|
60
45
|
ui.file_created(".gitignore")
|
|
61
46
|
|
|
62
47
|
|
|
63
|
-
def generate_github_workflow(base: Path):
|
|
64
|
-
|
|
65
|
-
workflow_dir.mkdir(parents=True, exist_ok=True)
|
|
66
|
-
|
|
67
|
-
content = """name: Sync Environment Branches
|
|
68
|
-
|
|
69
|
-
on:
|
|
70
|
-
push:
|
|
71
|
-
branches:
|
|
72
|
-
- main
|
|
73
|
-
|
|
74
|
-
permissions:
|
|
75
|
-
contents: write
|
|
76
|
-
|
|
77
|
-
jobs:
|
|
78
|
-
sync-branches:
|
|
79
|
-
runs-on: ubuntu-latest
|
|
80
|
-
steps:
|
|
81
|
-
- uses: actions/checkout@v4
|
|
82
|
-
with:
|
|
83
|
-
fetch-depth: 0
|
|
84
|
-
|
|
85
|
-
- name: Create staging branch if missing
|
|
86
|
-
run: |
|
|
87
|
-
if ! git ls-remote --exit-code origin refs/heads/staging; then
|
|
88
|
-
git push origin HEAD:refs/heads/staging
|
|
89
|
-
fi
|
|
90
|
-
|
|
91
|
-
- name: Create development branch if missing
|
|
92
|
-
run: |
|
|
93
|
-
if ! git ls-remote --exit-code origin refs/heads/development; then
|
|
94
|
-
git push origin HEAD:refs/heads/development
|
|
95
|
-
fi
|
|
96
|
-
"""
|
|
97
|
-
file_path = workflow_dir / "sync-branches.yml"
|
|
98
|
-
file_path.write_text(content.lstrip())
|
|
48
|
+
def generate_github_workflow(base: Path, project: str = ""):
|
|
49
|
+
render_file(ENGINE, base, ".github/workflows/sync-branches.yml", project)
|
|
99
50
|
ui.file_created(".github/workflows/sync-branches.yml")
|
|
100
51
|
|
|
101
52
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
3
|
+
from stackforge.templating import render_file
|
|
4
|
+
|
|
5
|
+
ENGINE = "dotnet"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _generate_layer(base: Path, project: str, layer: str, folders: list[str], templates: list[str]):
|
|
9
|
+
for folder in folders:
|
|
10
|
+
(base / folder).mkdir(parents=True, exist_ok=True)
|
|
11
|
+
|
|
12
|
+
for template in templates:
|
|
13
|
+
render_file(ENGINE, base, template, project)
|
|
14
|
+
|
|
15
|
+
ui.file_created(f"{layer}/ structure and files")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_application_layer(base: Path, project: str):
|
|
19
|
+
_generate_layer(
|
|
20
|
+
base,
|
|
21
|
+
project,
|
|
22
|
+
"Application",
|
|
23
|
+
folders=[
|
|
24
|
+
"Application/Common/Behaviors",
|
|
25
|
+
"Application/Common/Constants",
|
|
26
|
+
"Application/Common/Exceptions",
|
|
27
|
+
"Application/Common/Models/Serialization",
|
|
28
|
+
"Application/Helpers",
|
|
29
|
+
"Application/Interfaces",
|
|
30
|
+
"Application/Mappings",
|
|
31
|
+
"Application/Services",
|
|
32
|
+
],
|
|
33
|
+
templates=[
|
|
34
|
+
"Application/Common/Behaviors/ValidationBehavior.cs",
|
|
35
|
+
"Application/Common/Constants/ErrorMessages.cs",
|
|
36
|
+
"Application/Common/Exceptions/AppExceptions.cs",
|
|
37
|
+
"Application/Common/Exceptions/NotFoundException.cs",
|
|
38
|
+
"Application/Common/Models/ApiResponse.cs",
|
|
39
|
+
"Application/Common/Models/Result.cs",
|
|
40
|
+
"Application/Common/Models/PagingParams.cs",
|
|
41
|
+
"Application/Common/Models/PaginatedResult.cs",
|
|
42
|
+
"Application/Common/Models/PagedList.cs",
|
|
43
|
+
"Application/Common/Models/PagedResponse.cs",
|
|
44
|
+
"Application/Common/Models/Serialization/IsoDateTime.cs",
|
|
45
|
+
],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def generate_domain_layer(base: Path, project: str):
|
|
50
|
+
_generate_layer(
|
|
51
|
+
base,
|
|
52
|
+
project,
|
|
53
|
+
"Domain",
|
|
54
|
+
folders=[
|
|
55
|
+
"Domain/Common",
|
|
56
|
+
"Domain/Entities/Identity",
|
|
57
|
+
"Domain/Enums",
|
|
58
|
+
"Domain/Utils",
|
|
59
|
+
],
|
|
60
|
+
templates=[
|
|
61
|
+
"Domain/Common/BaseEntity.cs",
|
|
62
|
+
"Domain/Common/EnumMemberExtensions.cs",
|
|
63
|
+
"Domain/Entities/Identity/Role.cs",
|
|
64
|
+
"Domain/Entities/Identity/User.cs",
|
|
65
|
+
"Domain/Entities/Identity/UserRole.cs",
|
|
66
|
+
"Domain/GlobalUsings.cs",
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def generate_infrastructure_layer(base: Path, project: str):
|
|
72
|
+
_generate_layer(
|
|
73
|
+
base,
|
|
74
|
+
project,
|
|
75
|
+
"Infrastructure",
|
|
76
|
+
folders=[
|
|
77
|
+
"Infrastructure/BackgroundServices",
|
|
78
|
+
"Infrastructure/Configurations",
|
|
79
|
+
"Infrastructure/Options",
|
|
80
|
+
"Infrastructure/Services",
|
|
81
|
+
],
|
|
82
|
+
templates=[],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def generate_persistence_layer(base: Path, project: str):
|
|
87
|
+
_generate_layer(
|
|
88
|
+
base,
|
|
89
|
+
project,
|
|
90
|
+
"Persistence",
|
|
91
|
+
folders=[
|
|
92
|
+
"Persistence/Configurations",
|
|
93
|
+
"Persistence/Extensions",
|
|
94
|
+
"Persistence/Migrations",
|
|
95
|
+
"Persistence/Seeders",
|
|
96
|
+
],
|
|
97
|
+
templates=[
|
|
98
|
+
"Persistence/DataContext.cs",
|
|
99
|
+
"Persistence/Seeders/RoleSeeder.cs",
|
|
100
|
+
"Persistence/Seeders/UserSeeder.cs",
|
|
101
|
+
"Persistence/Configurations/UserRoleConfiguration.cs",
|
|
102
|
+
],
|
|
103
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
3
|
+
from stackforge.templating import render_file
|
|
4
|
+
|
|
5
|
+
ENGINE = "dotnet"
|
|
6
|
+
|
|
7
|
+
# Layers that get a ConfigureServices.Add<Layer>Services() extension.
|
|
8
|
+
CONFIGURE_SERVICES_LAYERS = {"Application", "Infrastructure", "Persistence", "API"}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_configure_services(layer: str, base: Path, project: str):
|
|
12
|
+
if layer not in CONFIGURE_SERVICES_LAYERS:
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
render_file(ENGINE, base, f"{layer}/ConfigureServices.cs", project)
|
|
16
|
+
ui.file_created(f"{layer}/ConfigureServices.cs")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def generate_program_cs(base: Path, project: str):
|
|
20
|
+
render_file(ENGINE, base, "API/Program.cs", project)
|
|
21
|
+
ui.file_created("API/Program.cs")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
using __PROJECT__.API.Interface;
|
|
2
|
+
using __PROJECT__.API.Options;
|
|
3
|
+
using __PROJECT__.API.Services;
|
|
4
|
+
using __PROJECT__.Domain.Entities.Identity;
|
|
5
|
+
using __PROJECT__.Persistence;
|
|
6
|
+
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
7
|
+
using Microsoft.AspNetCore.Authorization;
|
|
8
|
+
using Microsoft.AspNetCore.Identity;
|
|
9
|
+
using Microsoft.IdentityModel.Tokens;
|
|
10
|
+
using Microsoft.OpenApi.Models;
|
|
11
|
+
using System.Text;
|
|
12
|
+
using System.Text.Json.Serialization;
|
|
13
|
+
|
|
14
|
+
namespace __PROJECT__.API
|
|
15
|
+
{
|
|
16
|
+
public static class ConfigureServices
|
|
17
|
+
{
|
|
18
|
+
public const string CorsPolicy = "Cors";
|
|
19
|
+
|
|
20
|
+
public static IServiceCollection AddApiServices(this IServiceCollection services, IConfiguration configuration)
|
|
21
|
+
{
|
|
22
|
+
services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
|
|
23
|
+
var jwtOptions = new JwtOptions();
|
|
24
|
+
configuration.GetSection("Jwt").Bind(jwtOptions);
|
|
25
|
+
|
|
26
|
+
services.AddControllers()
|
|
27
|
+
.AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
|
28
|
+
|
|
29
|
+
services.AddEndpointsApiExplorer();
|
|
30
|
+
|
|
31
|
+
services.AddSwaggerGen(config =>
|
|
32
|
+
{
|
|
33
|
+
config.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
|
34
|
+
{
|
|
35
|
+
Name = "Authorization",
|
|
36
|
+
Type = SecuritySchemeType.Http,
|
|
37
|
+
Scheme = "bearer",
|
|
38
|
+
BearerFormat = "JWT",
|
|
39
|
+
In = ParameterLocation.Header,
|
|
40
|
+
Description = "Enter your JWT token only (no 'Bearer' prefix needed)"
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
config.AddSecurityRequirement(new OpenApiSecurityRequirement
|
|
44
|
+
{
|
|
45
|
+
{
|
|
46
|
+
new OpenApiSecurityScheme
|
|
47
|
+
{
|
|
48
|
+
Reference = new OpenApiReference
|
|
49
|
+
{
|
|
50
|
+
Type = ReferenceType.SecurityScheme,
|
|
51
|
+
Id = "Bearer"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
Array.Empty<string>()
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Browser origins allowed to call the API. Format: scheme + host (+ port if non-default), no trailing slash.
|
|
60
|
+
// Only add frontends you own: an allowed origin's scripts can call this API from a visitor's browser.
|
|
61
|
+
// Bearer tokens travel in the Authorization header, so credentials (cookies) are not allowed.
|
|
62
|
+
services.AddCors(options =>
|
|
63
|
+
{
|
|
64
|
+
options.AddPolicy(CorsPolicy, policy =>
|
|
65
|
+
{
|
|
66
|
+
policy.AllowAnyMethod()
|
|
67
|
+
.AllowAnyHeader()
|
|
68
|
+
.WithExposedHeaders("WWW-Authenticate", "Retry-After")
|
|
69
|
+
.WithOrigins(
|
|
70
|
+
"http://localhost:3000"
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
services.AddIdentity<User, Role>(options =>
|
|
76
|
+
{
|
|
77
|
+
options.Password.RequireDigit = true;
|
|
78
|
+
options.Password.RequireLowercase = true;
|
|
79
|
+
options.Password.RequireUppercase = true;
|
|
80
|
+
options.Password.RequireNonAlphanumeric = true;
|
|
81
|
+
options.Password.RequiredLength = 8;
|
|
82
|
+
})
|
|
83
|
+
.AddEntityFrameworkStores<DataContext>()
|
|
84
|
+
.AddDefaultTokenProviders();
|
|
85
|
+
|
|
86
|
+
services.AddAuthentication(options =>
|
|
87
|
+
{
|
|
88
|
+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
89
|
+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
90
|
+
})
|
|
91
|
+
.AddJwtBearer(options =>
|
|
92
|
+
{
|
|
93
|
+
options.TokenValidationParameters = new TokenValidationParameters
|
|
94
|
+
{
|
|
95
|
+
ValidateIssuer = true,
|
|
96
|
+
ValidateAudience = true,
|
|
97
|
+
ValidateLifetime = true,
|
|
98
|
+
ValidateIssuerSigningKey = true,
|
|
99
|
+
ValidIssuer = jwtOptions.Issuer,
|
|
100
|
+
ValidAudience = jwtOptions.Audience,
|
|
101
|
+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey))
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Deny by default: endpoints without an explicit policy require an authenticated user.
|
|
106
|
+
// Public endpoints must opt out with [AllowAnonymous].
|
|
107
|
+
services.AddAuthorization(options =>
|
|
108
|
+
{
|
|
109
|
+
options.FallbackPolicy = new AuthorizationPolicyBuilder()
|
|
110
|
+
.RequireAuthenticatedUser()
|
|
111
|
+
.Build();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
//Scopes
|
|
115
|
+
services.AddScoped<IJwtService, JwtService>();
|
|
116
|
+
|
|
117
|
+
return services;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Controllers/BaseApiController.cs.tmpl
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
using Microsoft.AspNetCore.Mvc;
|
|
2
|
+
using System.Security.Claims;
|
|
3
|
+
|
|
4
|
+
namespace __PROJECT__.API.Controllers
|
|
5
|
+
{
|
|
6
|
+
[Route("api/[controller]")]
|
|
7
|
+
[ApiController]
|
|
8
|
+
public class BaseApiController : ControllerBase
|
|
9
|
+
{
|
|
10
|
+
protected Guid GetUserId() => Guid.Parse(User?.FindFirst(ClaimTypes.NameIdentifier)?.Value!);
|
|
11
|
+
|
|
12
|
+
protected string GetCurrentEmail() => User?.FindFirstValue(ClaimTypes.Email)!;
|
|
13
|
+
|
|
14
|
+
protected Guid? GetOrganizationId()
|
|
15
|
+
{
|
|
16
|
+
var claim = User?.FindFirst("OrganizationId");
|
|
17
|
+
|
|
18
|
+
if (claim == null || !Guid.TryParse(claim.Value, out var orgId))
|
|
19
|
+
return null;
|
|
20
|
+
|
|
21
|
+
return orgId;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
stackforge_cli-0.1.5/src/stackforge/templates/dotnet/API/Controllers/Main/AccountController.cs.tmpl
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
using __PROJECT__.API.Controllers;
|
|
2
|
+
using __PROJECT__.API.DTOs;
|
|
3
|
+
using __PROJECT__.API.Interface;
|
|
4
|
+
using __PROJECT__.Domain.Entities.Identity;
|
|
5
|
+
using Microsoft.AspNetCore.Authorization;
|
|
6
|
+
using Microsoft.AspNetCore.Identity;
|
|
7
|
+
using Microsoft.AspNetCore.Mvc;
|
|
8
|
+
|
|
9
|
+
namespace __PROJECT__.API.Controllers.Main
|
|
10
|
+
{
|
|
11
|
+
public class AccountController : BaseApiController
|
|
12
|
+
{
|
|
13
|
+
private readonly UserManager<User> _userManager;
|
|
14
|
+
private readonly SignInManager<User> _signInManager;
|
|
15
|
+
private readonly IJwtService _jwtService;
|
|
16
|
+
|
|
17
|
+
public AccountController(
|
|
18
|
+
UserManager<User> userManager,
|
|
19
|
+
SignInManager<User> signInManager,
|
|
20
|
+
IJwtService jwtService)
|
|
21
|
+
{
|
|
22
|
+
_userManager = userManager;
|
|
23
|
+
_signInManager = signInManager;
|
|
24
|
+
_jwtService = jwtService;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
[AllowAnonymous]
|
|
28
|
+
[HttpPost("login")]
|
|
29
|
+
public async Task<IActionResult> Login([FromBody] LoginRequest request)
|
|
30
|
+
{
|
|
31
|
+
var user = await _userManager.FindByEmailAsync(request.Email);
|
|
32
|
+
if (user == null)
|
|
33
|
+
return Unauthorized("Invalid credentials.");
|
|
34
|
+
|
|
35
|
+
var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);
|
|
36
|
+
if (!result.Succeeded)
|
|
37
|
+
return Unauthorized("Invalid credentials.");
|
|
38
|
+
|
|
39
|
+
var token = _jwtService.GenerateToken(user.Id, user.Email!);
|
|
40
|
+
|
|
41
|
+
return Ok(new LoginResponse
|
|
42
|
+
{
|
|
43
|
+
Token = token,
|
|
44
|
+
Email = user.Email!,
|
|
45
|
+
FullName = $"{user.FirstName} {user.LastName}"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[Authorize]
|
|
50
|
+
[HttpGet("me")]
|
|
51
|
+
public async Task<IActionResult> Me()
|
|
52
|
+
{
|
|
53
|
+
var user = await _userManager.FindByIdAsync(GetUserId().ToString());
|
|
54
|
+
if (user == null)
|
|
55
|
+
return NotFound("User not found.");
|
|
56
|
+
|
|
57
|
+
var roles = await _userManager.GetRolesAsync(user);
|
|
58
|
+
|
|
59
|
+
return Ok(new CurrentUserResponse
|
|
60
|
+
{
|
|
61
|
+
Id = user.Id,
|
|
62
|
+
Email = user.Email!,
|
|
63
|
+
FullName = $"{user.FirstName} {user.LastName}",
|
|
64
|
+
Roles = roles
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
namespace __PROJECT__.API.DTOs
|
|
2
|
+
{
|
|
3
|
+
public class LoginRequest
|
|
4
|
+
{
|
|
5
|
+
public string Email { get; set; } = string.Empty;
|
|
6
|
+
public string Password { get; set; } = string.Empty;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
public class LoginResponse
|
|
10
|
+
{
|
|
11
|
+
public string Token { get; set; } = string.Empty;
|
|
12
|
+
public string Email { get; set; } = string.Empty;
|
|
13
|
+
public string FullName { get; set; } = string.Empty;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public class CurrentUserResponse
|
|
17
|
+
{
|
|
18
|
+
public Guid Id { get; set; }
|
|
19
|
+
public string Email { get; set; } = string.Empty;
|
|
20
|
+
public string FullName { get; set; } = string.Empty;
|
|
21
|
+
public IList<string> Roles { get; set; } = new List<string>();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
namespace __PROJECT__.API.Options
|
|
2
|
+
{
|
|
3
|
+
public class JwtOptions
|
|
4
|
+
{
|
|
5
|
+
public string Issuer { get; set; } = string.Empty;
|
|
6
|
+
public string Audience { get; set; } = string.Empty;
|
|
7
|
+
public string SecretKey { get; set; } = string.Empty;
|
|
8
|
+
public int ExpiryMinutes { get; set; }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
using __PROJECT__.API;
|
|
2
|
+
using __PROJECT__.Application;
|
|
3
|
+
using __PROJECT__.Domain.Entities.Identity;
|
|
4
|
+
using __PROJECT__.Infrastructure;
|
|
5
|
+
using __PROJECT__.Persistence;
|
|
6
|
+
using Microsoft.AspNetCore.Identity;
|
|
7
|
+
|
|
8
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
9
|
+
|
|
10
|
+
// Layer services
|
|
11
|
+
builder.Services.AddApplicationServices();
|
|
12
|
+
builder.Services.AddInfrastructureServices();
|
|
13
|
+
builder.Services.AddPersistenceServices(builder.Configuration);
|
|
14
|
+
builder.Services.AddApiServices(builder.Configuration);
|
|
15
|
+
|
|
16
|
+
var app = builder.Build();
|
|
17
|
+
|
|
18
|
+
if (app.Environment.IsDevelopment())
|
|
19
|
+
{
|
|
20
|
+
app.UseSwagger();
|
|
21
|
+
app.UseSwaggerUI();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!app.Environment.IsDevelopment())
|
|
25
|
+
{
|
|
26
|
+
app.UseHttpsRedirection();
|
|
27
|
+
}
|
|
28
|
+
app.UseRouting();
|
|
29
|
+
app.UseCors(__PROJECT__.API.ConfigureServices.CorsPolicy);
|
|
30
|
+
app.UseAuthentication();
|
|
31
|
+
app.UseAuthorization();
|
|
32
|
+
app.MapControllers();
|
|
33
|
+
|
|
34
|
+
using (var scope = app.Services.CreateScope())
|
|
35
|
+
{
|
|
36
|
+
var db = scope.ServiceProvider.GetRequiredService<DataContext>();
|
|
37
|
+
db.Database.EnsureCreated();
|
|
38
|
+
|
|
39
|
+
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
|
|
40
|
+
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
|
|
41
|
+
await __PROJECT__.Persistence.Seeders.RoleSeeder.SeedAsync(roleManager);
|
|
42
|
+
await __PROJECT__.Persistence.Seeders.UserSeeder.SeedAsync(userManager);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
app.Run();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/launchsettings.json",
|
|
3
|
+
"profiles": {
|
|
4
|
+
"http": {
|
|
5
|
+
"commandName": "Project",
|
|
6
|
+
"environmentVariables": {
|
|
7
|
+
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
8
|
+
},
|
|
9
|
+
"dotnetRunMessages": true,
|
|
10
|
+
"applicationUrl": "http://localhost:5000"
|
|
11
|
+
},
|
|
12
|
+
"https": {
|
|
13
|
+
"commandName": "Project",
|
|
14
|
+
"environmentVariables": {
|
|
15
|
+
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
16
|
+
},
|
|
17
|
+
"dotnetRunMessages": true,
|
|
18
|
+
"applicationUrl": "https://localhost:7013;http://localhost:5282"
|
|
19
|
+
},
|
|
20
|
+
"Container (Dockerfile)": {
|
|
21
|
+
"commandName": "Docker",
|
|
22
|
+
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
|
23
|
+
"environmentVariables": {
|
|
24
|
+
"ASPNETCORE_HTTPS_PORTS": "8081",
|
|
25
|
+
"ASPNETCORE_HTTP_PORTS": "8080"
|
|
26
|
+
},
|
|
27
|
+
"publishAllPorts": true,
|
|
28
|
+
"useSSL": true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|