@polymorphism-tech/morph-spec 4.16.0 → 4.18.0
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/CLAUDE.md +36 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +3 -0
- package/bin/task-manager.js +999 -962
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents/ai-agents/vector-search-expert.md +247 -233
- package/framework/agents/backend/ms-agent-expert.md +1 -1
- package/framework/agents/backend/neon-expert.md +379 -0
- package/framework/agents/frontend/gsap-animator.md +131 -0
- package/framework/agents/integrations/neon-auth.md +151 -0
- package/framework/agents.json +1183 -1085
- package/framework/commands/morph-preflight.md +200 -192
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +1 -1
- package/framework/rules/gsap-standards.md +13 -0
- package/framework/rules/neon-auth-standards.md +13 -0
- package/framework/rules/neon-standards.md +26 -0
- package/framework/skills/level-0-meta/mcp-registry.json +1 -1
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +472 -472
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +12 -12
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +7 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +24 -24
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +12 -12
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +9 -9
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +143 -143
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +197 -116
- package/framework/standards/ai-agents/production.md +1 -0
- package/framework/standards/ai-agents/setup.md +3 -2
- package/framework/standards/ai-agents/workflows.md +1 -0
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +4 -4
- package/framework/standards/backend/database/postgresql/database.md +63 -83
- package/framework/standards/backend/dotnet/scalar-ui.md +172 -172
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -274
- package/framework/standards/backend/integrations/asaas/asaas-api.md +2 -2
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +364 -0
- package/framework/standards/frontend/design-system/animations.md +9 -0
- package/framework/standards/frontend/gsap/gsap-core.md +463 -0
- package/framework/standards/frontend/gsap/gsap-plugins.md +294 -0
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +318 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +1 -0
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +47 -37
- package/framework/standards/infrastructure/docker/easypanel-deploy.md +4 -8
- package/framework/standards/infrastructure/neon/neon-pgrag.md +239 -0
- package/framework/standards/infrastructure/neon/neon-pgvector.md +236 -0
- package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +310 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +186 -0
- package/framework/standards/integration/mcp/mcp-tools.md +32 -34
- package/framework/templates/README.md +20 -20
- package/framework/templates/REGISTRY.json +54 -101
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/backend/repository.cs +148 -148
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/database/vector-entity.cs +93 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +1 -1
- package/framework/templates/code/sql/{supabase-migration.sql → postgresql-migration.sql} +12 -21
- package/framework/templates/code/sql/{supabase-migration.template.sql → postgresql-migration.template.sql} +13 -18
- package/framework/templates/code/sql/{rls-policy.sql → postgresql-rls-policy.sql} +6 -9
- package/framework/templates/code/typescript/contracts.ts +167 -168
- package/framework/templates/context/CONTEXT.md +3 -3
- package/framework/templates/docs/proposal.md +6 -6
- package/framework/templates/docs/ui-components.md +1 -1
- package/framework/templates/feature/decisions.md +122 -121
- package/framework/templates/feature/recap.md +145 -145
- package/framework/templates/feature/tasks.md +199 -199
- package/framework/templates/frontend/nextjs/gsap-scroll-section.tsx.hbs +44 -0
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -67
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/docker/easypanel.template.json +46 -54
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/integrations/neon-auth-config.cs +133 -0
- package/framework/templates/integrations/semantic-kernel-neon.cs +125 -0
- package/framework/workflows/README.md +1 -1
- package/framework/workflows/docs/full-morph.md +69 -69
- package/package.json +1 -1
- package/src/commands/agents/dispatch-agents.js +19 -15
- package/src/commands/state/advance-phase.js +5 -0
- package/src/commands/tasks/task.js +3 -0
- package/src/core/state/state-manager.js +45 -1
- package/src/core/templates/template-registry.js +1 -66
- package/src/lib/detectors/index.js +4 -4
- package/src/lib/generators/architecture-scanner.js +639 -640
- package/src/lib/installers/mcp-installer.js +1 -1
- package/src/lib/tasks/task-classifier.js +15 -1
- package/src/lib/tasks/test-runner.js +5 -1
- package/src/lib/validators/content/content-validator.js +9 -10
- package/src/lib/validators/validation-runner.js +7 -5
- package/framework/agents/backend/supabase-expert.md +0 -256
- package/framework/agents/integrations/clerk-auth.md +0 -109
- package/framework/rules/clerk-standards.md +0 -13
- package/framework/rules/supabase-standards.md +0 -29
- package/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -290
- package/framework/standards/infrastructure/supabase/mcp-setup.md +0 -142
- package/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
- package/framework/standards/infrastructure/supabase/supabase-custom-jwt.md +0 -206
- package/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
- package/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
- package/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
- package/framework/templates/integrations/clerk-config.cs +0 -217
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# Supabase Storage Standard
|
|
2
|
-
|
|
3
|
-
> **Scope:** nextjs-supabase
|
|
4
|
-
> **Layer:** 2 (on keyword)
|
|
5
|
-
> **Keywords:** supabase, storage, bucket, upload, file storage
|
|
6
|
-
> **Load When:** supabase storage keywords detected
|
|
7
|
-
|
|
8
|
-
Stack: Next.js 15 + Supabase + .NET Backend
|
|
9
|
-
|
|
10
|
-
## Core Rules
|
|
11
|
-
|
|
12
|
-
- ALWAYS use RLS policies on storage.objects for access control
|
|
13
|
-
- NEVER serve private files without signed URLs
|
|
14
|
-
- ALWAYS validate file type and size before upload
|
|
15
|
-
- Public buckets: anyone can read, still need auth for write
|
|
16
|
-
- Private buckets: require signed URLs or authenticated access
|
|
17
|
-
|
|
18
|
-
## Bucket Configuration
|
|
19
|
-
|
|
20
|
-
```sql
|
|
21
|
-
-- Public bucket (avatars, logos)
|
|
22
|
-
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
23
|
-
VALUES ('avatars', 'avatars', true, 2097152, ARRAY['image/jpeg','image/png','image/webp']);
|
|
24
|
-
|
|
25
|
-
-- Private bucket (documents, reports)
|
|
26
|
-
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
27
|
-
VALUES ('documents', 'documents', false, 10485760, ARRAY['application/pdf','image/jpeg','image/png']);
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
| Setting | Public Bucket | Private Bucket |
|
|
31
|
-
|---------|--------------|----------------|
|
|
32
|
-
| Read access | Anyone via URL | Signed URL or auth required |
|
|
33
|
-
| Write access | RLS policy required | RLS policy required |
|
|
34
|
-
| Use case | Avatars, public images | Documents, reports |
|
|
35
|
-
|
|
36
|
-
## Storage RLS Policies
|
|
37
|
-
|
|
38
|
-
```sql
|
|
39
|
-
-- Users upload/view/delete in their own folder
|
|
40
|
-
CREATE POLICY "user_upload" ON storage.objects FOR INSERT
|
|
41
|
-
WITH CHECK (bucket_id = 'documents' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
42
|
-
|
|
43
|
-
CREATE POLICY "user_select" ON storage.objects FOR SELECT
|
|
44
|
-
USING (bucket_id = 'documents' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
45
|
-
|
|
46
|
-
CREATE POLICY "user_delete" ON storage.objects FOR DELETE
|
|
47
|
-
USING (bucket_id = 'documents' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
48
|
-
|
|
49
|
-
-- Public bucket: anyone reads, auth users upload to own folder
|
|
50
|
-
CREATE POLICY "public_read" ON storage.objects FOR SELECT
|
|
51
|
-
USING (bucket_id = 'avatars');
|
|
52
|
-
|
|
53
|
-
CREATE POLICY "auth_upload_avatar" ON storage.objects FOR INSERT
|
|
54
|
-
WITH CHECK (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Path Naming Convention
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
{bucket}/{user_id}/{category}/{filename}
|
|
61
|
-
avatars/{user_id}/profile.webp
|
|
62
|
-
documents/{user_id}/invoices/2026-01-invoice.pdf
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
First folder segment = user_id (enables simple RLS). Use lowercase, hyphens, include extension.
|
|
66
|
-
|
|
67
|
-
## Upload Patterns
|
|
68
|
-
|
|
69
|
-
### Browser Upload (Next.js)
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
async function uploadFile(file: File, userId: string) {
|
|
73
|
-
if (file.size > 10 * 1024 * 1024) throw new Error("File too large");
|
|
74
|
-
const allowedTypes = ["application/pdf", "image/jpeg", "image/png"];
|
|
75
|
-
if (!allowedTypes.includes(file.type)) throw new Error("Invalid type");
|
|
76
|
-
|
|
77
|
-
const path = `${userId}/uploads/${Date.now()}-${file.name}`;
|
|
78
|
-
const { data, error } = await supabase.storage
|
|
79
|
-
.from("documents")
|
|
80
|
-
.upload(path, file, { cacheControl: "3600", upsert: false, contentType: file.type });
|
|
81
|
-
if (error) throw error;
|
|
82
|
-
return data.path;
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Server Upload (.NET)
|
|
87
|
-
|
|
88
|
-
```csharp
|
|
89
|
-
public sealed class StorageService(Supabase.Client client, ILogger<StorageService> logger)
|
|
90
|
-
{
|
|
91
|
-
private static readonly string[] AllowedTypes = ["application/pdf", "image/jpeg", "image/png"];
|
|
92
|
-
|
|
93
|
-
public async Task<string> UploadAsync(
|
|
94
|
-
string bucket, string path, Stream stream,
|
|
95
|
-
string contentType, CancellationToken ct = default)
|
|
96
|
-
{
|
|
97
|
-
if (!AllowedTypes.Contains(contentType))
|
|
98
|
-
throw new ValidationException($"Invalid content type: {contentType}");
|
|
99
|
-
var response = await client.Storage.From(bucket)
|
|
100
|
-
.Upload(stream, path, new FileOptions { ContentType = contentType, Upsert = false });
|
|
101
|
-
return response;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Download and Signed URLs
|
|
107
|
-
|
|
108
|
-
```ts
|
|
109
|
-
// Public bucket: direct URL
|
|
110
|
-
const { data } = supabase.storage.from("avatars").getPublicUrl("user-id/profile.webp");
|
|
111
|
-
|
|
112
|
-
// Private bucket: signed URL (time-limited)
|
|
113
|
-
const { data } = await supabase.storage
|
|
114
|
-
.from("documents").createSignedUrl("user-id/report.pdf", 3600); // 1 hour
|
|
115
|
-
|
|
116
|
-
// Download file content (returns Blob)
|
|
117
|
-
const { data } = await supabase.storage.from("documents").download("user-id/report.pdf");
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Image Transformations
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
const { data } = supabase.storage.from("avatars").getPublicUrl("user-id/photo.jpg", {
|
|
124
|
-
transform: { width: 200, height: 200, resize: "cover", quality: 80 },
|
|
125
|
-
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
| Parameter | Options | Default |
|
|
129
|
-
|-----------|---------|---------|
|
|
130
|
-
| `width` / `height` | 1-2500 px | original |
|
|
131
|
-
| `resize` | cover, contain, fill | cover |
|
|
132
|
-
| `quality` | 20-100 | 80 |
|
|
133
|
-
| `format` | origin, avif, webp | origin |
|
|
134
|
-
|
|
135
|
-
## File Validation
|
|
136
|
-
|
|
137
|
-
| Check | Client-Side | Server-Side | Bucket Config |
|
|
138
|
-
|-------|------------|-------------|---------------|
|
|
139
|
-
| File size | `file.size` | Stream length | `file_size_limit` |
|
|
140
|
-
| MIME type | `file.type` | Content-Type | `allowed_mime_types` |
|
|
141
|
-
| Extension | File name | File name | -- |
|
|
142
|
-
|
|
143
|
-
Always validate at all three levels. Client-side can be bypassed.
|
|
144
|
-
|
|
145
|
-
## Common Mistakes
|
|
146
|
-
|
|
147
|
-
| Wrong | Right | Why |
|
|
148
|
-
|-------|-------|-----|
|
|
149
|
-
| No RLS on storage.objects | Policies for each bucket | Anyone can read/write without policies |
|
|
150
|
-
| Private files via public URL | `createSignedUrl()` | Private bucket returns 403 without auth |
|
|
151
|
-
| User-provided filename as-is | Sanitize + timestamp prefix | Path traversal attacks, collisions |
|
|
152
|
-
| `upsert: true` by default | `upsert: false` | Prevents accidental file overwrites |
|
|
153
|
-
| Files without user folder | `{user_id}/...` path pattern | Cannot write simple RLS policies |
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
// ==============================================================================
|
|
2
|
-
// MORPH-SPEC - Clerk Authentication Configuration Template
|
|
3
|
-
// Configuração de autenticação com Clerk para .NET
|
|
4
|
-
// ==============================================================================
|
|
5
|
-
|
|
6
|
-
using Microsoft.AspNetCore.Authentication;
|
|
7
|
-
using System.Security.Claims;
|
|
8
|
-
|
|
9
|
-
namespace {{Namespace}}.Infrastructure.Auth;
|
|
10
|
-
|
|
11
|
-
// ==============================================================================
|
|
12
|
-
// OPTIONS
|
|
13
|
-
// ==============================================================================
|
|
14
|
-
|
|
15
|
-
public class ClerkOptions
|
|
16
|
-
{
|
|
17
|
-
public const string SectionName = "Clerk";
|
|
18
|
-
|
|
19
|
-
/// <summary>
|
|
20
|
-
/// Clerk Secret Key (starts with sk_)
|
|
21
|
-
/// </summary>
|
|
22
|
-
public string SecretKey { get; set; } = string.Empty;
|
|
23
|
-
|
|
24
|
-
/// <summary>
|
|
25
|
-
/// Clerk Publishable Key (starts with pk_)
|
|
26
|
-
/// </summary>
|
|
27
|
-
public string PublishableKey { get; set; } = string.Empty;
|
|
28
|
-
|
|
29
|
-
/// <summary>
|
|
30
|
-
/// Clerk Frontend API URL
|
|
31
|
-
/// </summary>
|
|
32
|
-
public string FrontendApi { get; set; } = string.Empty;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ==============================================================================
|
|
36
|
-
// SERVICE EXTENSIONS
|
|
37
|
-
// ==============================================================================
|
|
38
|
-
|
|
39
|
-
public static class ClerkServiceExtensions
|
|
40
|
-
{
|
|
41
|
-
/// <summary>
|
|
42
|
-
/// Adiciona autenticação Clerk ao projeto
|
|
43
|
-
/// Requer: Clerk.Net.AspNetCore.Security
|
|
44
|
-
/// </summary>
|
|
45
|
-
public static IServiceCollection AddClerkAuthentication(
|
|
46
|
-
this IServiceCollection services,
|
|
47
|
-
IConfiguration configuration)
|
|
48
|
-
{
|
|
49
|
-
services.Configure<ClerkOptions>(configuration.GetSection(ClerkOptions.SectionName));
|
|
50
|
-
|
|
51
|
-
// Opção 1: Usando Clerk.Net SDK oficial
|
|
52
|
-
// services.AddClerk(configuration);
|
|
53
|
-
|
|
54
|
-
// Opção 2: Configuração manual com JWT
|
|
55
|
-
services.AddAuthentication("Clerk")
|
|
56
|
-
.AddJwtBearer("Clerk", options =>
|
|
57
|
-
{
|
|
58
|
-
var clerkOptions = configuration.GetSection(ClerkOptions.SectionName).Get<ClerkOptions>()!;
|
|
59
|
-
|
|
60
|
-
options.Authority = clerkOptions.FrontendApi;
|
|
61
|
-
options.TokenValidationParameters = new()
|
|
62
|
-
{
|
|
63
|
-
ValidateIssuer = true,
|
|
64
|
-
ValidateAudience = false,
|
|
65
|
-
ValidateLifetime = true,
|
|
66
|
-
ValidateIssuerSigningKey = true,
|
|
67
|
-
NameClaimType = ClaimTypes.NameIdentifier
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
services.AddAuthorization();
|
|
72
|
-
|
|
73
|
-
return services;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ==============================================================================
|
|
79
|
-
// CLERK USER SERVICE
|
|
80
|
-
// ==============================================================================
|
|
81
|
-
|
|
82
|
-
public interface IClerkUserService
|
|
83
|
-
{
|
|
84
|
-
Task<ClerkUser?> GetCurrentUserAsync(CancellationToken ct = default);
|
|
85
|
-
Task<ClerkUser?> GetUserByIdAsync(string userId, CancellationToken ct = default);
|
|
86
|
-
Task UpdateUserMetadataAsync(string userId, Dictionary<string, object> metadata, CancellationToken ct = default);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public class ClerkUserService : IClerkUserService
|
|
90
|
-
{
|
|
91
|
-
private readonly HttpClient _httpClient;
|
|
92
|
-
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
93
|
-
private readonly ILogger<ClerkUserService> _logger;
|
|
94
|
-
|
|
95
|
-
public ClerkUserService(
|
|
96
|
-
HttpClient httpClient,
|
|
97
|
-
IHttpContextAccessor httpContextAccessor,
|
|
98
|
-
ILogger<ClerkUserService> logger)
|
|
99
|
-
{
|
|
100
|
-
_httpClient = httpClient;
|
|
101
|
-
_httpContextAccessor = httpContextAccessor;
|
|
102
|
-
_logger = logger;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public async Task<ClerkUser?> GetCurrentUserAsync(CancellationToken ct = default)
|
|
106
|
-
{
|
|
107
|
-
var userId = _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
108
|
-
if (string.IsNullOrEmpty(userId)) return null;
|
|
109
|
-
|
|
110
|
-
return await GetUserByIdAsync(userId, ct);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
public async Task<ClerkUser?> GetUserByIdAsync(string userId, CancellationToken ct = default)
|
|
114
|
-
{
|
|
115
|
-
try
|
|
116
|
-
{
|
|
117
|
-
var response = await _httpClient.GetAsync($"users/{userId}", ct);
|
|
118
|
-
|
|
119
|
-
if (!response.IsSuccessStatusCode)
|
|
120
|
-
{
|
|
121
|
-
_logger.LogWarning("Failed to get Clerk user {UserId}: {Status}", userId, response.StatusCode);
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return await response.Content.ReadFromJsonAsync<ClerkUser>(ct);
|
|
126
|
-
}
|
|
127
|
-
catch (Exception ex)
|
|
128
|
-
{
|
|
129
|
-
_logger.LogError(ex, "Error getting Clerk user {UserId}", userId);
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public async Task UpdateUserMetadataAsync(string userId, Dictionary<string, object> metadata, CancellationToken ct = default)
|
|
135
|
-
{
|
|
136
|
-
var request = new { public_metadata = metadata };
|
|
137
|
-
var response = await _httpClient.PatchAsJsonAsync($"users/{userId}", request, ct);
|
|
138
|
-
response.EnsureSuccessStatusCode();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ==============================================================================
|
|
143
|
-
// CLERK USER MODEL
|
|
144
|
-
// ==============================================================================
|
|
145
|
-
|
|
146
|
-
public record ClerkUser
|
|
147
|
-
{
|
|
148
|
-
public string Id { get; init; } = string.Empty;
|
|
149
|
-
public string? FirstName { get; init; }
|
|
150
|
-
public string? LastName { get; init; }
|
|
151
|
-
public string? ImageUrl { get; init; }
|
|
152
|
-
public List<ClerkEmailAddress>? EmailAddresses { get; init; }
|
|
153
|
-
public string? PrimaryEmailAddressId { get; init; }
|
|
154
|
-
public Dictionary<string, object>? PublicMetadata { get; init; }
|
|
155
|
-
public long CreatedAt { get; init; }
|
|
156
|
-
public long UpdatedAt { get; init; }
|
|
157
|
-
|
|
158
|
-
public string? PrimaryEmail => EmailAddresses?
|
|
159
|
-
.FirstOrDefault(e => e.Id == PrimaryEmailAddressId)?.EmailAddress;
|
|
160
|
-
|
|
161
|
-
public string FullName => $"{FirstName} {LastName}".Trim();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public record ClerkEmailAddress
|
|
165
|
-
{
|
|
166
|
-
public string Id { get; init; } = string.Empty;
|
|
167
|
-
public string EmailAddress { get; init; } = string.Empty;
|
|
168
|
-
public bool Verified { get; init; }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ==============================================================================
|
|
172
|
-
// MIDDLEWARE PIPELINE
|
|
173
|
-
// ==============================================================================
|
|
174
|
-
|
|
175
|
-
public static class ClerkMiddlewareExtensions
|
|
176
|
-
{
|
|
177
|
-
public static IApplicationBuilder UseClerkAuthentication(this IApplicationBuilder app)
|
|
178
|
-
{
|
|
179
|
-
app.UseAuthentication();
|
|
180
|
-
app.UseAuthorization();
|
|
181
|
-
return app;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ==============================================================================
|
|
186
|
-
// APPSETTINGS EXAMPLE
|
|
187
|
-
// ==============================================================================
|
|
188
|
-
|
|
189
|
-
/*
|
|
190
|
-
{
|
|
191
|
-
"Clerk": {
|
|
192
|
-
"SecretKey": "sk_test_xxxxx",
|
|
193
|
-
"PublishableKey": "pk_test_xxxxx",
|
|
194
|
-
"FrontendApi": "https://your-app.clerk.accounts.dev"
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
*/
|
|
198
|
-
|
|
199
|
-
// ==============================================================================
|
|
200
|
-
// PROGRAM.CS EXAMPLE
|
|
201
|
-
// ==============================================================================
|
|
202
|
-
|
|
203
|
-
/*
|
|
204
|
-
// Program.cs
|
|
205
|
-
var builder = WebApplication.CreateBuilder(args);
|
|
206
|
-
|
|
207
|
-
// Add Clerk authentication
|
|
208
|
-
builder.Services.AddClerkAuthentication(builder.Configuration);
|
|
209
|
-
|
|
210
|
-
var app = builder.Build();
|
|
211
|
-
|
|
212
|
-
app.UseClerkAuthentication();
|
|
213
|
-
|
|
214
|
-
app.MapControllers().RequireAuthorization();
|
|
215
|
-
|
|
216
|
-
app.Run();
|
|
217
|
-
*/
|