@polymorphism-tech/morph-spec 4.7.0 → 4.7.2
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.
- package/.morph/.morphversion +5 -0
- package/.morph/analytics/threads-log.jsonl +5 -0
- package/.morph/config/config.json +8 -0
- package/.morph/framework/agents.json +1815 -0
- package/.morph/framework/hooks/README.md +205 -0
- package/.morph/framework/hooks/claude-code/notification/approval-reminder.js +54 -0
- package/.morph/framework/hooks/claude-code/post-tool-use/dispatch.js +83 -0
- package/.morph/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +42 -0
- package/.morph/framework/hooks/claude-code/pre-compact/save-morph-context.js +61 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +71 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +58 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +64 -0
- package/.morph/framework/hooks/claude-code/session-start/inject-morph-context.js +94 -0
- package/.morph/framework/hooks/claude-code/statusline.py +538 -0
- package/.morph/framework/hooks/claude-code/statusline.sh +7 -0
- package/.morph/framework/hooks/claude-code/stop/validate-completion.js +88 -0
- package/.morph/framework/hooks/claude-code/user-prompt/enrich-prompt.js +91 -0
- package/.morph/framework/hooks/git/commit-msg/conventional-commits.sh +33 -0
- package/.morph/framework/hooks/git/pre-commit/agents.sh +25 -0
- package/.morph/framework/hooks/git/pre-commit/orchestrator.sh +64 -0
- package/.morph/framework/hooks/git/pre-commit/specs.sh +50 -0
- package/.morph/framework/hooks/git/pre-push/run-tests.sh +44 -0
- package/.morph/framework/hooks/shared/hook-response.js +45 -0
- package/.morph/framework/hooks/shared/phase-utils.js +129 -0
- package/.morph/framework/hooks/shared/state-reader.js +138 -0
- package/.morph/framework/hooks/shared/stdin-reader.js +26 -0
- package/.morph/framework/standards/STANDARDS.json +933 -0
- package/.morph/framework/standards/ai-agents/blazor-ui.md +364 -0
- package/.morph/framework/standards/ai-agents/production.md +415 -0
- package/.morph/framework/standards/ai-agents/setup.md +418 -0
- package/.morph/framework/standards/ai-agents/team-orchestration.md +479 -0
- package/.morph/framework/standards/ai-agents/workflows.md +354 -0
- package/.morph/framework/standards/architecture/ddd/aggregates.md +120 -0
- package/.morph/framework/standards/architecture/ddd/bounded-contexts.md +105 -0
- package/.morph/framework/standards/architecture/ddd/complexity-levels.md +108 -0
- package/.morph/framework/standards/architecture/ddd/entities.md +99 -0
- package/.morph/framework/standards/architecture/ddd/ubiquitous-language.md +58 -0
- package/.morph/framework/standards/architecture/ddd/value-objects.md +124 -0
- package/.morph/framework/standards/backend/api/minimal-api.md +494 -0
- package/.morph/framework/standards/backend/api/rest.md +492 -0
- package/.morph/framework/standards/backend/api/validation.md +88 -0
- package/.morph/framework/standards/backend/authentication/passkeys.md +428 -0
- package/.morph/framework/standards/backend/database/ef-core.md +199 -0
- package/.morph/framework/standards/backend/database/migrations.md +393 -0
- package/.morph/framework/standards/backend/database/postgresql/database.md +352 -0
- package/.morph/framework/standards/backend/database/repository-patterns.md +528 -0
- package/.morph/framework/standards/backend/database/vector-search-rag.md +541 -0
- package/.morph/framework/standards/backend/dotnet/async.md +366 -0
- package/.morph/framework/standards/backend/dotnet/core.md +117 -0
- package/.morph/framework/standards/backend/dotnet/di.md +439 -0
- package/.morph/framework/standards/backend/dotnet/program-cs-checklist.md +92 -0
- package/.morph/framework/standards/backend/integrations/asaas/asaas-api.md +216 -0
- package/.morph/framework/standards/backend/integrations/clerk/clerk-auth.md +290 -0
- package/.morph/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +350 -0
- package/.morph/framework/standards/backend/integrations/resend/resend-email.md +385 -0
- package/.morph/framework/standards/context/analytics.md +96 -0
- package/.morph/framework/standards/context/bundles.md +110 -0
- package/.morph/framework/standards/context/priming.md +78 -0
- package/.morph/framework/standards/core/architecture.md +185 -0
- package/.morph/framework/standards/core/coding.md +214 -0
- package/.morph/framework/standards/core/git-branching-strategy.md +403 -0
- package/.morph/framework/standards/core/git.md +185 -0
- package/.morph/framework/standards/core/testing.md +295 -0
- package/.morph/framework/standards/data/nosql/blob-storage.md +102 -0
- package/.morph/framework/standards/data/nosql/cache/redis.md +97 -0
- package/.morph/framework/standards/data/nosql/cosmos-db.md +118 -0
- package/.morph/framework/standards/data/vector-search/azure-ai-search.md +121 -0
- package/.morph/framework/standards/data/vector-search/rag-chunking.md +104 -0
- package/.morph/framework/standards/frontend/blazor/design-checklist.md +222 -0
- package/.morph/framework/standards/frontend/blazor/fluent-ui-setup.md +595 -0
- package/.morph/framework/standards/frontend/blazor/fluent-ui.md +137 -0
- package/.morph/framework/standards/frontend/blazor/html-conversion.md +184 -0
- package/.morph/framework/standards/frontend/blazor/lifecycle.md +195 -0
- package/.morph/framework/standards/frontend/blazor/pitfalls.md +198 -0
- package/.morph/framework/standards/frontend/blazor/state.md +191 -0
- package/.morph/framework/standards/frontend/design-system/animations.md +151 -0
- package/.morph/framework/standards/frontend/design-system/naming.md +64 -0
- package/.morph/framework/standards/frontend/nextjs/app-router.md +123 -0
- package/.morph/framework/standards/frontend/nextjs/components.md +132 -0
- package/.morph/framework/standards/frontend/nextjs/data-fetching.md +126 -0
- package/.morph/framework/standards/frontend/nextjs/forms.md +128 -0
- package/.morph/framework/standards/frontend/nextjs/naming-conventions.md +67 -0
- package/.morph/framework/standards/frontend/nextjs/nextjs-patterns.md +215 -0
- package/.morph/framework/standards/frontend/nextjs/project-structure.md +102 -0
- package/.morph/framework/standards/frontend/nextjs/state-management.md +72 -0
- package/.morph/framework/standards/frontend/nextjs/testing.md +111 -0
- package/.morph/framework/standards/infrastructure/azure/azure.md +624 -0
- package/.morph/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +422 -0
- package/.morph/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +516 -0
- package/.morph/framework/standards/infrastructure/azure/devops/local-development.md +520 -0
- package/.morph/framework/standards/infrastructure/azure/services/functions.md +486 -0
- package/.morph/framework/standards/infrastructure/azure/services/service-bus.md +459 -0
- package/.morph/framework/standards/infrastructure/azure/services/storage.md +407 -0
- package/.morph/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -0
- package/.morph/framework/standards/infrastructure/supabase/mcp-setup.md +252 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-auth.md +176 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md +169 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-rls.md +184 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-storage.md +153 -0
- package/.morph/framework/standards/integration/api/graphql.md +91 -0
- package/.morph/framework/standards/integration/api/grpc.md +114 -0
- package/.morph/framework/standards/integration/api/rest-design.md +95 -0
- package/.morph/framework/standards/integration/event-driven/cqrs.md +101 -0
- package/.morph/framework/standards/integration/event-driven/event-sourcing.md +124 -0
- package/.morph/framework/standards/integration/event-driven/service-bus.md +95 -0
- package/.morph/framework/standards/integration/mcp/mcp-tools.md +384 -0
- package/.morph/framework/standards/observability/logging.md +131 -0
- package/.morph/framework/standards/observability/metrics.md +121 -0
- package/.morph/framework/standards/observability/monitoring.md +114 -0
- package/.morph/framework/standards/observability/tracing.md +132 -0
- package/.morph/framework/standards/workflows/parallel-execution.md +112 -0
- package/.morph/framework/standards/workflows/thread-management.md +113 -0
- package/.morph/framework/templates/.idea/morph-templates.xml +92 -0
- package/.morph/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/.morph/framework/templates/IDE-SNIPPETS.md +266 -0
- package/.morph/framework/templates/README.md +814 -0
- package/.morph/framework/templates/REGISTRY.json +1888 -0
- package/.morph/framework/templates/code/dotnet/backend/repository.cs +141 -0
- package/.morph/framework/templates/code/dotnet/backend/service.cs +139 -0
- package/.morph/framework/templates/code/dotnet/contracts/Commands.cs +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/Entities.cs +25 -0
- package/.morph/framework/templates/code/dotnet/contracts/Queries.cs +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/README.md +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level1.cs +69 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level2.cs +86 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level3.cs +41 -0
- package/.morph/framework/templates/code/dotnet/database/migration.cs +83 -0
- package/.morph/framework/templates/code/dotnet/frontend/component.razor +239 -0
- package/.morph/framework/templates/code/dotnet/jobs/agent.cs +163 -0
- package/.morph/framework/templates/code/dotnet/jobs/job.cs +171 -0
- package/.morph/framework/templates/code/dotnet/test.cs +239 -0
- package/.morph/framework/templates/code/sql/rls-policy.sql +57 -0
- package/.morph/framework/templates/code/sql/supabase-migration.sql +100 -0
- package/.morph/framework/templates/code/sql/supabase-migration.template.sql +113 -0
- package/.morph/framework/templates/code/typescript/contracts.ts +168 -0
- package/.morph/framework/templates/context/CONTEXT-FEATURE.md +276 -0
- package/.morph/framework/templates/context/CONTEXT.md +181 -0
- package/.morph/framework/templates/docs/clarifications.md +253 -0
- package/.morph/framework/templates/docs/onboarding.md +123 -0
- package/.morph/framework/templates/docs/proposal.md +182 -0
- package/.morph/framework/templates/docs/schema-analysis.md +119 -0
- package/.morph/framework/templates/docs/spec.md +198 -0
- package/.morph/framework/templates/docs/ui-components.md +124 -0
- package/.morph/framework/templates/docs/ui-design-system.md +76 -0
- package/.morph/framework/templates/docs/ui-flows.md +167 -0
- package/.morph/framework/templates/docs/ui-mockups.md +98 -0
- package/.morph/framework/templates/docs/user-stories.md +34 -0
- package/.morph/framework/templates/examples/design-system-examples.md +357 -0
- package/.morph/framework/templates/examples/spec-examples.md +90 -0
- package/.morph/framework/templates/feature/decisions.md +187 -0
- package/.morph/framework/templates/feature/recap.md +146 -0
- package/.morph/framework/templates/feature/tasks.md +199 -0
- package/.morph/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -0
- package/.morph/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -0
- package/.morph/framework/templates/frontend/nextjs/env.mjs.hbs +32 -0
- package/.morph/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -0
- package/.morph/framework/templates/frontend/nextjs/page.tsx.hbs +22 -0
- package/.morph/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -0
- package/.morph/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -0
- package/.morph/framework/templates/infrastructure/azure/Dockerfile.example +82 -0
- package/.morph/framework/templates/infrastructure/azure/README.md +286 -0
- package/.morph/framework/templates/infrastructure/azure/app-insights.bicep +63 -0
- package/.morph/framework/templates/infrastructure/azure/app-service.bicep +164 -0
- package/.morph/framework/templates/infrastructure/azure/container-app-env.bicep +49 -0
- package/.morph/framework/templates/infrastructure/azure/container-app.bicep +156 -0
- package/.morph/framework/templates/infrastructure/azure/deploy-checklist.md +426 -0
- package/.morph/framework/templates/infrastructure/azure/deploy.ps1 +229 -0
- package/.morph/framework/templates/infrastructure/azure/deploy.sh +208 -0
- package/.morph/framework/templates/infrastructure/azure/key-vault.bicep +91 -0
- package/.morph/framework/templates/infrastructure/azure/main.bicep +189 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.dev.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.prod.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.staging.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/sql-database.bicep +103 -0
- package/.morph/framework/templates/infrastructure/azure/storage.bicep +106 -0
- package/.morph/framework/templates/infrastructure/docker/Dockerfile.template +58 -0
- package/.morph/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -0
- package/.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -0
- package/.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -0
- package/.morph/framework/templates/infrastructure/docker/easypanel.template.json +54 -0
- package/.morph/framework/templates/infrastructure/github/README.md +593 -0
- package/.morph/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +22 -0
- package/.morph/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -0
- package/.morph/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -0
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +61 -0
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +31 -0
- package/.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -0
- package/.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -0
- package/.morph/framework/templates/integrations/asaas-client.cs +387 -0
- package/.morph/framework/templates/integrations/asaas-webhook.cs +351 -0
- package/.morph/framework/templates/integrations/azure-identity-config.cs +288 -0
- package/.morph/framework/templates/integrations/clerk-config.cs +258 -0
- package/.morph/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -0
- package/.morph/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-retry.md +78 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-validation.md +97 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-wrapper.md +36 -0
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -0
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/backend-squad.md +90 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +126 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/squad-leader.md +43 -0
- package/.morph/framework/templates/meta-prompts/validators/checkpoint-validator.md +107 -0
- package/.morph/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -0
- package/.morph/framework/templates/project-structure/dotnet-ddd.md +70 -0
- package/.morph/framework/templates/saas/subscription.cs +347 -0
- package/.morph/framework/templates/saas/tenant.cs +338 -0
- package/.morph/framework/templates/state.template.json +17 -0
- package/.morph/framework/templates/ui/FluentDesignTheme.cs +149 -0
- package/.morph/framework/templates/ui/MudTheme.cs +281 -0
- package/.morph/framework/templates/ui/design-system.css +226 -0
- package/.morph/logs/tool-failures.log +17 -0
- package/.morph/memory/pre-compact-2026-02-24T17-43-30-049Z.json +16 -0
- package/.morph/plans/eager-watching-bunny.md +105 -0
- package/.morph/plans/temporal-seeking-nebula.md +45 -0
- package/.morph/state.json +48 -0
- package/CLAUDE.md +1 -1
- package/README.md +119 -99
- package/bin/morph-spec.js +0 -9
- package/framework/CLAUDE.md +1 -1
- package/framework/hooks/README.md +10 -6
- package/framework/hooks/claude-code/notification/approval-reminder.js +2 -0
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +1 -1
- package/framework/hooks/claude-code/stop/validate-completion.js +1 -1
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +1 -1
- package/package.json +1 -1
- package/src/commands/project/init.js +15 -42
- package/src/commands/project/update.js +22 -37
- package/src/lib/installers/mcp-installer.js +18 -3
- package/src/utils/hooks-installer.js +5 -15
- package/src/commands/project/detect.js +0 -114
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CONTRACTS: {{titleCase FEATURE_NAME}}
|
|
3
|
+
// Stack: Next.js + Supabase + .NET API
|
|
4
|
+
// Generated by MORPH Framework
|
|
5
|
+
// Date: {{DATE}}
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
#region Usings
|
|
9
|
+
|
|
10
|
+
using System;
|
|
11
|
+
using System.Collections.Generic;
|
|
12
|
+
using System.Threading;
|
|
13
|
+
using System.Threading.Tasks;
|
|
14
|
+
|
|
15
|
+
#endregion
|
|
16
|
+
|
|
17
|
+
namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}};
|
|
18
|
+
|
|
19
|
+
#region Configuration
|
|
20
|
+
|
|
21
|
+
/// <summary>
|
|
22
|
+
/// Supabase configuration options.
|
|
23
|
+
/// Bind from appsettings.json "Supabase" section.
|
|
24
|
+
/// </summary>
|
|
25
|
+
public sealed record SupabaseConfig
|
|
26
|
+
{
|
|
27
|
+
public required string Url { get; init; }
|
|
28
|
+
public required string AnonKey { get; init; }
|
|
29
|
+
public required string ServiceRoleKey { get; init; }
|
|
30
|
+
public required string JwtSecret { get; init; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#endregion
|
|
34
|
+
|
|
35
|
+
#region Pagination
|
|
36
|
+
|
|
37
|
+
/// <summary>
|
|
38
|
+
/// Pagination query parameters. Use with [AsParameters] in Minimal API.
|
|
39
|
+
/// </summary>
|
|
40
|
+
public sealed record PaginationQuery(
|
|
41
|
+
int Page = 1,
|
|
42
|
+
int PageSize = 20)
|
|
43
|
+
{
|
|
44
|
+
public int Offset => (Page - 1) * PageSize;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// <summary>
|
|
48
|
+
/// Paginated result wrapper.
|
|
49
|
+
/// </summary>
|
|
50
|
+
public sealed record PagedResult<T>(
|
|
51
|
+
List<T> Items,
|
|
52
|
+
int TotalCount,
|
|
53
|
+
int Page,
|
|
54
|
+
int PageSize)
|
|
55
|
+
{
|
|
56
|
+
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
|
|
57
|
+
public bool HasNext => Page < TotalPages;
|
|
58
|
+
public bool HasPrevious => Page > 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#endregion
|
|
62
|
+
|
|
63
|
+
#region Repository Interfaces
|
|
64
|
+
|
|
65
|
+
/// <summary>
|
|
66
|
+
/// Generic repository interface for Supabase/PostgreSQL data access via Dapper.
|
|
67
|
+
/// </summary>
|
|
68
|
+
public interface IRepository<T, TId>
|
|
69
|
+
{
|
|
70
|
+
Task<T?> GetByIdAsync(TId id, CancellationToken cancellationToken = default);
|
|
71
|
+
Task<List<T>> GetAllAsync(CancellationToken cancellationToken = default);
|
|
72
|
+
Task<PagedResult<T>> GetPagedAsync(PaginationQuery query, CancellationToken cancellationToken = default);
|
|
73
|
+
Task<TId> CreateAsync(T entity, CancellationToken cancellationToken = default);
|
|
74
|
+
Task UpdateAsync(T entity, CancellationToken cancellationToken = default);
|
|
75
|
+
Task DeleteAsync(TId id, CancellationToken cancellationToken = default);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#endregion
|
|
79
|
+
|
|
80
|
+
#region Service Interfaces
|
|
81
|
+
|
|
82
|
+
/// <summary>
|
|
83
|
+
/// Service for managing {{pascalCase FEATURE_NAME}} operations.
|
|
84
|
+
/// </summary>
|
|
85
|
+
public interface I{{pascalCase FEATURE_NAME}}Service
|
|
86
|
+
{
|
|
87
|
+
/// <summary>
|
|
88
|
+
/// Gets a {{pascalCase FEATURE_NAME}} by its identifier.
|
|
89
|
+
/// </summary>
|
|
90
|
+
Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
|
91
|
+
|
|
92
|
+
/// <summary>
|
|
93
|
+
/// Gets paginated list of {{pascalCase FEATURE_NAME}} items.
|
|
94
|
+
/// </summary>
|
|
95
|
+
Task<PagedResult<{{pascalCase FEATURE_NAME}}Dto>> GetPagedAsync(PaginationQuery query, Guid userId, CancellationToken cancellationToken = default);
|
|
96
|
+
|
|
97
|
+
/// <summary>
|
|
98
|
+
/// Creates a new {{pascalCase FEATURE_NAME}}.
|
|
99
|
+
/// </summary>
|
|
100
|
+
Task<{{pascalCase FEATURE_NAME}}Dto> CreateAsync(Create{{pascalCase FEATURE_NAME}}Request request, Guid userId, CancellationToken cancellationToken = default);
|
|
101
|
+
|
|
102
|
+
/// <summary>
|
|
103
|
+
/// Updates an existing {{pascalCase FEATURE_NAME}}.
|
|
104
|
+
/// </summary>
|
|
105
|
+
Task UpdateAsync(Guid id, Update{{pascalCase FEATURE_NAME}}Request request, Guid userId, CancellationToken cancellationToken = default);
|
|
106
|
+
|
|
107
|
+
/// <summary>
|
|
108
|
+
/// Deletes a {{pascalCase FEATURE_NAME}}.
|
|
109
|
+
/// </summary>
|
|
110
|
+
Task DeleteAsync(Guid id, Guid userId, CancellationToken cancellationToken = default);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#endregion
|
|
114
|
+
|
|
115
|
+
#region DTOs
|
|
116
|
+
|
|
117
|
+
/// <summary>
|
|
118
|
+
/// Data transfer object for {{pascalCase FEATURE_NAME}}.
|
|
119
|
+
/// </summary>
|
|
120
|
+
public sealed record {{pascalCase FEATURE_NAME}}Dto(
|
|
121
|
+
Guid Id,
|
|
122
|
+
string Name,
|
|
123
|
+
Guid UserId,
|
|
124
|
+
{{pascalCase FEATURE_NAME}}Status Status,
|
|
125
|
+
DateTime CreatedAt,
|
|
126
|
+
DateTime? UpdatedAt
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/// <summary>
|
|
130
|
+
/// Request to create a new {{pascalCase FEATURE_NAME}}.
|
|
131
|
+
/// </summary>
|
|
132
|
+
public sealed record Create{{pascalCase FEATURE_NAME}}Request(
|
|
133
|
+
string Name
|
|
134
|
+
// Add other required fields
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
/// <summary>
|
|
138
|
+
/// Request to update an existing {{pascalCase FEATURE_NAME}}.
|
|
139
|
+
/// </summary>
|
|
140
|
+
public sealed record Update{{pascalCase FEATURE_NAME}}Request(
|
|
141
|
+
string Name
|
|
142
|
+
// Add other updatable fields
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
#endregion
|
|
146
|
+
|
|
147
|
+
#region Enums
|
|
148
|
+
|
|
149
|
+
/// <summary>
|
|
150
|
+
/// Status of a {{pascalCase FEATURE_NAME}}.
|
|
151
|
+
/// </summary>
|
|
152
|
+
public enum {{pascalCase FEATURE_NAME}}Status
|
|
153
|
+
{
|
|
154
|
+
Draft = 0,
|
|
155
|
+
Active = 1,
|
|
156
|
+
Completed = 2,
|
|
157
|
+
Archived = 3,
|
|
158
|
+
// Error states
|
|
159
|
+
Failed = 100,
|
|
160
|
+
Cancelled = 101
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#endregion
|
|
164
|
+
|
|
165
|
+
#region Exceptions
|
|
166
|
+
|
|
167
|
+
public sealed class {{pascalCase FEATURE_NAME}}NotFoundException(Guid id)
|
|
168
|
+
: Exception($"{{pascalCase FEATURE_NAME}} with ID {id} was not found.");
|
|
169
|
+
|
|
170
|
+
public sealed class {{pascalCase FEATURE_NAME}}AccessDeniedException(Guid id, Guid userId)
|
|
171
|
+
: Exception($"User {userId} does not have access to {{pascalCase FEATURE_NAME}} {id}.");
|
|
172
|
+
|
|
173
|
+
#endregion
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CONTRACTS: {{titleCase FEATURE_NAME}} — Level 1 (CRUD)
|
|
3
|
+
// Generated by MORPH Framework | Date: {{DATE}}
|
|
4
|
+
// Domain Complexity: CRUD — Entity simples, sem invariants de negócio
|
|
5
|
+
// Ref: framework/standards/architecture/ddd/complexity-levels.md
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
using System;
|
|
9
|
+
using System.Collections.Generic;
|
|
10
|
+
using System.Threading;
|
|
11
|
+
using System.Threading.Tasks;
|
|
12
|
+
|
|
13
|
+
namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}};
|
|
14
|
+
|
|
15
|
+
#region Service Interface
|
|
16
|
+
|
|
17
|
+
/// <summary>Service for managing {{pascalCase FEATURE_NAME}} operations.</summary>
|
|
18
|
+
public interface I{{pascalCase FEATURE_NAME}}Service
|
|
19
|
+
{
|
|
20
|
+
Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(Guid id, CancellationToken ct = default);
|
|
21
|
+
Task<List<{{pascalCase FEATURE_NAME}}Dto>> GetAllAsync(CancellationToken ct = default);
|
|
22
|
+
Task<{{pascalCase FEATURE_NAME}}Dto> CreateAsync(Create{{pascalCase FEATURE_NAME}}Request request, CancellationToken ct = default);
|
|
23
|
+
Task UpdateAsync(Guid id, Update{{pascalCase FEATURE_NAME}}Request request, CancellationToken ct = default);
|
|
24
|
+
Task DeleteAsync(Guid id, CancellationToken ct = default);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#endregion
|
|
28
|
+
|
|
29
|
+
#region DTOs
|
|
30
|
+
|
|
31
|
+
public record {{pascalCase FEATURE_NAME}}Dto(
|
|
32
|
+
Guid Id,
|
|
33
|
+
string Name,
|
|
34
|
+
DateTime CreatedAt,
|
|
35
|
+
DateTime? UpdatedAt
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
public record Create{{pascalCase FEATURE_NAME}}Request(
|
|
39
|
+
string Name
|
|
40
|
+
// Add required fields
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
public record Update{{pascalCase FEATURE_NAME}}Request(
|
|
44
|
+
string? Name
|
|
45
|
+
// Add updatable fields
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
#endregion
|
|
49
|
+
|
|
50
|
+
#region Repository Interface
|
|
51
|
+
|
|
52
|
+
public interface I{{pascalCase FEATURE_NAME}}Repository
|
|
53
|
+
{
|
|
54
|
+
Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(Guid id, CancellationToken ct = default);
|
|
55
|
+
Task<List<{{pascalCase FEATURE_NAME}}>> GetAllAsync(CancellationToken ct = default);
|
|
56
|
+
Task AddAsync({{pascalCase FEATURE_NAME}} entity, CancellationToken ct = default);
|
|
57
|
+
void Update({{pascalCase FEATURE_NAME}} entity);
|
|
58
|
+
void Remove({{pascalCase FEATURE_NAME}} entity);
|
|
59
|
+
Task SaveChangesAsync(CancellationToken ct = default);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#endregion
|
|
63
|
+
|
|
64
|
+
#region Exceptions
|
|
65
|
+
|
|
66
|
+
public class {{pascalCase FEATURE_NAME}}NotFoundException(Guid id)
|
|
67
|
+
: Exception($"{{pascalCase FEATURE_NAME}} '{id}' not found.");
|
|
68
|
+
|
|
69
|
+
#endregion
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CONTRACTS: {{titleCase FEATURE_NAME}} — Level 2 (Business Logic)
|
|
3
|
+
// Generated by MORPH Framework | Date: {{DATE}}
|
|
4
|
+
// Domain Complexity: Aggregate com invariants, Domain Events, CQRS
|
|
5
|
+
// Ref: framework/standards/architecture/ddd/aggregates.md
|
|
6
|
+
// framework/standards/architecture/ddd/complexity-levels.md
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
using System;
|
|
10
|
+
using System.Collections.Generic;
|
|
11
|
+
using System.Threading;
|
|
12
|
+
using System.Threading.Tasks;
|
|
13
|
+
using MediatR;
|
|
14
|
+
|
|
15
|
+
// ===== AGGREGATE ROOT =====
|
|
16
|
+
// Implemente em: Domain/{{pascalCase FEATURE_NAME}}s/Aggregates/{{pascalCase FEATURE_NAME}}.cs
|
|
17
|
+
// Invariants documentados no spec.md > Aggregate Blueprint
|
|
18
|
+
//
|
|
19
|
+
// public sealed class {{pascalCase FEATURE_NAME}} : AggregateRoot
|
|
20
|
+
// {
|
|
21
|
+
// private {{pascalCase FEATURE_NAME}}() { } // EF constructor
|
|
22
|
+
// public static {{pascalCase FEATURE_NAME}} Create(/* params */) { /* validate + RaiseDomainEvent */ }
|
|
23
|
+
// public void {Action}() { /* validate invariant + mutate + RaiseDomainEvent */ }
|
|
24
|
+
// }
|
|
25
|
+
|
|
26
|
+
namespace {{NAMESPACE}}.Domain.{{pascalCase FEATURE_NAME}}s.Events;
|
|
27
|
+
|
|
28
|
+
// ===== DOMAIN EVENTS =====
|
|
29
|
+
|
|
30
|
+
public record {{pascalCase FEATURE_NAME}}CreatedEvent(
|
|
31
|
+
Guid {{pascalCase FEATURE_NAME}}Id,
|
|
32
|
+
Guid UserId
|
|
33
|
+
) : DomainEvent;
|
|
34
|
+
|
|
35
|
+
// Add events per state change: {{pascalCase FEATURE_NAME}}ConfirmedEvent, {{pascalCase FEATURE_NAME}}CancelledEvent, etc.
|
|
36
|
+
|
|
37
|
+
namespace {{NAMESPACE}}.Application.{{pascalCase FEATURE_NAME}}s.Commands;
|
|
38
|
+
|
|
39
|
+
// ===== COMMANDS (CQRS) =====
|
|
40
|
+
|
|
41
|
+
public record Create{{pascalCase FEATURE_NAME}}Command(
|
|
42
|
+
Guid UserId
|
|
43
|
+
// Add fields
|
|
44
|
+
) : IRequest<Create{{pascalCase FEATURE_NAME}}Result>;
|
|
45
|
+
|
|
46
|
+
public record Create{{pascalCase FEATURE_NAME}}Result(Guid Id);
|
|
47
|
+
|
|
48
|
+
// public record Cancel{{pascalCase FEATURE_NAME}}Command(Guid Id, string Reason) : IRequest;
|
|
49
|
+
|
|
50
|
+
namespace {{NAMESPACE}}.Application.{{pascalCase FEATURE_NAME}}s.Queries;
|
|
51
|
+
|
|
52
|
+
// ===== QUERIES =====
|
|
53
|
+
|
|
54
|
+
public record Get{{pascalCase FEATURE_NAME}}Query(Guid Id) : IRequest<{{pascalCase FEATURE_NAME}}Dto?>;
|
|
55
|
+
public record List{{pascalCase FEATURE_NAME}}sQuery() : IRequest<List<{{pascalCase FEATURE_NAME}}Dto>>;
|
|
56
|
+
|
|
57
|
+
namespace {{NAMESPACE}}.Application.{{pascalCase FEATURE_NAME}}s;
|
|
58
|
+
|
|
59
|
+
// ===== DTOs (read models — nunca expõe o Aggregate diretamente) =====
|
|
60
|
+
|
|
61
|
+
public record {{pascalCase FEATURE_NAME}}Dto(
|
|
62
|
+
Guid Id,
|
|
63
|
+
string Status,
|
|
64
|
+
DateTime CreatedAt,
|
|
65
|
+
DateTime? UpdatedAt
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
namespace {{NAMESPACE}}.Domain.{{pascalCase FEATURE_NAME}}s;
|
|
69
|
+
|
|
70
|
+
// ===== REPOSITORY (um por Aggregate Root) =====
|
|
71
|
+
|
|
72
|
+
public interface I{{pascalCase FEATURE_NAME}}Repository
|
|
73
|
+
{
|
|
74
|
+
Task<{{pascalCase FEATURE_NAME}}?> GetAsync(Guid id, CancellationToken ct = default);
|
|
75
|
+
Task AddAsync({{pascalCase FEATURE_NAME}} aggregate, CancellationToken ct = default);
|
|
76
|
+
Task UpdateAsync({{pascalCase FEATURE_NAME}} aggregate, CancellationToken ct = default);
|
|
77
|
+
Task<bool> ExistsAsync(Guid id, CancellationToken ct = default);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ===== EXCEPTIONS =====
|
|
81
|
+
|
|
82
|
+
public class {{pascalCase FEATURE_NAME}}NotFoundException(Guid id)
|
|
83
|
+
: DomainException($"{{pascalCase FEATURE_NAME}} '{id}' not found.");
|
|
84
|
+
|
|
85
|
+
public class {{pascalCase FEATURE_NAME}}InvalidStateException(string message)
|
|
86
|
+
: DomainException(message);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CONTRACTS: {{titleCase FEATURE_NAME}} — Level 3 (Bounded Context)
|
|
3
|
+
// Generated by MORPH Framework | Date: {{DATE}}
|
|
4
|
+
// Bounded Context: {{BOUNDED_CONTEXT}}
|
|
5
|
+
// Ref: framework/standards/architecture/ddd/bounded-contexts.md
|
|
6
|
+
// framework/standards/architecture/ddd/complexity-levels.md
|
|
7
|
+
// ============================================================
|
|
8
|
+
//
|
|
9
|
+
// ⚠️ REGRAS DE BC:
|
|
10
|
+
// 1. Referências cross-BC APENAS por ID (nunca navigation property)
|
|
11
|
+
// 2. Comunicação cross-BC APENAS via Integration Events
|
|
12
|
+
// 3. Cada BC tem seu próprio repositório (nunca compartilhar DbContext)
|
|
13
|
+
// 4. Linguagem Ubíqua em 1-design/ubiquitous-language.md
|
|
14
|
+
|
|
15
|
+
// ===== TUDO DO NÍVEL 2 DENTRO DO NAMESPACE DO BC =====
|
|
16
|
+
|
|
17
|
+
namespace {{NAMESPACE}}.Domain.{{BOUNDED_CONTEXT}}.{{pascalCase FEATURE_NAME}}s;
|
|
18
|
+
|
|
19
|
+
// Aggregates, ValueObjects, Events — mesma estrutura do Level 2
|
|
20
|
+
// mas dentro do namespace do Bounded Context ({{BOUNDED_CONTEXT}})
|
|
21
|
+
|
|
22
|
+
// ===== INTEGRATION EVENTS (comunicação cross-BC) =====
|
|
23
|
+
|
|
24
|
+
namespace {{NAMESPACE}}.Domain.{{BOUNDED_CONTEXT}}.IntegrationEvents;
|
|
25
|
+
|
|
26
|
+
// Integration Event: cross-BC (Service Bus ou MediatR INotification, assíncrono)
|
|
27
|
+
// Domain Event: intra-BC (MediatR, síncrono) — veja contracts-level2.cs
|
|
28
|
+
|
|
29
|
+
public record {{pascalCase FEATURE_NAME}}IntegrationEvent(
|
|
30
|
+
Guid {{pascalCase FEATURE_NAME}}Id,
|
|
31
|
+
string EventType,
|
|
32
|
+
DateTime OccurredAt
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// ===== CROSS-BC REFERENCES =====
|
|
36
|
+
// Exemplo correto:
|
|
37
|
+
// public class {{pascalCase FEATURE_NAME}} : AggregateRoot
|
|
38
|
+
// {
|
|
39
|
+
// public Guid ExternalBcEntityId { get; private set; } // ✅ por ID
|
|
40
|
+
// // public ExternalEntity External { get; } // ❌ proibido
|
|
41
|
+
// }
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// EF CORE MIGRATION TEMPLATE
|
|
3
|
+
// Generated by MORPH Framework
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
using Microsoft.EntityFrameworkCore.Migrations;
|
|
7
|
+
|
|
8
|
+
#nullable disable
|
|
9
|
+
|
|
10
|
+
namespace {{NAMESPACE}}.Infrastructure.Data.Migrations;
|
|
11
|
+
|
|
12
|
+
/// <inheritdoc />
|
|
13
|
+
public partial class Add{{pascalCase FEATURE_NAME}} : Migration
|
|
14
|
+
{
|
|
15
|
+
/// <inheritdoc />
|
|
16
|
+
protected override void Up(MigrationBuilder migrationBuilder)
|
|
17
|
+
{
|
|
18
|
+
migrationBuilder.CreateTable(
|
|
19
|
+
name: "{{pascalCase FEATURE_NAME}}s",
|
|
20
|
+
columns: table => new
|
|
21
|
+
{
|
|
22
|
+
Id = table.Column<int>(type: "int", nullable: false)
|
|
23
|
+
.Annotation("SqlServer:Identity", "1, 1"),
|
|
24
|
+
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
|
25
|
+
Status = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
26
|
+
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false,
|
|
27
|
+
defaultValueSql: "GETUTCDATE()"),
|
|
28
|
+
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true)
|
|
29
|
+
},
|
|
30
|
+
constraints: table =>
|
|
31
|
+
{
|
|
32
|
+
table.PrimaryKey("PK_{{pascalCase FEATURE_NAME}}s", x => x.Id);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Indexes
|
|
36
|
+
migrationBuilder.CreateIndex(
|
|
37
|
+
name: "IX_{{pascalCase FEATURE_NAME}}s_Name",
|
|
38
|
+
table: "{{pascalCase FEATURE_NAME}}s",
|
|
39
|
+
column: "Name");
|
|
40
|
+
|
|
41
|
+
migrationBuilder.CreateIndex(
|
|
42
|
+
name: "IX_{{pascalCase FEATURE_NAME}}s_Status",
|
|
43
|
+
table: "{{pascalCase FEATURE_NAME}}s",
|
|
44
|
+
column: "Status");
|
|
45
|
+
|
|
46
|
+
migrationBuilder.CreateIndex(
|
|
47
|
+
name: "IX_{{pascalCase FEATURE_NAME}}s_CreatedAt",
|
|
48
|
+
table: "{{pascalCase FEATURE_NAME}}s",
|
|
49
|
+
column: "CreatedAt");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// <inheritdoc />
|
|
53
|
+
protected override void Down(MigrationBuilder migrationBuilder)
|
|
54
|
+
{
|
|
55
|
+
migrationBuilder.DropTable(
|
|
56
|
+
name: "{{pascalCase FEATURE_NAME}}s");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================
|
|
61
|
+
// HOW TO CREATE A MIGRATION
|
|
62
|
+
// ============================================================
|
|
63
|
+
//
|
|
64
|
+
// 1. Add your entity to AppDbContext:
|
|
65
|
+
//
|
|
66
|
+
// public DbSet<{{pascalCase FEATURE_NAME}}> {{pascalCase FEATURE_NAME}}s => Set<{{pascalCase FEATURE_NAME}}>();
|
|
67
|
+
//
|
|
68
|
+
// 2. Add your configuration:
|
|
69
|
+
//
|
|
70
|
+
// protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
71
|
+
// {
|
|
72
|
+
// modelBuilder.ApplyConfiguration(new {{pascalCase FEATURE_NAME}}Configuration());
|
|
73
|
+
// }
|
|
74
|
+
//
|
|
75
|
+
// 3. Run migration command:
|
|
76
|
+
//
|
|
77
|
+
// dotnet ef migrations add Add{{pascalCase FEATURE_NAME}} -p src/{{NAMESPACE}}.Infrastructure -s src/{{NAMESPACE}}.Web
|
|
78
|
+
//
|
|
79
|
+
// 4. Apply migration:
|
|
80
|
+
//
|
|
81
|
+
// dotnet ef database update -p src/{{NAMESPACE}}.Infrastructure -s src/{{NAMESPACE}}.Web
|
|
82
|
+
//
|
|
83
|
+
// ============================================================
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
@* ============================================================
|
|
2
|
+
BLAZOR COMPONENT TEMPLATE
|
|
3
|
+
Generated by MORPH Framework
|
|
4
|
+
============================================================ *@
|
|
5
|
+
|
|
6
|
+
@page "/{{kebabCase FEATURE_NAME}}/{{kebabCase FEATURE_NAME}}s"
|
|
7
|
+
@attribute [Authorize(Policy = "CanView{{pascalCase FEATURE_NAME}}")]
|
|
8
|
+
@inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service
|
|
9
|
+
@inject ILogger<{{pascalCase FEATURE_NAME}}List> Logger
|
|
10
|
+
@inject NavigationManager Navigation
|
|
11
|
+
|
|
12
|
+
<PageTitle>{{pascalCase FEATURE_NAME}}s</PageTitle>
|
|
13
|
+
|
|
14
|
+
<div class="container-fluid">
|
|
15
|
+
<div class="row mb-4">
|
|
16
|
+
<div class="col">
|
|
17
|
+
<h1>{{pascalCase FEATURE_NAME}}s</h1>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="col-auto">
|
|
20
|
+
<AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
|
|
21
|
+
<button class="btn btn-primary" @onclick="ShowCreateModal">
|
|
22
|
+
<i class="bi bi-plus-lg"></i> New {{pascalCase FEATURE_NAME}}
|
|
23
|
+
</button>
|
|
24
|
+
</AuthorizeView>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
@if (_isLoading)
|
|
29
|
+
{
|
|
30
|
+
<div class="d-flex justify-content-center py-5">
|
|
31
|
+
<div class="spinner-border text-primary" role="status">
|
|
32
|
+
<span class="visually-hidden">Loading...</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
}
|
|
36
|
+
else if (_error is not null)
|
|
37
|
+
{
|
|
38
|
+
<div class="alert alert-danger" role="alert">
|
|
39
|
+
<i class="bi bi-exclamation-triangle"></i>
|
|
40
|
+
@_error
|
|
41
|
+
<button class="btn btn-link" @onclick="LoadDataAsync">Retry</button>
|
|
42
|
+
</div>
|
|
43
|
+
}
|
|
44
|
+
else if (_items is null || !_items.Any())
|
|
45
|
+
{
|
|
46
|
+
<div class="text-center py-5">
|
|
47
|
+
<i class="bi bi-inbox display-1 text-muted"></i>
|
|
48
|
+
<p class="lead mt-3">No {{kebabCase FEATURE_NAME}}s found</p>
|
|
49
|
+
<AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
|
|
50
|
+
<button class="btn btn-primary" @onclick="ShowCreateModal">
|
|
51
|
+
Create your first {{kebabCase FEATURE_NAME}}
|
|
52
|
+
</button>
|
|
53
|
+
</AuthorizeView>
|
|
54
|
+
</div>
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
{
|
|
58
|
+
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
|
59
|
+
@foreach (var item in _items)
|
|
60
|
+
{
|
|
61
|
+
<div class="col">
|
|
62
|
+
<div class="card h-100">
|
|
63
|
+
<div class="card-body">
|
|
64
|
+
<h5 class="card-title">@item.Name</h5>
|
|
65
|
+
<p class="card-text">
|
|
66
|
+
<span class="badge @GetStatusBadgeClass(item.Status)">
|
|
67
|
+
@item.Status
|
|
68
|
+
</span>
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="card-footer bg-transparent">
|
|
72
|
+
<small class="text-muted">
|
|
73
|
+
Created @item.CreatedAt.ToString("d")
|
|
74
|
+
</small>
|
|
75
|
+
<AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
|
|
76
|
+
<div class="float-end">
|
|
77
|
+
<button class="btn btn-sm btn-outline-primary"
|
|
78
|
+
@onclick="() => ShowEditModal(item)">
|
|
79
|
+
Edit
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</AuthorizeView>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
}
|
|
87
|
+
</div>
|
|
88
|
+
}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
@* Modal for Create/Edit *@
|
|
92
|
+
@if (_showModal)
|
|
93
|
+
{
|
|
94
|
+
<div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5)">
|
|
95
|
+
<div class="modal-dialog">
|
|
96
|
+
<div class="modal-content">
|
|
97
|
+
<div class="modal-header">
|
|
98
|
+
<h5 class="modal-title">
|
|
99
|
+
@(_editingItem is null ? "New {{pascalCase FEATURE_NAME}}" : "Edit {{pascalCase FEATURE_NAME}}")
|
|
100
|
+
</h5>
|
|
101
|
+
<button type="button" class="btn-close" @onclick="CloseModal"></button>
|
|
102
|
+
</div>
|
|
103
|
+
<EditForm Model="_formModel" OnValidSubmit="HandleSubmitAsync">
|
|
104
|
+
<DataAnnotationsValidator />
|
|
105
|
+
<div class="modal-body">
|
|
106
|
+
<div class="mb-3">
|
|
107
|
+
<label class="form-label">Name</label>
|
|
108
|
+
<InputText class="form-control" @bind-Value="_formModel.Name" />
|
|
109
|
+
<ValidationMessage For="() => _formModel.Name" />
|
|
110
|
+
</div>
|
|
111
|
+
@* Add more fields as needed *@
|
|
112
|
+
</div>
|
|
113
|
+
<div class="modal-footer">
|
|
114
|
+
<button type="button" class="btn btn-secondary" @onclick="CloseModal">
|
|
115
|
+
Cancel
|
|
116
|
+
</button>
|
|
117
|
+
<button type="submit" class="btn btn-primary" disabled="@_isSaving">
|
|
118
|
+
@if (_isSaving)
|
|
119
|
+
{
|
|
120
|
+
<span class="spinner-border spinner-border-sm"></span>
|
|
121
|
+
}
|
|
122
|
+
Save
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</EditForm>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@code {
|
|
132
|
+
private List<{{pascalCase FEATURE_NAME}}Dto>? _items;
|
|
133
|
+
private bool _isLoading = true;
|
|
134
|
+
private bool _showModal;
|
|
135
|
+
private bool _isSaving;
|
|
136
|
+
private string? _error;
|
|
137
|
+
private {{pascalCase FEATURE_NAME}}Dto? _editingItem;
|
|
138
|
+
private {{pascalCase FEATURE_NAME}}FormModel _formModel = new();
|
|
139
|
+
|
|
140
|
+
protected override async Task OnInitializedAsync()
|
|
141
|
+
{
|
|
142
|
+
await LoadDataAsync();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async Task LoadDataAsync()
|
|
146
|
+
{
|
|
147
|
+
_isLoading = true;
|
|
148
|
+
_error = null;
|
|
149
|
+
|
|
150
|
+
try
|
|
151
|
+
{
|
|
152
|
+
_items = await {{pascalCase FEATURE_NAME}}Service.GetAllAsync();
|
|
153
|
+
}
|
|
154
|
+
catch (Exception ex)
|
|
155
|
+
{
|
|
156
|
+
Logger.LogError(ex, "Failed to load {{kebabCase FEATURE_NAME}}s");
|
|
157
|
+
_error = "Failed to load data. Please try again.";
|
|
158
|
+
}
|
|
159
|
+
finally
|
|
160
|
+
{
|
|
161
|
+
_isLoading = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private void ShowCreateModal()
|
|
166
|
+
{
|
|
167
|
+
_editingItem = null;
|
|
168
|
+
_formModel = new {{pascalCase FEATURE_NAME}}FormModel();
|
|
169
|
+
_showModal = true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private void ShowEditModal({{pascalCase FEATURE_NAME}}Dto item)
|
|
173
|
+
{
|
|
174
|
+
_editingItem = item;
|
|
175
|
+
_formModel = new {{pascalCase FEATURE_NAME}}FormModel
|
|
176
|
+
{
|
|
177
|
+
Name = item.Name
|
|
178
|
+
// Map other fields
|
|
179
|
+
};
|
|
180
|
+
_showModal = true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private void CloseModal()
|
|
184
|
+
{
|
|
185
|
+
_showModal = false;
|
|
186
|
+
_formModel = new();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async Task HandleSubmitAsync()
|
|
190
|
+
{
|
|
191
|
+
_isSaving = true;
|
|
192
|
+
|
|
193
|
+
try
|
|
194
|
+
{
|
|
195
|
+
if (_editingItem is null)
|
|
196
|
+
{
|
|
197
|
+
// Create
|
|
198
|
+
var request = new Create{{pascalCase FEATURE_NAME}}Request(_formModel.Name);
|
|
199
|
+
await {{pascalCase FEATURE_NAME}}Service.CreateAsync(request);
|
|
200
|
+
}
|
|
201
|
+
else
|
|
202
|
+
{
|
|
203
|
+
// Update
|
|
204
|
+
var request = new Update{{pascalCase FEATURE_NAME}}Request(_formModel.Name);
|
|
205
|
+
await {{pascalCase FEATURE_NAME}}Service.UpdateAsync(_editingItem.Id, request);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
CloseModal();
|
|
209
|
+
await LoadDataAsync();
|
|
210
|
+
}
|
|
211
|
+
catch (Exception ex)
|
|
212
|
+
{
|
|
213
|
+
Logger.LogError(ex, "Failed to save {{kebabCase FEATURE_NAME}}");
|
|
214
|
+
// Show error to user
|
|
215
|
+
}
|
|
216
|
+
finally
|
|
217
|
+
{
|
|
218
|
+
_isSaving = false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private static string GetStatusBadgeClass({{pascalCase FEATURE_NAME}}Status status) => status switch
|
|
223
|
+
{
|
|
224
|
+
{{pascalCase FEATURE_NAME}}Status.Active => "bg-success",
|
|
225
|
+
{{pascalCase FEATURE_NAME}}Status.Pending => "bg-warning text-dark",
|
|
226
|
+
{{pascalCase FEATURE_NAME}}Status.Completed => "bg-info",
|
|
227
|
+
{{pascalCase FEATURE_NAME}}Status.Failed => "bg-danger",
|
|
228
|
+
_ => "bg-secondary"
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
private class {{pascalCase FEATURE_NAME}}FormModel
|
|
232
|
+
{
|
|
233
|
+
[Required]
|
|
234
|
+
[StringLength(200, MinimumLength = 3)]
|
|
235
|
+
public string Name { get; set; } = string.Empty;
|
|
236
|
+
|
|
237
|
+
// Add more fields as needed
|
|
238
|
+
}
|
|
239
|
+
}
|