@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -0
  4. package/app/composables/useAutoPagination.ts +30 -0
  5. package/app/composables/useOrganization.ts +117 -0
  6. package/app/composables/usePaginatedResource.ts +167 -0
  7. package/app/composables/usePortalFeatures.ts +74 -0
  8. package/app/composables/usePortalSession.ts +20 -0
  9. package/app/composables/useUserStore.ts +460 -0
  10. package/app/middleware/auth.global.ts +32 -0
  11. package/app/plugins/auth.ts +28 -0
  12. package/app/plugins/portal-core-feature.ts +3 -0
  13. package/app/types/index.d.ts +75 -0
  14. package/app/utils/auth-client.ts +31 -0
  15. package/app/utils/permission-utils.ts +21 -0
  16. package/app/utils/random.ts +9 -0
  17. package/i18n/locales/en.json +81 -0
  18. package/i18n/locales/nl.json +81 -0
  19. package/migrations/0000_baseline.sql +119 -0
  20. package/migrations/0001_organization_types.sql +12 -0
  21. package/migrations/0002_invitation_created_at.sql +1 -0
  22. package/migrations/0003_system_admin_role.sql +5 -0
  23. package/migrations/0004_provider_organization_type.sql +16 -0
  24. package/migrations/0005_portal_email_settings.sql +15 -0
  25. package/migrations/meta/0000_snapshot.json +880 -0
  26. package/migrations/meta/_journal.json +13 -0
  27. package/nuxt.config.ts +55 -0
  28. package/package.json +66 -0
  29. package/portal.manifest.mjs +8 -0
  30. package/server/api/auth/[...].ts +18 -0
  31. package/server/api/auth/permissions.get.ts +64 -0
  32. package/server/api/notifications.ts +294 -0
  33. package/server/db/core-schema.ts +1 -0
  34. package/server/db/schema/auth-schema.ts +198 -0
  35. package/server/db/schema/index.ts +1 -0
  36. package/server/middleware/api-docs-auth.ts +26 -0
  37. package/server/middleware/auth.ts +61 -0
  38. package/server/portal.ts +212 -0
  39. package/server/routes/api-docs/openapi.json.get.ts +22 -0
  40. package/server/utils/admin-email-allowlist.ts +12 -0
  41. package/server/utils/auth.ts +326 -0
  42. package/server/utils/business-hooks.ts +28 -0
  43. package/server/utils/db.ts +18 -0
  44. package/server/utils/email-template.html +105 -0
  45. package/server/utils/email-texts.ts +110 -0
  46. package/server/utils/email.ts +37 -0
  47. package/server/utils/invitation-management.ts +29 -0
  48. package/server/utils/openapi-auth.ts +67 -0
  49. package/server/utils/openapi-contracts.ts +129 -0
  50. package/server/utils/openapi-order.ts +136 -0
  51. package/server/utils/permissions.ts +179 -0
  52. package/server/utils/portal-email.ts +424 -0
  53. package/server/utils/query-builder.ts +270 -0
  54. package/server/utils/route-meta.ts +28 -0
  55. package/shared/core-feature.ts +152 -0
  56. package/shared/feature-registry.ts +62 -0
  57. package/shared/invitation-validation.ts +8 -0
  58. package/shared/organization-avatar.ts +11 -0
  59. package/shared/permissions.ts +26 -0
  60. package/shared/portal-session.ts +17 -0
  61. package/shared/types/feature.ts +130 -0
  62. package/shared/types/index.ts +40 -0
  63. package/shared/types/sections/api.ts +134 -0
  64. package/shared/types/sections/auth.ts +71 -0
  65. package/shared/types/sections/database.ts +86 -0
  66. 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,5 @@
1
+ UPDATE "user" SET "role" = 'user' WHERE "role" IS NULL;
2
+ --> statement-breakpoint
3
+ ALTER TABLE "user" ALTER COLUMN "role" SET DEFAULT 'user';
4
+ --> statement-breakpoint
5
+ ALTER TABLE "user" ALTER COLUMN "role" SET 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
+ );