@nuxt-customer-portal/core 0.3.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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/app/composables/useAutoPagination.ts +30 -0
- package/app/composables/useOrganization.ts +117 -0
- package/app/composables/usePaginatedResource.ts +167 -0
- package/app/composables/usePortalFeatures.ts +74 -0
- package/app/composables/usePortalSession.ts +20 -0
- package/app/composables/useUserStore.ts +460 -0
- package/app/middleware/auth.global.ts +32 -0
- package/app/plugins/auth.ts +28 -0
- package/app/plugins/portal-core-feature.ts +3 -0
- package/app/types/index.d.ts +75 -0
- package/app/utils/auth-client.ts +31 -0
- package/app/utils/permission-utils.ts +21 -0
- package/app/utils/random.ts +9 -0
- package/i18n/locales/en.json +81 -0
- package/i18n/locales/nl.json +81 -0
- package/migrations/0000_baseline.sql +119 -0
- package/migrations/0001_organization_types.sql +12 -0
- package/migrations/0002_invitation_created_at.sql +1 -0
- package/migrations/0003_system_admin_role.sql +5 -0
- package/migrations/0004_provider_organization_type.sql +16 -0
- package/migrations/0005_portal_email_settings.sql +15 -0
- package/migrations/meta/0000_snapshot.json +880 -0
- package/migrations/meta/_journal.json +13 -0
- package/nuxt.config.ts +55 -0
- package/package.json +66 -0
- package/portal.manifest.mjs +8 -0
- package/server/api/auth/[...].ts +18 -0
- package/server/api/auth/permissions.get.ts +64 -0
- package/server/api/notifications.ts +294 -0
- package/server/db/core-schema.ts +1 -0
- package/server/db/schema/auth-schema.ts +198 -0
- package/server/db/schema/index.ts +1 -0
- package/server/middleware/api-docs-auth.ts +26 -0
- package/server/middleware/auth.ts +61 -0
- package/server/portal.ts +212 -0
- package/server/routes/api-docs/openapi.json.get.ts +22 -0
- package/server/utils/admin-email-allowlist.ts +12 -0
- package/server/utils/auth.ts +326 -0
- package/server/utils/business-hooks.ts +28 -0
- package/server/utils/db.ts +18 -0
- package/server/utils/email-template.html +105 -0
- package/server/utils/email-texts.ts +110 -0
- package/server/utils/email.ts +37 -0
- package/server/utils/invitation-management.ts +29 -0
- package/server/utils/openapi-auth.ts +67 -0
- package/server/utils/openapi-contracts.ts +129 -0
- package/server/utils/openapi-order.ts +136 -0
- package/server/utils/permissions.ts +179 -0
- package/server/utils/portal-email.ts +424 -0
- package/server/utils/query-builder.ts +270 -0
- package/server/utils/route-meta.ts +28 -0
- package/shared/core-feature.ts +152 -0
- package/shared/feature-registry.ts +62 -0
- package/shared/invitation-validation.ts +8 -0
- package/shared/organization-avatar.ts +11 -0
- package/shared/permissions.ts +26 -0
- package/shared/portal-session.ts +17 -0
- package/shared/types/feature.ts +130 -0
- package/shared/types/index.ts +40 -0
- package/shared/types/sections/api.ts +134 -0
- package/shared/types/sections/auth.ts +71 -0
- package/shared/types/sections/database.ts +86 -0
- package/shared/types/sections/queryBuilder.ts +31 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"invitationManagement": {
|
|
3
|
+
"pending": "Pending invitations",
|
|
4
|
+
"empty": "No pending invitations.",
|
|
5
|
+
"edit": "Change invitation role",
|
|
6
|
+
"role": "Role",
|
|
7
|
+
"save": "Save role",
|
|
8
|
+
"revoke": "Revoke invitation",
|
|
9
|
+
"confirmRevoke": "Revoke the invitation for {email}? This invitation link will no longer work.",
|
|
10
|
+
"updated": "Invitation role updated",
|
|
11
|
+
"revoked": "Invitation revoked",
|
|
12
|
+
"failed": "Could not change this invitation. Refresh the page and try again."
|
|
13
|
+
},
|
|
14
|
+
"common": {
|
|
15
|
+
"refresh": "Refresh",
|
|
16
|
+
"cancel": "Cancel",
|
|
17
|
+
"close": "Close",
|
|
18
|
+
"confirm": "Confirm",
|
|
19
|
+
"create": "Create",
|
|
20
|
+
"createdAt": "Created at",
|
|
21
|
+
"delete": "Delete",
|
|
22
|
+
"descending": "Descending",
|
|
23
|
+
"direction": "Direction",
|
|
24
|
+
"edit": "Edit",
|
|
25
|
+
"error": "An error occurred",
|
|
26
|
+
"filters": "Filters",
|
|
27
|
+
"loaded": "Loaded",
|
|
28
|
+
"loading": "Loading...",
|
|
29
|
+
"reset": "Reset",
|
|
30
|
+
"save": "Save",
|
|
31
|
+
"sort": "Sort",
|
|
32
|
+
"sortBy": "Sort by",
|
|
33
|
+
"success": "Success",
|
|
34
|
+
"searchPlaceholder": "Search...",
|
|
35
|
+
"ascending": "Ascending",
|
|
36
|
+
"view": "View"
|
|
37
|
+
},
|
|
38
|
+
"menu": {
|
|
39
|
+
"moduleNavigation": "Modules and pages",
|
|
40
|
+
"search": "Search",
|
|
41
|
+
"switchOrganization": "Switch organization",
|
|
42
|
+
"module": "Module",
|
|
43
|
+
"dashboard": "Dashboard",
|
|
44
|
+
"settings": {
|
|
45
|
+
"title": "Settings"
|
|
46
|
+
},
|
|
47
|
+
"logout": "Log Out"
|
|
48
|
+
},
|
|
49
|
+
"dashboard": {
|
|
50
|
+
"title": "Dashboard",
|
|
51
|
+
"greeting": "Welcome back, {name}",
|
|
52
|
+
"introduction": "Start with the work that needs your attention.",
|
|
53
|
+
"notifications": "Notifications",
|
|
54
|
+
"areas": {
|
|
55
|
+
"attention": "Needs attention",
|
|
56
|
+
"main": "Your work",
|
|
57
|
+
"aside": "Recent activity"
|
|
58
|
+
},
|
|
59
|
+
"empty": {
|
|
60
|
+
"title": "You're all caught up",
|
|
61
|
+
"description": "There is no dashboard work available for this organization."
|
|
62
|
+
},
|
|
63
|
+
"error": {
|
|
64
|
+
"title": "This dashboard item is unavailable",
|
|
65
|
+
"description": "The rest of your dashboard is still available.",
|
|
66
|
+
"retry": "Try again"
|
|
67
|
+
},
|
|
68
|
+
"seo": {
|
|
69
|
+
"description": "View work requiring your attention.",
|
|
70
|
+
"title": "Dashboard"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"footer": {
|
|
74
|
+
"copyright": "All rights reserved."
|
|
75
|
+
},
|
|
76
|
+
"nav": {
|
|
77
|
+
"admin": "Admin",
|
|
78
|
+
"blog": "Blog",
|
|
79
|
+
"contact": "Contact"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"invitationManagement": {
|
|
3
|
+
"pending": "Openstaande uitnodigingen",
|
|
4
|
+
"empty": "Geen openstaande uitnodigingen.",
|
|
5
|
+
"edit": "Rol van uitnodiging wijzigen",
|
|
6
|
+
"role": "Rol",
|
|
7
|
+
"save": "Rol opslaan",
|
|
8
|
+
"revoke": "Uitnodiging intrekken",
|
|
9
|
+
"confirmRevoke": "De uitnodiging voor {email} intrekken? Deze uitnodigingslink werkt daarna niet meer.",
|
|
10
|
+
"updated": "Rol van uitnodiging gewijzigd",
|
|
11
|
+
"revoked": "Uitnodiging ingetrokken",
|
|
12
|
+
"failed": "Deze uitnodiging kon niet worden gewijzigd. Vernieuw de pagina en probeer opnieuw."
|
|
13
|
+
},
|
|
14
|
+
"common": {
|
|
15
|
+
"refresh": "Verversen",
|
|
16
|
+
"cancel": "Annuleren",
|
|
17
|
+
"close": "Sluiten",
|
|
18
|
+
"create": "Aanmaken",
|
|
19
|
+
"createdAt": "Aangemaakt op",
|
|
20
|
+
"delete": "Verwijderen",
|
|
21
|
+
"descending": "Aflopend",
|
|
22
|
+
"direction": "Richting",
|
|
23
|
+
"edit": "Bewerken",
|
|
24
|
+
"error": "Er is een fout opgetreden",
|
|
25
|
+
"filters": "Filters",
|
|
26
|
+
"loaded": "Geladen",
|
|
27
|
+
"loading": "Laden...",
|
|
28
|
+
"reset": "Resetten",
|
|
29
|
+
"confirm": "Bevestigen",
|
|
30
|
+
"save": "Opslaan",
|
|
31
|
+
"sort": "Sorteren",
|
|
32
|
+
"sortBy": "Sorteren op",
|
|
33
|
+
"success": "Succesvol!",
|
|
34
|
+
"searchPlaceholder": "Zoeken...",
|
|
35
|
+
"ascending": "Oplopend",
|
|
36
|
+
"view": "Bekijken"
|
|
37
|
+
},
|
|
38
|
+
"menu": {
|
|
39
|
+
"moduleNavigation": "Modules en pagina's",
|
|
40
|
+
"search": "Zoeken",
|
|
41
|
+
"switchOrganization": "Van organisatie wisselen",
|
|
42
|
+
"module": "Module",
|
|
43
|
+
"dashboard": "Dashboard",
|
|
44
|
+
"settings": {
|
|
45
|
+
"title": "Instellingen"
|
|
46
|
+
},
|
|
47
|
+
"logout": "Uitloggen"
|
|
48
|
+
},
|
|
49
|
+
"dashboard": {
|
|
50
|
+
"title": "Dashboard",
|
|
51
|
+
"greeting": "Welkom terug, {name}",
|
|
52
|
+
"introduction": "Begin met het werk dat jouw aandacht nodig heeft.",
|
|
53
|
+
"notifications": "Meldingen",
|
|
54
|
+
"areas": {
|
|
55
|
+
"attention": "Heeft aandacht nodig",
|
|
56
|
+
"main": "Jouw werk",
|
|
57
|
+
"aside": "Recente activiteit"
|
|
58
|
+
},
|
|
59
|
+
"empty": {
|
|
60
|
+
"title": "Je bent helemaal bij",
|
|
61
|
+
"description": "Er is geen dashboardwerk beschikbaar voor deze organisatie."
|
|
62
|
+
},
|
|
63
|
+
"error": {
|
|
64
|
+
"title": "Dit dashboarditem is niet beschikbaar",
|
|
65
|
+
"description": "De rest van je dashboard blijft beschikbaar.",
|
|
66
|
+
"retry": "Opnieuw proberen"
|
|
67
|
+
},
|
|
68
|
+
"seo": {
|
|
69
|
+
"description": "Bekijk het werk dat jouw aandacht nodig heeft.",
|
|
70
|
+
"title": "Dashboard"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"footer": {
|
|
74
|
+
"copyright": "Alle rechten voorbehouden."
|
|
75
|
+
},
|
|
76
|
+
"nav": {
|
|
77
|
+
"admin": "Beheer",
|
|
78
|
+
"blog": "Blog",
|
|
79
|
+
"contact": "Contact"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
CREATE TABLE "account" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"account_id" text NOT NULL,
|
|
4
|
+
"provider_id" text NOT NULL,
|
|
5
|
+
"user_id" text NOT NULL,
|
|
6
|
+
"access_token" text,
|
|
7
|
+
"refresh_token" text,
|
|
8
|
+
"id_token" text,
|
|
9
|
+
"access_token_expires_at" timestamp,
|
|
10
|
+
"refresh_token_expires_at" timestamp,
|
|
11
|
+
"scope" text,
|
|
12
|
+
"password" text,
|
|
13
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
14
|
+
"updated_at" timestamp NOT NULL
|
|
15
|
+
);
|
|
16
|
+
--> statement-breakpoint
|
|
17
|
+
CREATE TABLE "invitation" (
|
|
18
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
19
|
+
"organization_id" text NOT NULL,
|
|
20
|
+
"email" text NOT NULL,
|
|
21
|
+
"role" text,
|
|
22
|
+
"status" text DEFAULT 'pending' NOT NULL,
|
|
23
|
+
"expires_at" timestamp NOT NULL,
|
|
24
|
+
"inviter_id" text NOT NULL
|
|
25
|
+
);
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
CREATE TABLE "member" (
|
|
28
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
29
|
+
"organization_id" text NOT NULL,
|
|
30
|
+
"user_id" text NOT NULL,
|
|
31
|
+
"role" text DEFAULT 'member' NOT NULL,
|
|
32
|
+
"created_at" timestamp NOT NULL
|
|
33
|
+
);
|
|
34
|
+
--> statement-breakpoint
|
|
35
|
+
CREATE TABLE "organization" (
|
|
36
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
37
|
+
"name" text NOT NULL,
|
|
38
|
+
"slug" text NOT NULL,
|
|
39
|
+
"logo" text,
|
|
40
|
+
"created_at" timestamp NOT NULL,
|
|
41
|
+
"metadata" text,
|
|
42
|
+
CONSTRAINT "organization_slug_unique" UNIQUE("slug")
|
|
43
|
+
);
|
|
44
|
+
--> statement-breakpoint
|
|
45
|
+
CREATE TABLE "organization_email_credential" (
|
|
46
|
+
"organization_id" text PRIMARY KEY NOT NULL,
|
|
47
|
+
"provider" text DEFAULT 'RESEND' NOT NULL,
|
|
48
|
+
"api_key" text,
|
|
49
|
+
"key_fingerprint" text,
|
|
50
|
+
"key_last_four" text,
|
|
51
|
+
"configured_by_id" text,
|
|
52
|
+
"removed_by_id" text,
|
|
53
|
+
"removed_at" timestamp,
|
|
54
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
55
|
+
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
56
|
+
);
|
|
57
|
+
--> statement-breakpoint
|
|
58
|
+
CREATE TABLE "session" (
|
|
59
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
60
|
+
"expires_at" timestamp NOT NULL,
|
|
61
|
+
"token" text NOT NULL,
|
|
62
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
63
|
+
"updated_at" timestamp NOT NULL,
|
|
64
|
+
"ip_address" text,
|
|
65
|
+
"user_agent" text,
|
|
66
|
+
"user_id" text NOT NULL,
|
|
67
|
+
"active_organization_id" text,
|
|
68
|
+
"impersonated_by" text,
|
|
69
|
+
CONSTRAINT "session_token_unique" UNIQUE("token")
|
|
70
|
+
);
|
|
71
|
+
--> statement-breakpoint
|
|
72
|
+
CREATE TABLE "user" (
|
|
73
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
74
|
+
"name" text NOT NULL,
|
|
75
|
+
"email" text NOT NULL,
|
|
76
|
+
"email_verified" boolean DEFAULT false NOT NULL,
|
|
77
|
+
"image" text,
|
|
78
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
79
|
+
"updated_at" timestamp DEFAULT now() NOT NULL,
|
|
80
|
+
"role" text,
|
|
81
|
+
"banned" boolean DEFAULT false,
|
|
82
|
+
"ban_reason" text,
|
|
83
|
+
"ban_expires" timestamp,
|
|
84
|
+
CONSTRAINT "user_email_unique" UNIQUE("email")
|
|
85
|
+
);
|
|
86
|
+
--> statement-breakpoint
|
|
87
|
+
CREATE TABLE "verification" (
|
|
88
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
89
|
+
"identifier" text NOT NULL,
|
|
90
|
+
"value" text NOT NULL,
|
|
91
|
+
"expires_at" timestamp NOT NULL,
|
|
92
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
93
|
+
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
94
|
+
);
|
|
95
|
+
--> statement-breakpoint
|
|
96
|
+
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
97
|
+
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
98
|
+
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
99
|
+
ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
100
|
+
ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
101
|
+
ALTER TABLE "organization_email_credential" ADD CONSTRAINT "organization_email_credential_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
102
|
+
ALTER TABLE "organization_email_credential" ADD CONSTRAINT "organization_email_credential_configured_by_id_user_id_fk" FOREIGN KEY ("configured_by_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
|
103
|
+
ALTER TABLE "organization_email_credential" ADD CONSTRAINT "organization_email_credential_removed_by_id_user_id_fk" FOREIGN KEY ("removed_by_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
|
104
|
+
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
105
|
+
CREATE INDEX "account_user_id_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
|
|
106
|
+
CREATE INDEX "account_account_id_idx" ON "account" USING btree ("account_id");--> statement-breakpoint
|
|
107
|
+
CREATE INDEX "account_provider_id_idx" ON "account" USING btree ("provider_id");--> statement-breakpoint
|
|
108
|
+
CREATE INDEX "invitation_organization_id_idx" ON "invitation" USING btree ("organization_id");--> statement-breakpoint
|
|
109
|
+
CREATE INDEX "invitation_inviter_id_idx" ON "invitation" USING btree ("inviter_id");--> statement-breakpoint
|
|
110
|
+
CREATE INDEX "invitation_email_idx" ON "invitation" USING btree ("email");--> statement-breakpoint
|
|
111
|
+
CREATE INDEX "member_organization_id_idx" ON "member" USING btree ("organization_id");--> statement-breakpoint
|
|
112
|
+
CREATE INDEX "member_user_id_idx" ON "member" USING btree ("user_id");--> statement-breakpoint
|
|
113
|
+
CREATE INDEX "organization_slug_idx" ON "organization" USING btree ("slug");--> statement-breakpoint
|
|
114
|
+
CREATE INDEX "session_user_id_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
|
|
115
|
+
CREATE INDEX "session_active_organization_id_idx" ON "session" USING btree ("active_organization_id");--> statement-breakpoint
|
|
116
|
+
CREATE INDEX "user_name_idx" ON "user" USING btree ("name");--> statement-breakpoint
|
|
117
|
+
CREATE UNIQUE INDEX "user_email_idx" ON "user" USING btree ("email");--> statement-breakpoint
|
|
118
|
+
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");--> statement-breakpoint
|
|
119
|
+
CREATE INDEX "verification_value_idx" ON "verification" USING btree ("value");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ALTER TABLE "organization" ADD COLUMN IF NOT EXISTS "organization_type" text;
|
|
2
|
+
UPDATE "organization" SET "organization_type" = 'CLIENT' WHERE "organization_type" IS NULL;
|
|
3
|
+
ALTER TABLE "organization" ALTER COLUMN "organization_type" SET NOT NULL;
|
|
4
|
+
|
|
5
|
+
DO $$ BEGIN
|
|
6
|
+
ALTER TABLE "organization" ADD CONSTRAINT "organization_type_check" CHECK ("organization_type" IN ('OWNER', 'CLIENT'));
|
|
7
|
+
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
8
|
+
END $$;
|
|
9
|
+
|
|
10
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "organization_single_owner_uidx" ON "organization" ("organization_type") WHERE "organization_type" = 'OWNER';
|
|
11
|
+
ALTER TABLE "member" ADD COLUMN IF NOT EXISTS "phone" text;
|
|
12
|
+
ALTER TABLE "member" ADD COLUMN IF NOT EXISTS "job_title" text;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "invitation" ADD COLUMN IF NOT EXISTS "created_at" timestamp DEFAULT now() NOT NULL;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
DROP INDEX IF EXISTS "organization_single_owner_uidx";
|
|
2
|
+
|
|
3
|
+
ALTER TABLE "organization"
|
|
4
|
+
DROP CONSTRAINT IF EXISTS "organization_type_check";
|
|
5
|
+
|
|
6
|
+
UPDATE "organization"
|
|
7
|
+
SET "organization_type" = 'PROVIDER'
|
|
8
|
+
WHERE "organization_type" = 'OWNER';
|
|
9
|
+
|
|
10
|
+
ALTER TABLE "organization"
|
|
11
|
+
ADD CONSTRAINT "organization_type_check"
|
|
12
|
+
CHECK ("organization_type" IN ('PROVIDER', 'CLIENT'));
|
|
13
|
+
|
|
14
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "organization_single_provider_uidx"
|
|
15
|
+
ON "organization" ("organization_type")
|
|
16
|
+
WHERE "organization_type" = 'PROVIDER';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE "portal_email_settings" (
|
|
2
|
+
"id" boolean PRIMARY KEY DEFAULT true NOT NULL CHECK ("id" = true),
|
|
3
|
+
"provider" text DEFAULT 'RESEND' NOT NULL,
|
|
4
|
+
"encrypted_api_key" text,
|
|
5
|
+
"key_fingerprint" text,
|
|
6
|
+
"key_last_four" text,
|
|
7
|
+
"from_name" text,
|
|
8
|
+
"from_email" text,
|
|
9
|
+
"default_locale" text DEFAULT 'en' NOT NULL,
|
|
10
|
+
"html_template" text,
|
|
11
|
+
"text_overrides" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
12
|
+
"configured_by_id" text REFERENCES "user"("id") ON DELETE SET NULL,
|
|
13
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
14
|
+
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
15
|
+
);
|