@nexttylabs/echo 0.2.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/.changeset/README.md +21 -0
- package/.changeset/config.json +11 -0
- package/.changeset/cozy-ghosts-care.md +5 -0
- package/.changeset/sharp-lines-stand.md +5 -0
- package/.changeset/sour-doodles-eat.md +5 -0
- package/.changeset/tender-moose-shop.md +5 -0
- package/.github/pull_request_template.md +13 -0
- package/.github/workflows/ci.yml +41 -0
- package/.github/workflows/publish.yml +44 -0
- package/.github/workflows/release.yml +73 -0
- package/AGENTS.md +92 -0
- package/CHANGELOG.md +13 -0
- package/Dockerfile +57 -0
- package/LICENSE +661 -0
- package/Makefile +77 -0
- package/README.md +198 -0
- package/app/(auth)/login/page.tsx +53 -0
- package/app/(auth)/register/page.tsx +48 -0
- package/app/(auth)/sign-in/page.tsx +22 -0
- package/app/(dashboard)/admin/feedback/[id]/edit/page.tsx +103 -0
- package/app/(dashboard)/admin/feedback/[id]/page.tsx +154 -0
- package/app/(dashboard)/admin/feedback/new/page.tsx +91 -0
- package/app/(dashboard)/admin/feedback/page.tsx +81 -0
- package/app/(dashboard)/admin/layout.tsx +48 -0
- package/app/(dashboard)/analytics/portal/page.tsx +30 -0
- package/app/(dashboard)/dashboard/page.tsx +133 -0
- package/app/(dashboard)/layout.tsx +69 -0
- package/app/(dashboard)/no-access/page.tsx +45 -0
- package/app/(dashboard)/settings/access/page.tsx +56 -0
- package/app/(dashboard)/settings/api-keys/page.tsx +55 -0
- package/app/(dashboard)/settings/appearance/page.tsx +40 -0
- package/app/(dashboard)/settings/branding/page.tsx +62 -0
- package/app/(dashboard)/settings/changelog/page.tsx +51 -0
- package/app/(dashboard)/settings/danger-zone/page.tsx +92 -0
- package/app/(dashboard)/settings/feedback/page.tsx +63 -0
- package/app/(dashboard)/settings/integrations/page.tsx +94 -0
- package/app/(dashboard)/settings/layout.tsx +43 -0
- package/app/(dashboard)/settings/modules/page.tsx +54 -0
- package/app/(dashboard)/settings/notifications/page.tsx +48 -0
- package/app/(dashboard)/settings/organization/page.tsx +104 -0
- package/app/(dashboard)/settings/organization/portal/access/page.tsx +22 -0
- package/app/(dashboard)/settings/organization/portal/experience/page.tsx +22 -0
- package/app/(dashboard)/settings/organization/portal/growth/page.tsx +22 -0
- package/app/(dashboard)/settings/organization/portal/layout.tsx +24 -0
- package/app/(dashboard)/settings/organization/portal/page.tsx +22 -0
- package/app/(dashboard)/settings/organizations/[orgId]/members/page.tsx +69 -0
- package/app/(dashboard)/settings/organizations/new/page.tsx +36 -0
- package/app/(dashboard)/settings/page.tsx +22 -0
- package/app/(dashboard)/settings/portal-access/page.tsx +53 -0
- package/app/(dashboard)/settings/portal-branding/page.tsx +59 -0
- package/app/(dashboard)/settings/portal-growth/page.tsx +57 -0
- package/app/(dashboard)/settings/portal-modules/page.tsx +49 -0
- package/app/(dashboard)/settings/portal-resources/page.tsx +66 -0
- package/app/(dashboard)/settings/profile/page.tsx +48 -0
- package/app/(dashboard)/settings/widgets/page.tsx +63 -0
- package/app/(public)/[organizationSlug]/changelog/page.tsx +109 -0
- package/app/(public)/[organizationSlug]/feedback/[id]/page.tsx +146 -0
- package/app/(public)/[organizationSlug]/page.tsx +160 -0
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +142 -0
- package/app/(public)/docs/page.tsx +48 -0
- package/app/(public)/feedback/[id]/not-found.tsx +33 -0
- package/app/(public)/feedback/[id]/page.tsx +102 -0
- package/app/(public)/invite/[token]/page.tsx +121 -0
- package/app/(public)/page.tsx +22 -0
- package/app/(public)/widget/[organizationId]/page.tsx +122 -0
- package/app/api/_utils.ts +29 -0
- package/app/api/admin/backup/route.ts +72 -0
- package/app/api/api-keys/[keyId]/route.ts +92 -0
- package/app/api/api-keys/route.ts +116 -0
- package/app/api/auth/[...all]/route.ts +21 -0
- package/app/api/auth/clear-session/route.ts +43 -0
- package/app/api/auth/register/handler.ts +176 -0
- package/app/api/auth/register/route.ts +26 -0
- package/app/api/docs/route.ts +28 -0
- package/app/api/feedback/[id]/comments/[commentId]/route.ts +105 -0
- package/app/api/feedback/[id]/comments/route.ts +421 -0
- package/app/api/feedback/[id]/duplicates/route.ts +285 -0
- package/app/api/feedback/[id]/handler.ts +91 -0
- package/app/api/feedback/[id]/processing-status/route.ts +199 -0
- package/app/api/feedback/[id]/reclassify/route.ts +145 -0
- package/app/api/feedback/[id]/route.ts +511 -0
- package/app/api/feedback/[id]/suggest-tags/route.ts +227 -0
- package/app/api/feedback/[id]/sync-github/route.ts +52 -0
- package/app/api/feedback/[id]/vote/route.ts +431 -0
- package/app/api/feedback/bulk/route.ts +212 -0
- package/app/api/feedback/handler.ts +138 -0
- package/app/api/feedback/route.ts +298 -0
- package/app/api/feedback/similar/route.ts +100 -0
- package/app/api/health/route.test.ts +64 -0
- package/app/api/health/route.ts +92 -0
- package/app/api/identify/jwt/route.ts +29 -0
- package/app/api/integrations/github/route.ts +196 -0
- package/app/api/internal/domain-lookup/route.ts +67 -0
- package/app/api/invitations/accept/handler.ts +101 -0
- package/app/api/invitations/accept/route.ts +29 -0
- package/app/api/notifications/preferences/route.ts +109 -0
- package/app/api/organizations/[orgId]/handler.ts +123 -0
- package/app/api/organizations/[orgId]/invitations/handler.ts +121 -0
- package/app/api/organizations/[orgId]/invitations/route.ts +29 -0
- package/app/api/organizations/[orgId]/members/[memberId]/handler.ts +208 -0
- package/app/api/organizations/[orgId]/members/[memberId]/route.ts +30 -0
- package/app/api/organizations/[orgId]/members/handler.ts +77 -0
- package/app/api/organizations/[orgId]/members/route.ts +29 -0
- package/app/api/organizations/[orgId]/route.ts +30 -0
- package/app/api/organizations/handler.ts +97 -0
- package/app/api/organizations/route.ts +29 -0
- package/app/api/tags/sync/route.ts +88 -0
- package/app/api/upload/handler.ts +79 -0
- package/app/api/upload/route.ts +37 -0
- package/app/api/v1/feedback/[id]/route.ts +276 -0
- package/app/api/v1/feedback/route.ts +250 -0
- package/app/api/v1/spec/route.ts +356 -0
- package/app/api/webhooks/[webhookId]/route.ts +213 -0
- package/app/api/webhooks/github/route.ts +158 -0
- package/app/api/webhooks/route.ts +143 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +139 -0
- package/app/health/route.ts +108 -0
- package/app/layout.tsx +60 -0
- package/bun.lock +2503 -0
- package/components/api/rate-limit-info.tsx +86 -0
- package/components/api-keys/api-key-manager.tsx +262 -0
- package/components/auth/login-form.tsx +207 -0
- package/components/auth/register-form.tsx +230 -0
- package/components/comment/comment-form.tsx +111 -0
- package/components/comment/internal-notes.tsx +219 -0
- package/components/comment/public-comments.tsx +387 -0
- package/components/component-example-client-only.tsx +29 -0
- package/components/component-example.tsx +519 -0
- package/components/dashboard/index.ts +22 -0
- package/components/dashboard/organization-switcher.tsx +96 -0
- package/components/dashboard/quick-actions.tsx +57 -0
- package/components/dashboard/recent-feedback-list.tsx +152 -0
- package/components/dashboard/stats-cards.tsx +88 -0
- package/components/dashboard/status-chart.tsx +106 -0
- package/components/example.tsx +70 -0
- package/components/feedback/attachment-list.tsx +103 -0
- package/components/feedback/auto-classification-badge.tsx +92 -0
- package/components/feedback/classification-override.tsx +64 -0
- package/components/feedback/duplicate-suggestions-inline.tsx +158 -0
- package/components/feedback/duplicate-suggestions.tsx +188 -0
- package/components/feedback/embedded-feedback-form.tsx +439 -0
- package/components/feedback/feedback-actions.tsx +160 -0
- package/components/feedback/feedback-bulk-actions.tsx +184 -0
- package/components/feedback/feedback-detail-view.tsx +321 -0
- package/components/feedback/feedback-detail.tsx +305 -0
- package/components/feedback/feedback-edit-form.tsx +131 -0
- package/components/feedback/feedback-filters.tsx +222 -0
- package/components/feedback/feedback-list-controls.tsx +433 -0
- package/components/feedback/feedback-list-item.tsx +298 -0
- package/components/feedback/feedback-list-skeleton.tsx +49 -0
- package/components/feedback/feedback-list.tsx +523 -0
- package/components/feedback/feedback-sorter.tsx +117 -0
- package/components/feedback/feedback-stats.tsx +124 -0
- package/components/feedback/file-upload.tsx +289 -0
- package/components/feedback/processing-status.tsx +161 -0
- package/components/feedback/status-history.tsx +134 -0
- package/components/feedback/status-selector.tsx +153 -0
- package/components/feedback/submit-on-behalf-form.tsx +403 -0
- package/components/feedback/tag-suggestions.tsx +212 -0
- package/components/feedback/vote-button.tsx +113 -0
- package/components/feedback/vote-list.tsx +108 -0
- package/components/integrations/github-config.tsx +200 -0
- package/components/landing/hero.tsx +150 -0
- package/components/layout/dashboard-layout.tsx +59 -0
- package/components/layout/index.ts +20 -0
- package/components/layout/language-switcher.tsx +129 -0
- package/components/layout/mobile-sidebar.tsx +66 -0
- package/components/layout/sidebar.tsx +279 -0
- package/components/portal/changelog-entry.tsx +132 -0
- package/components/portal/changelog-list.tsx +85 -0
- package/components/portal/contributor-badge.tsx +29 -0
- package/components/portal/contributors-sidebar.tsx +98 -0
- package/components/portal/create-post-dialog.tsx +247 -0
- package/components/portal/feedback-board.tsx +205 -0
- package/components/portal/feedback-post-card.tsx +198 -0
- package/components/portal/help-center.tsx +169 -0
- package/components/portal/leaderboard.tsx +29 -0
- package/components/portal/portal-header.tsx +153 -0
- package/components/portal/portal-layout.tsx +62 -0
- package/components/portal/portal-modules-panel.tsx +118 -0
- package/components/portal/portal-nav.tsx +59 -0
- package/components/portal/portal-overview.tsx +174 -0
- package/components/portal/portal-settings-nav.tsx +62 -0
- package/components/portal/portal-settings-shell.tsx +71 -0
- package/components/portal/portal-shell.tsx +62 -0
- package/components/portal/portal-tab-nav.tsx +77 -0
- package/components/portal/project-switcher.tsx +20 -0
- package/components/portal/roadmap-board.tsx +82 -0
- package/components/portal/roadmap-card.tsx +76 -0
- package/components/portal/roadmap-column.tsx +78 -0
- package/components/portal/settings-forms/access-form.tsx +194 -0
- package/components/portal/settings-forms/copy-form.tsx +95 -0
- package/components/portal/settings-forms/index.ts +23 -0
- package/components/portal/settings-forms/languages-form.tsx +223 -0
- package/components/portal/settings-forms/seo-form.tsx +156 -0
- package/components/portal/settings-forms/sharing-form.tsx +155 -0
- package/components/portal/settings-forms/theme-form.tsx +104 -0
- package/components/settings/api-keys-list.tsx +167 -0
- package/components/settings/appearance-form.tsx +71 -0
- package/components/settings/index.ts +25 -0
- package/components/settings/invite-member-form.tsx +119 -0
- package/components/settings/notification-preferences.tsx +174 -0
- package/components/settings/organization-form.tsx +165 -0
- package/components/settings/organization-members-list.tsx +197 -0
- package/components/settings/profile-form.tsx +124 -0
- package/components/settings/role-selector.tsx +57 -0
- package/components/settings/settings-sidebar.tsx +115 -0
- package/components/shared/pagination.tsx +215 -0
- package/components/ui/alert-dialog.tsx +201 -0
- package/components/ui/alert.tsx +75 -0
- package/components/ui/avatar.tsx +126 -0
- package/components/ui/badge.tsx +62 -0
- package/components/ui/button.tsx +77 -0
- package/components/ui/card.tsx +111 -0
- package/components/ui/combobox.tsx +311 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/dropdown-menu.tsx +272 -0
- package/components/ui/field.tsx +256 -0
- package/components/ui/input-group.tsx +164 -0
- package/components/ui/input.tsx +36 -0
- package/components/ui/label.tsx +41 -0
- package/components/ui/pagination.tsx +142 -0
- package/components/ui/select.tsx +202 -0
- package/components/ui/separator.tsx +45 -0
- package/components/ui/sheet.tsx +151 -0
- package/components/ui/skeleton.tsx +32 -0
- package/components/ui/switch.tsx +49 -0
- package/components/ui/table.tsx +118 -0
- package/components/ui/tabs.tsx +107 -0
- package/components/ui/textarea.tsx +35 -0
- package/components/ui/tooltip.tsx +78 -0
- package/components/widget/widget-form.tsx +439 -0
- package/components.json +24 -0
- package/db/init/01-init.sql +13 -0
- package/docker-compose.dev.yml +26 -0
- package/docker-compose.yml +98 -0
- package/docs/architecture.md +259 -0
- package/docs/component-inventory.md +261 -0
- package/docs/database-migrations.md +76 -0
- package/docs/development-guide.md +209 -0
- package/docs/e2e-user-flows.csv +31 -0
- package/docs/er-diagram-feedback.mmd +138 -0
- package/docs/er-diagram.mmd +281 -0
- package/docs/i18n-check-report.md +296 -0
- package/docs/index.md +214 -0
- package/docs/logic-chain.md +94 -0
- package/docs/plans/2026-01-02-database-migration-scripts.md +496 -0
- package/docs/plans/2026-01-02-user-login-design.md +37 -0
- package/docs/plans/2026-01-02-user-login.md +437 -0
- package/docs/plans/2026-01-02-user-registration-design.md +47 -0
- package/docs/plans/2026-01-02-user-registration.md +628 -0
- package/docs/plans/2026-01-03-roles-permissions-design.md +20 -0
- package/docs/plans/2026-01-03-roles-permissions.md +266 -0
- package/docs/plans/2026-01-05-authentication-middleware.md +207 -0
- package/docs/plans/2026-01-05-member-removal.md +186 -0
- package/docs/plans/2026-01-05-organization-creation.md +374 -0
- package/docs/plans/2026-01-05-rbac-middleware.md +112 -0
- package/docs/plans/2026-01-05-role-configuration.md +441 -0
- package/docs/plans/2026-01-06-file-upload-support.md +804 -0
- package/docs/plans/2026-01-06-permission-check-hook.md +155 -0
- package/docs/plans/2026-01-06-resource-ownership-check.md +231 -0
- package/docs/plans/2026-01-07-feedback-tracking-link.md +459 -0
- package/docs/plans/2026-01-09-logout-redirect-design.md +52 -0
- package/docs/plans/2026-01-09-phase2-3-plan.md +654 -0
- package/docs/plans/2026-01-09-portal-execution-plan.md +408 -0
- package/docs/plans/2026-01-09-project-delete-feature-design.md +163 -0
- package/docs/plans/2026-01-09-project-delete-implementation.md +451 -0
- package/docs/plans/2026-01-09-project-edit-delete-design.md +52 -0
- package/docs/plans/2026-01-09-settings-center-design.md +114 -0
- package/docs/plans/2026-01-09-settings-center.md +948 -0
- package/docs/plans/2026-01-10-organization-only-design.md +66 -0
- package/docs/plans/2026-01-10-organization-only-implementation.md +433 -0
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +18 -0
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +296 -0
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +173 -0
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +82 -0
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +521 -0
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +75 -0
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +293 -0
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +180 -0
- package/docs/plans/2026-01-16-e2e-test-fixes.md +158 -0
- package/docs/plans/2026-01-17-admin-feedback-filters.md +214 -0
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +453 -0
- package/docs/plans/2026-01-18-changesets-design.md +40 -0
- package/docs/product_changes.md +37 -0
- package/docs/project-overview.md +159 -0
- package/docs/project-scan-report.json +104 -0
- package/docs/route-role-visibility.md +51 -0
- package/docs/source-tree-analysis.md +150 -0
- package/docs/testing/delete-project-manual-tests.md +18 -0
- package/docs/user-story-tracking.md +191 -0
- package/drizzle.config.ts +32 -0
- package/eslint.config.mjs +19 -0
- package/hooks/use-permissions.ts +56 -0
- package/i18n/config.ts +45 -0
- package/i18n/request.ts +28 -0
- package/i18n/resolve-locale.ts +38 -0
- package/lib/api/errors.ts +62 -0
- package/lib/auth/cli-config.ts +35 -0
- package/lib/auth/client.ts +20 -0
- package/lib/auth/config.ts +55 -0
- package/lib/auth/jwt-identity.ts +21 -0
- package/lib/auth/org-context.ts +71 -0
- package/lib/auth/organization.ts +107 -0
- package/lib/auth/permissions.ts +87 -0
- package/lib/auth/session.ts +23 -0
- package/lib/config/rate-limits.ts +64 -0
- package/lib/dashboard/get-dashboard-stats.ts +136 -0
- package/lib/db/index.ts +41 -0
- package/lib/db/migrate.test.ts +49 -0
- package/lib/db/migrate.ts +62 -0
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +53 -0
- package/lib/db/migrations/0001_wandering_sunfire.sql +27 -0
- package/lib/db/migrations/0002_shallow_speedball.sql +1 -0
- package/lib/db/migrations/0003_add_org_description.sql +1 -0
- package/lib/db/migrations/0003_boring_wild_pack.sql +13 -0
- package/lib/db/migrations/0004_windy_tyrannus.sql +27 -0
- package/lib/db/migrations/0005_perpetual_doorman.sql +5 -0
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +13 -0
- package/lib/db/migrations/0007_clever_captain_cross.sql +14 -0
- package/lib/db/migrations/0008_sparkling_pandemic.sql +2 -0
- package/lib/db/migrations/0009_happy_black_tom.sql +29 -0
- package/lib/db/migrations/0010_kind_junta.sql +8 -0
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +25 -0
- package/lib/db/migrations/0012_giant_power_man.sql +24 -0
- package/lib/db/migrations/0013_damp_titanium_man.sql +17 -0
- package/lib/db/migrations/0014_blue_alice.sql +18 -0
- package/lib/db/migrations/0015_webhook_tables.sql +41 -0
- package/lib/db/migrations/0016_github_integration.sql +30 -0
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +22 -0
- package/lib/db/migrations/0017_slimy_inhumans.sql +6 -0
- package/lib/db/migrations/0018_same_spitfire.sql +1 -0
- package/lib/db/migrations/0019_jittery_loners.sql +16 -0
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +14 -0
- package/lib/db/migrations/meta/0000_snapshot.json +374 -0
- package/lib/db/migrations/meta/0001_snapshot.json +553 -0
- package/lib/db/migrations/meta/0002_snapshot.json +560 -0
- package/lib/db/migrations/meta/0003_snapshot.json +650 -0
- package/lib/db/migrations/meta/0004_snapshot.json +852 -0
- package/lib/db/migrations/meta/0005_snapshot.json +900 -0
- package/lib/db/migrations/meta/0006_snapshot.json +1011 -0
- package/lib/db/migrations/meta/0007_snapshot.json +1125 -0
- package/lib/db/migrations/meta/0008_snapshot.json +1146 -0
- package/lib/db/migrations/meta/0009_snapshot.json +1386 -0
- package/lib/db/migrations/meta/0010_snapshot.json +1419 -0
- package/lib/db/migrations/meta/0011_snapshot.json +1615 -0
- package/lib/db/migrations/meta/0012_snapshot.json +1805 -0
- package/lib/db/migrations/meta/0013_snapshot.json +1948 -0
- package/lib/db/migrations/meta/0014_snapshot.json +2082 -0
- package/lib/db/migrations/meta/0015_snapshot.json +2476 -0
- package/lib/db/migrations/meta/0016_snapshot.json +2633 -0
- package/lib/db/migrations/meta/0017_snapshot.json +2680 -0
- package/lib/db/migrations/meta/0018_snapshot.json +2686 -0
- package/lib/db/migrations/meta/0019_snapshot.json +2741 -0
- package/lib/db/migrations/meta/_journal.json +146 -0
- package/lib/db/schema/ai-processing.ts +90 -0
- package/lib/db/schema/api-keys.ts +61 -0
- package/lib/db/schema/attachments.ts +48 -0
- package/lib/db/schema/auth.ts +111 -0
- package/lib/db/schema/comments.ts +74 -0
- package/lib/db/schema/duplicates.ts +80 -0
- package/lib/db/schema/feedback.ts +88 -0
- package/lib/db/schema/github-integrations.ts +66 -0
- package/lib/db/schema/index.ts +35 -0
- package/lib/db/schema/invitations.ts +32 -0
- package/lib/db/schema/notifications.ts +85 -0
- package/lib/db/schema/organization-members.ts +37 -0
- package/lib/db/schema/organization-settings.ts +134 -0
- package/lib/db/schema/organizations.ts +30 -0
- package/lib/db/schema/projects.ts +145 -0
- package/lib/db/schema/status-history.ts +63 -0
- package/lib/db/schema/tags.ts +194 -0
- package/lib/db/schema/user-profiles.ts +31 -0
- package/lib/db/schema/votes.ts +60 -0
- package/lib/db/schema/webhooks.ts +106 -0
- package/lib/feedback/filters.ts +28 -0
- package/lib/feedback/find-similar.ts +49 -0
- package/lib/feedback/get-feedback-by-id.ts +159 -0
- package/lib/feedback/prefill.ts +51 -0
- package/lib/http/get-request-url.ts +28 -0
- package/lib/integrations/github.ts +159 -0
- package/lib/invitations.ts +22 -0
- package/lib/logger.test.ts +31 -0
- package/lib/logger.ts +58 -0
- package/lib/middleware/api-key.ts +126 -0
- package/lib/middleware/rate-limit-keys.ts +47 -0
- package/lib/middleware/rate-limit.ts +148 -0
- package/lib/middleware/rbac.ts +39 -0
- package/lib/middleware/request-id.test.ts +28 -0
- package/lib/middleware/request-id.ts +30 -0
- package/lib/middleware/request-logger.test.ts +36 -0
- package/lib/middleware/request-logger.ts +41 -0
- package/lib/middleware/with-rate-limit.ts +33 -0
- package/lib/portal/analytics.ts +20 -0
- package/lib/portal/contributors.ts +27 -0
- package/lib/portal/i18n.ts +20 -0
- package/lib/portal/leaderboard-settings.ts +20 -0
- package/lib/portal/modules.ts +20 -0
- package/lib/portal/portal-copy.ts +20 -0
- package/lib/portal/public-context.tsx +110 -0
- package/lib/portal/seo.ts +20 -0
- package/lib/portal/settings-context.ts +56 -0
- package/lib/portal/sharing.ts +20 -0
- package/lib/portal/sorting.ts +20 -0
- package/lib/portal/theme.ts +20 -0
- package/lib/services/ai/classifier.ts +296 -0
- package/lib/services/ai/duplicate-detector.ts +255 -0
- package/lib/services/ai/tag-suggester.ts +108 -0
- package/lib/services/api-keys.ts +164 -0
- package/lib/services/backup.ts +173 -0
- package/lib/services/email/templates.ts +158 -0
- package/lib/services/email.ts +68 -0
- package/lib/services/github-sync.ts +205 -0
- package/lib/services/notifications/index.ts +224 -0
- package/lib/services/portal-settings.ts +157 -0
- package/lib/swagger/config.ts +296 -0
- package/lib/swagger/generate.ts +400 -0
- package/lib/upload/file-validator.ts +52 -0
- package/lib/upload/storage.ts +59 -0
- package/lib/utils/format.ts +26 -0
- package/lib/utils/slug.ts +28 -0
- package/lib/utils.ts +23 -0
- package/lib/validations/auth.ts +56 -0
- package/lib/validations/comment.ts +44 -0
- package/lib/validations/feedback.ts +51 -0
- package/lib/validations/invitations.ts +23 -0
- package/lib/validations/organizations.ts +34 -0
- package/lib/validations/projects.ts +49 -0
- package/lib/validators/feedback.ts +57 -0
- package/lib/validators/index.ts +18 -0
- package/lib/webhooks/events.ts +73 -0
- package/lib/webhooks/index.ts +21 -0
- package/lib/webhooks/retry.ts +188 -0
- package/lib/webhooks/sender.ts +183 -0
- package/lib/webhooks/verify.ts +37 -0
- package/lib/workers/feedback-processor.ts +255 -0
- package/messages/en.json +965 -0
- package/messages/jp.json +862 -0
- package/messages/zh-CN.json +855 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +66 -0
- package/package.json +84 -0
- package/playwright.config.ts +44 -0
- package/postcss.config.mjs +7 -0
- package/proxy.test.ts +131 -0
- package/proxy.ts +190 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo-64.svg +5 -0
- package/public/logo.svg +5 -0
- package/public/next.svg +1 -0
- package/public/openapi.json +673 -0
- package/public/uploads/.gitkeep +0 -0
- package/public/uploads/02695701-ded0-4c81-8a21-9326c1d65448.pdf +1 -0
- package/public/uploads/178843ea-2780-48ef-8988-f4cba442e4cb.pdf +1 -0
- package/public/uploads/24b0a9ef-da93-49da-934f-637f89c7871d.pdf +1 -0
- package/public/uploads/7a11626d-a8e4-4b91-a8eb-20b6213b0a5a.pdf +1 -0
- package/public/uploads/b0703f4d-6e7b-4aab-8191-1a7b15f1b8ee.pdf +1 -0
- package/public/uploads/c8de0aed-4d3a-44aa-83bb-6594b7a2ddb3.pdf +1 -0
- package/public/uploads/e4cce295-0d85-4525-a1b0-a61c45722e26.pdf +1 -0
- package/public/uploads/eb4df45e-563c-48b8-9c68-c18212312426.pdf +1 -0
- package/public/vercel.svg +1 -0
- package/public/widget/embed.js +249 -0
- package/public/window.svg +1 -0
- package/scripts/backup-db.sh +57 -0
- package/scripts/backup-db.ts +24 -0
- package/scripts/generate-openapi.ts +22 -0
- package/scripts/migration-helper.ts +39 -0
- package/scripts/pre-deploy.ts +75 -0
- package/scripts/restore-db.sh +60 -0
- package/scripts/rollback.ts +72 -0
- package/scripts/seed-tags.ts +48 -0
- package/tests/api/feedback-bulk.test.ts +47 -0
- package/tests/api/feedback-by-id.test.ts +67 -0
- package/tests/api/feedback-comments-route-import.test.ts +26 -0
- package/tests/api/feedback-create.test.ts +71 -0
- package/tests/api/feedback-delete.test.ts +160 -0
- package/tests/api/feedback-filter.test.ts +250 -0
- package/tests/api/feedback-list.test.ts +234 -0
- package/tests/api/feedback-route-assignee-condition.test.ts +32 -0
- package/tests/api/feedback-similar.test.ts +46 -0
- package/tests/api/feedback-sort.test.ts +261 -0
- package/tests/api/feedback-status-enum.test.ts +49 -0
- package/tests/api/feedback-status-filter.test.ts +117 -0
- package/tests/api/feedback-submit-on-behalf.test.ts +269 -0
- package/tests/api/feedback.test.ts +175 -0
- package/tests/api/identify-jwt.test.ts +25 -0
- package/tests/api/invitation-accept.test.ts +213 -0
- package/tests/api/organization-invitations.test.ts +186 -0
- package/tests/api/organization-members-list.test.ts +79 -0
- package/tests/api/organization-members.test.ts +340 -0
- package/tests/api/organizations.test.ts +149 -0
- package/tests/api/register.test.ts +112 -0
- package/tests/api/upload.test.ts +103 -0
- package/tests/api/vote.test.ts +82 -0
- package/tests/app/admin-feedback-detail-page.test.tsx +25 -0
- package/tests/app/admin-feedback-list-page.test.tsx +25 -0
- package/tests/app/admin-feedback-new-page.test.tsx +25 -0
- package/tests/app/health-route-helpers.test.ts +27 -0
- package/tests/app/login-page.test.ts +26 -0
- package/tests/app/portal-page.test.ts +29 -0
- package/tests/app/project-portal-overview.test.tsx +25 -0
- package/tests/app/widget-page-import.test.ts +25 -0
- package/tests/components/create-post-dialog-defaults.test.ts +43 -0
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +27 -0
- package/tests/components/feedback/embedded-feedback-form.test.tsx +96 -0
- package/tests/components/feedback/feedback-detail.test.tsx +25 -0
- package/tests/components/feedback/feedback-stats.test.tsx +49 -0
- package/tests/components/feedback-bulk-actions.test.tsx +39 -0
- package/tests/components/feedback-i18n-keys.test.ts +70 -0
- package/tests/components/feedback-list-controls-compile.test.ts +25 -0
- package/tests/components/feedback-list-controls.test.tsx +204 -0
- package/tests/components/feedback-list-item.test.tsx +67 -0
- package/tests/components/landing/hero.test.tsx +46 -0
- package/tests/components/layout/language-switcher.test.tsx +25 -0
- package/tests/components/layout/sidebar.test.tsx +157 -0
- package/tests/components/login-form.test.ts +25 -0
- package/tests/components/organization-form.test.ts +32 -0
- package/tests/components/organization-switcher.test.ts +25 -0
- package/tests/components/pagination.test.tsx +43 -0
- package/tests/components/portal-overview.test.tsx +25 -0
- package/tests/components/profile-form.test.tsx +139 -0
- package/tests/components/role-selector.test.ts +31 -0
- package/tests/components/status-chart.test.tsx +90 -0
- package/tests/e2e/auth.e2e.ts +323 -0
- package/tests/e2e/feedback-actions.e2e.ts +471 -0
- package/tests/e2e/feedback-attachment.e2e.ts +168 -0
- package/tests/e2e/feedback-customer.e2e.ts +226 -0
- package/tests/e2e/feedback-management.e2e.ts +565 -0
- package/tests/e2e/feedback-submit.e2e.ts +133 -0
- package/tests/e2e/feedback-view.e2e.ts +297 -0
- package/tests/e2e/fixtures/test-data.ts +235 -0
- package/tests/e2e/health-check.e2e.ts +230 -0
- package/tests/e2e/helpers/test-utils-helpers.test.ts +43 -0
- package/tests/e2e/helpers/test-utils.ts +298 -0
- package/tests/e2e/integration-placeholders.e2e.ts +199 -0
- package/tests/e2e/organization.e2e.ts +292 -0
- package/tests/e2e/permissions.e2e.ts +424 -0
- package/tests/e2e/project-widget.e2e.ts +63 -0
- package/tests/feedback/filters.test.ts +29 -0
- package/tests/hooks/use-permissions.test.ts +52 -0
- package/tests/lib/ai/classifier.test.ts +104 -0
- package/tests/lib/ai/duplicate-detector.test.ts +234 -0
- package/tests/lib/attachments-schema.test.ts +30 -0
- package/tests/lib/auth/session.test.ts +49 -0
- package/tests/lib/auth-client.test.ts +37 -0
- package/tests/lib/auth-config.test.ts +26 -0
- package/tests/lib/feedback-prefill.test.ts +52 -0
- package/tests/lib/feedback-processor.test.ts +41 -0
- package/tests/lib/feedback-schema.test.ts +33 -0
- package/tests/lib/file-validator.test.ts +48 -0
- package/tests/lib/get-feedback-by-id.test.ts +37 -0
- package/tests/lib/invitations.test.ts +35 -0
- package/tests/lib/login-schema.test.ts +36 -0
- package/tests/lib/org-context.test.ts +95 -0
- package/tests/lib/organization-access.test.ts +44 -0
- package/tests/lib/organization-member-role-schema.test.ts +41 -0
- package/tests/lib/permissions.test.ts +88 -0
- package/tests/lib/portal-analytics.test.ts +25 -0
- package/tests/lib/portal-contributors.test.ts +25 -0
- package/tests/lib/portal-copy.test.ts +27 -0
- package/tests/lib/portal-i18n.test.ts +30 -0
- package/tests/lib/portal-leaderboard-settings.test.ts +25 -0
- package/tests/lib/portal-modules.test.ts +25 -0
- package/tests/lib/portal-seo.test.ts +25 -0
- package/tests/lib/portal-sharing.test.ts +25 -0
- package/tests/lib/portal-sorting.test.ts +25 -0
- package/tests/lib/portal-theme.test.ts +25 -0
- package/tests/lib/rate-limit.test.ts +142 -0
- package/tests/lib/resolve-locale.test.ts +34 -0
- package/tests/lib/services/backup.test.ts +145 -0
- package/tests/lib/user-organizations.test.ts +42 -0
- package/tests/lib/user-role-schema.test.ts +33 -0
- package/tests/lib/user-schema.test.ts +25 -0
- package/tests/setup.ts +74 -0
- package/tsconfig.json +34 -0
- package/types/bun-test.d.ts +31 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Echo 用户故事追踪清单(按用户旅程串联)
|
|
2
|
+
|
|
3
|
+
> 生成日期:2026-01-09
|
|
4
|
+
> 说明:基于 PRD 的用户旅程与现有实现梳理。缺失表示当前未发现对应页面/功能实现。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 旅程 1:Sarah(终端用户)— 让她的声音被听见
|
|
9
|
+
|
|
10
|
+
**旅程摘要**:用户在产品内提交反馈、上传截图、获取跟踪链接、收到状态通知,并在问题解决后获得正向回馈。
|
|
11
|
+
|
|
12
|
+
| ID | 用户故事 | 旅程步骤 | 期望能力 | 当前实现 | 对应页面/组件 | 状态 |
|
|
13
|
+
|---|---|---|---|---|---|---|
|
|
14
|
+
| J1-US1 | 作为终端用户,我希望在产品中提交反馈,以便让团队知道我的问题/需求 | 提交反馈 | 嵌入式反馈表单(标题、描述、类型、优先级) | ✅ | `app/widget/[organizationId]/page.tsx` | 已实现 |
|
|
15
|
+
| J1-US2 | 作为终端用户,我希望上传截图/附件,以便说明问题 | 提交反馈 | 附件上传 | ⚠ | 组件存在:`components/feedback/embedded-feedback-form.tsx`,但未挂接到路由 | 部分缺失 |
|
|
16
|
+
| J1-US3 | 作为终端用户,我希望获得反馈跟踪链接,以便随时查看进展 | 提交后 | 跟踪链接/详情页 | ✅ | `app/feedback/[id]/page.tsx` | 已实现 |
|
|
17
|
+
| J1-US4 | 作为终端用户,我希望收到状态变更通知,以便知道处理进度 | 处理阶段 | 邮件/通知 | ❌ | 仅有通知设置页 `app/(dashboard)/settings/notifications/page.tsx` | 缺失 |
|
|
18
|
+
| J1-US5 | 作为终端用户,我希望为其他人的反馈投票,以便表达支持 | 处理中 | 投票 | ⚠ | 后台列表有投票:`app/(dashboard)/feedback/page.tsx`;缺少公开反馈列表页 | 部分缺失 |
|
|
19
|
+
| J1-US6 | 作为终端用户,我希望在问题修复后得到感谢/反馈,以便感受到被重视 | 完成后 | 感谢机制 | ❌ | 未发现页面/功能 | 缺失 |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 旅程 2:Alex(产品经理)— 基于真实需求做决策
|
|
24
|
+
|
|
25
|
+
**旅程摘要**:产品经理查看仪表盘与反馈列表,按投票/类型/状态筛选,进入详情评估收入影响,并将需求加入路线图、导出报告。
|
|
26
|
+
|
|
27
|
+
| ID | 用户故事 | 旅程步骤 | 期望能力 | 当前实现 | 对应页面/组件 | 状态 |
|
|
28
|
+
|---|---|---|---|---|---|---|
|
|
29
|
+
| J2-US1 | 作为产品经理,我希望看到反馈统计仪表盘,以便快速了解反馈概况 | 打开后台 | 仪表盘 | ✅ | `app/(dashboard)/dashboard/page.tsx` | 已实现 |
|
|
30
|
+
| J2-US2 | 作为产品经理,我希望按投票/类型/状态筛选反馈,以便聚焦高优先级需求 | 管理反馈 | 筛选/排序 | ✅ | `app/(dashboard)/feedback/page.tsx` | 已实现 |
|
|
31
|
+
| J2-US3 | 作为产品经理,我希望查看反馈详情(投票、附件、状态),以便做决策 | 管理反馈 | 反馈详情 | ✅ | `app/admin/feedback/[id]/page.tsx` | 已实现 |
|
|
32
|
+
| J2-US4 | 作为产品经理,我希望将反馈加入路线图,以便安排版本规划 | 规划 | 路线图/泳道 | ❌ | 未发现页面/功能 | 缺失 |
|
|
33
|
+
| J2-US5 | 作为产品经理,我希望导出报告,以便在会议中展示 | 复盘/汇报 | 报告导出 | ❌ | 未发现页面/功能 | 缺失 |
|
|
34
|
+
| J2-US6 | 作为产品经理,我希望看到收入影响数据,以便做更合理的优先级决策 | 评估 | CRM/收入影响 | ❌ | 未发现页面/功能 | 缺失 |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 旅程 3:Kevin(开发者)— 理解用户真实场景
|
|
39
|
+
|
|
40
|
+
**旅程摘要**:开发者从反馈详情了解背景、查看附件/评论/内部备注,并将需求同步到项目管理工具。
|
|
41
|
+
|
|
42
|
+
| ID | 用户故事 | 旅程步骤 | 期望能力 | 当前实现 | 对应页面/组件 | 状态 |
|
|
43
|
+
|---|---|---|---|---|---|---|
|
|
44
|
+
| J3-US1 | 作为开发者,我希望查看反馈详情与附件,以便理解真实场景 | 查看需求 | 反馈详情 + 附件 | ✅ | `app/admin/feedback/[id]/page.tsx` | 已实现 |
|
|
45
|
+
| J3-US2 | 作为开发者,我希望看到产品经理的内部备注,以便理解技术要求 | 协作 | 内部备注 | ✅ | `app/admin/feedback/[id]/page.tsx`(InternalNotes) | 已实现 |
|
|
46
|
+
| J3-US3 | 作为开发者,我希望在反馈页与用户互动评论,以便澄清需求 | 澄清 | 评论互动 | ✅ | `app/feedback/[id]/page.tsx`(公开评论) | 已实现 |
|
|
47
|
+
| J3-US4 | 作为开发者,我希望将反馈同步到 Linear/Jira,以便纳入研发流程 | 集成 | 项目管理工具集成 | ❌ | 未发现页面/功能 | 缺失 |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 旅程 4:Emily(客服)— 让客户感到被重视
|
|
52
|
+
|
|
53
|
+
**旅程摘要**:客服代表客户提交反馈,向客户发送公开跟踪链接,后续批量通知状态。
|
|
54
|
+
|
|
55
|
+
| ID | 用户故事 | 旅程步骤 | 期望能力 | 当前实现 | 对应页面/组件 | 状态 |
|
|
56
|
+
|---|---|---|---|---|---|---|
|
|
57
|
+
| J4-US1 | 作为客服,我希望代客户提交反馈,以便高效转述需求 | 提交反馈 | 代提交通道 | ✅ | `app/(dashboard)/feedback/new/page.tsx` | 已实现 |
|
|
58
|
+
| J4-US2 | 作为客服,我希望记录客户信息,以便后续联系 | 提交反馈 | 客户信息字段 | ✅ | `app/(dashboard)/feedback/new/page.tsx` | 已实现 |
|
|
59
|
+
| J4-US3 | 作为客服,我希望给客户一个公开反馈链接,以便客户跟踪进展 | 提交后 | 公开链接 | ✅ | `app/feedback/[id]/page.tsx` | 已实现 |
|
|
60
|
+
| J4-US4 | 作为客服,我希望批量通知相关客户状态变更,以便提升满意度 | 通知 | 批量通知 | ❌ | 未发现页面/功能 | 缺失 |
|
|
61
|
+
| J4-US5 | 作为客服,我希望与支持工具(Intercom/Zendesk)集成,以便复用现有流程 | 集成 | 支持工具集成 | ❌ | 未发现页面/功能 | 缺失 |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 旅程 5:David(管理员)— 10 分钟部署,完全掌控
|
|
66
|
+
|
|
67
|
+
**旅程摘要**:管理员完成组织创建、成员邀请、Portal 与 widget 配置;需要白标配置与运维能力。
|
|
68
|
+
|
|
69
|
+
| ID | 用户故事 | 旅程步骤 | 期望能力 | 当前实现 | 对应页面/组件 | 状态 |
|
|
70
|
+
|---|---|---|---|---|---|---|
|
|
71
|
+
| J5-US1 | 作为管理员,我希望创建组织,以便管理不同团队 | 初始设置 | 组织创建 | ✅ | `app/(dashboard)/settings/organizations/new/page.tsx` | 已实现 |
|
|
72
|
+
| J5-US2 | 作为管理员,我希望邀请成员并设置角色,以便建立协作 | 初始设置 | 成员邀请/角色管理 | ✅ | `app/(dashboard)/settings/organizations/[orgId]/members/page.tsx` | 已实现 |
|
|
73
|
+
| J5-US3 | 作为管理员,我希望配置 Portal,以便对外展示反馈入口 | 配置 | Portal 设置 | ✅ | `app/(dashboard)/settings/organization/portal/page.tsx` | 已实现 |
|
|
74
|
+
| J5-US4 | 作为管理员,我希望配置反馈 Widget 以便嵌入产品 | 配置 | Widget 设置 | ❌ | 未发现页面/功能 | 缺失 |
|
|
75
|
+
| J5-US5 | 作为管理员,我希望配置白标域名,以便匹配品牌 | 配置 | 白标域名 | ❌ | 未发现页面/功能 | 缺失 |
|
|
76
|
+
| J5-US6 | 作为管理员,我希望有初始设置向导,以便快速完成部署配置 | 初始设置 | Setup Wizard | ❌ | 未发现页面/功能 | 缺失 |
|
|
77
|
+
| J5-US7 | 作为管理员,我希望有备份/更新工具,以便安全自托管 | 运维 | 自托管管理 | ❌ | 未发现页面/功能 | 缺失 |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 旅程 6:Ryan(API 消费者)— 完美的自动化集成
|
|
82
|
+
|
|
83
|
+
**旅程摘要**:通过 API 文档、API 密钥、Webhook 完成自动化集成。
|
|
84
|
+
|
|
85
|
+
| ID | 用户故事 | 旅程步骤 | 期望能力 | 当前实现 | 对应页面/组件 | 状态 |
|
|
86
|
+
|---|---|---|---|---|---|---|
|
|
87
|
+
| J6-US1 | 作为开发者,我希望查看 API 文档,以便调用接口 | 集成准备 | API 文档 | ✅ | `app/docs/page.tsx` | 已实现 |
|
|
88
|
+
| J6-US2 | 作为开发者,我希望管理 API Key,以便安全调用 | 集成准备 | API 密钥管理 | ❌ | 仅有 API 路由 `app/api/api-keys/route.ts` | 缺失 |
|
|
89
|
+
| J6-US3 | 作为开发者,我希望配置 Webhook,以便事件驱动通知 | 自动化 | Webhook 管理 | ❌ | 仅有 API 路由 `app/api/webhooks/route.ts` | 缺失 |
|
|
90
|
+
| J6-US4 | 作为开发者,我希望 Webhook 支持签名验证与速率限制 | 安全 | 安全/限流 | ❌ | 未发现页面/功能 | 缺失 |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 旅程 7:Lisa(运维)— 自托管不可怕
|
|
95
|
+
|
|
96
|
+
**旅程摘要**:运维人员需要健康检查、指标、日志、备份、迁移、部署文档支持。
|
|
97
|
+
|
|
98
|
+
| ID | 用户故事 | 旅程步骤 | 期望能力 | 当前实现 | 对应页面/组件 | 状态 |
|
|
99
|
+
|---|---|---|---|---|---|---|
|
|
100
|
+
| J7-US1 | 作为运维,我希望有健康检查端点,以便监控服务 | 监控 | /health | ⚠ | 仅有 API 路由 `app/api/health/route.ts` | 部分缺失 |
|
|
101
|
+
| J7-US2 | 作为运维,我希望有 Prometheus 指标端点,以便采集监控数据 | 监控 | /metrics | ❌ | 未发现页面/功能 | 缺失 |
|
|
102
|
+
| J7-US3 | 作为运维,我希望有结构化日志与告警配置,以便快速排障 | 监控 | 日志/告警 | ❌ | 未发现页面/功能 | 缺失 |
|
|
103
|
+
| J7-US4 | 作为运维,我希望有备份与迁移工具,以便保障数据安全 | 运维 | 备份/迁移 | ❌ | 未发现页面/功能 | 缺失 |
|
|
104
|
+
| J7-US5 | 作为运维,我希望有部署/故障排除文档,以便快速恢复 | 运维 | 运维文档 | ❌ | 未发现页面/功能 | 缺失 |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 统计概览
|
|
109
|
+
|
|
110
|
+
- 已实现:17
|
|
111
|
+
- 部分缺失:4
|
|
112
|
+
- 缺失:16
|
|
113
|
+
|
|
114
|
+
> 注:统计为粗略计数,后续可在需求管理中按模块/阶段细化拆分。
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 用户操作说明(按用户故事 ID,含入口路径)
|
|
119
|
+
|
|
120
|
+
> 说明:入口路径以当前项目路由为准;若标记“缺失/部分缺失”,表示无完整可操作入口。
|
|
121
|
+
|
|
122
|
+
### 旅程 1:Sarah(终端用户)
|
|
123
|
+
- **J1-US1**:打开嵌入的 Widget(iframe 页面)→ 填写并提交。
|
|
124
|
+
- 入口:`/widget/[organizationId]`
|
|
125
|
+
- **J1-US2**:暂无可操作入口(缺失)。
|
|
126
|
+
- 备注:附件上传组件存在但未挂接到路由。
|
|
127
|
+
- **J1-US3**:提交后进入反馈详情页 → 点击“复制链接”获取跟踪链接。
|
|
128
|
+
- 入口:`/feedback/[id]`
|
|
129
|
+
- **J1-US4**:暂无可操作入口(缺失)。
|
|
130
|
+
- 备注:仅有通知设置页。
|
|
131
|
+
- **J1-US5**:目前仅后台列表可投票;公开列表缺失。
|
|
132
|
+
- 入口(后台):`/feedback`
|
|
133
|
+
- **J1-US6**:暂无可操作入口(缺失)。
|
|
134
|
+
|
|
135
|
+
### 旅程 2:Alex(产品经理)
|
|
136
|
+
- **J2-US1**:登录后台 → 查看仪表盘。
|
|
137
|
+
- 入口:`/dashboard`
|
|
138
|
+
- **J2-US2**:进入反馈管理 → 使用筛选与排序控件。
|
|
139
|
+
- 入口:`/feedback`
|
|
140
|
+
- **J2-US3**:在反馈列表点击进入详情。
|
|
141
|
+
- 入口:`/admin/feedback/[id]`
|
|
142
|
+
- **J2-US4**:暂无可操作入口(缺失)。
|
|
143
|
+
- **J2-US5**:暂无可操作入口(缺失)。
|
|
144
|
+
- **J2-US6**:暂无可操作入口(缺失)。
|
|
145
|
+
|
|
146
|
+
### 旅程 3:Kevin(开发者)
|
|
147
|
+
- **J3-US1**:后台反馈列表进入详情查看附件。
|
|
148
|
+
- 入口:`/admin/feedback/[id]`
|
|
149
|
+
- **J3-US2**:在反馈详情页查看/记录内部备注。
|
|
150
|
+
- 入口:`/admin/feedback/[id]`
|
|
151
|
+
- **J3-US3**:在公开反馈详情页参与评论(登录或匿名)。
|
|
152
|
+
- 入口:`/feedback/[id]`
|
|
153
|
+
- **J3-US4**:暂无可操作入口(缺失)。
|
|
154
|
+
|
|
155
|
+
### 旅程 4:Emily(客服)
|
|
156
|
+
- **J4-US1**:进入“代客户提交反馈”页面并提交。
|
|
157
|
+
- 入口:`/feedback/new`
|
|
158
|
+
- **J4-US2**:在代提交表单中填写客户信息。
|
|
159
|
+
- 入口:`/feedback/new`
|
|
160
|
+
- **J4-US3**:提交成功后打开反馈详情页复制链接发送客户。
|
|
161
|
+
- 入口:`/feedback/[id]`
|
|
162
|
+
- **J4-US4**:暂无可操作入口(缺失)。
|
|
163
|
+
- **J4-US5**:暂无可操作入口(缺失)。
|
|
164
|
+
|
|
165
|
+
### 旅程 5:David(管理员)
|
|
166
|
+
- **J5-US1**:创建组织。
|
|
167
|
+
- 入口:`/settings/organizations/new`
|
|
168
|
+
- **J5-US2**:成员管理/邀请。
|
|
169
|
+
- 入口:`/settings/organizations/[orgId]/members`
|
|
170
|
+
- **J5-US3**:配置 Portal。
|
|
171
|
+
- 入口:`/settings/organization/portal`
|
|
172
|
+
- **J5-US4**:配置 Widget 外观与字段。
|
|
173
|
+
- 入口:暂无可操作入口(缺失)。
|
|
174
|
+
- **J5-US5**:暂无可操作入口(缺失)。
|
|
175
|
+
- **J5-US6**:暂无可操作入口(缺失)。
|
|
176
|
+
- **J5-US7**:暂无可操作入口(缺失)。
|
|
177
|
+
|
|
178
|
+
### 旅程 6:Ryan(API 消费者)
|
|
179
|
+
- **J6-US1**:查看 API 文档。
|
|
180
|
+
- 入口:`/docs`
|
|
181
|
+
- **J6-US2**:暂无可操作入口(缺失)。
|
|
182
|
+
- **J6-US3**:暂无可操作入口(缺失)。
|
|
183
|
+
- **J6-US4**:暂无可操作入口(缺失)。
|
|
184
|
+
|
|
185
|
+
### 旅程 7:Lisa(运维)
|
|
186
|
+
- **J7-US1**:健康检查(直接请求 API)。
|
|
187
|
+
- 入口:`/api/health`
|
|
188
|
+
- **J7-US2**:暂无可操作入口(缺失)。
|
|
189
|
+
- **J7-US3**:暂无可操作入口(缺失)。
|
|
190
|
+
- **J7-US4**:暂无可操作入口(缺失)。
|
|
191
|
+
- **J7-US5**:暂无可操作入口(缺失)。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { defineConfig } from "drizzle-kit";
|
|
19
|
+
import { config } from "dotenv";
|
|
20
|
+
|
|
21
|
+
config({ path: ".env.local" });
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
schema: "./lib/db/schema",
|
|
25
|
+
out: "./lib/db/migrations",
|
|
26
|
+
dialect: "postgresql",
|
|
27
|
+
dbCredentials: {
|
|
28
|
+
url: process.env.DATABASE_URL!,
|
|
29
|
+
},
|
|
30
|
+
verbose: true,
|
|
31
|
+
strict: true,
|
|
32
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
".agent/**",
|
|
16
|
+
]),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (c) 2026 Echo Team
|
|
6
|
+
*
|
|
7
|
+
* This program is free software: you can redistribute it and/or modify
|
|
8
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
9
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
* (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* This program is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
* GNU Affero General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { authClient } from "@/lib/auth/client";
|
|
22
|
+
import {
|
|
23
|
+
hasAllPermissions,
|
|
24
|
+
hasPermission,
|
|
25
|
+
type Permission,
|
|
26
|
+
type UserRole,
|
|
27
|
+
} from "@/lib/auth/permissions";
|
|
28
|
+
|
|
29
|
+
type Session = (typeof authClient)["$Infer"]["Session"];
|
|
30
|
+
|
|
31
|
+
export function useCan(
|
|
32
|
+
permission: Permission,
|
|
33
|
+
sessionOverride?: Session | null,
|
|
34
|
+
): boolean {
|
|
35
|
+
const session =
|
|
36
|
+
sessionOverride === undefined ? authClient.useSession().data : sessionOverride;
|
|
37
|
+
const role = (session?.user as { role?: UserRole })?.role;
|
|
38
|
+
|
|
39
|
+
return role ? hasPermission(role, permission) : false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useHasPermission(
|
|
43
|
+
permissions: Permission | Permission[],
|
|
44
|
+
sessionOverride?: Session | null,
|
|
45
|
+
): boolean {
|
|
46
|
+
const session =
|
|
47
|
+
sessionOverride === undefined ? authClient.useSession().data : sessionOverride;
|
|
48
|
+
const role = (session?.user as { role?: UserRole })?.role;
|
|
49
|
+
|
|
50
|
+
if (!role) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const list = Array.isArray(permissions) ? permissions : [permissions];
|
|
55
|
+
return hasAllPermissions(role, list);
|
|
56
|
+
}
|
package/i18n/config.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_LOCALE = "en" as const;
|
|
19
|
+
export const SUPPORTED_LOCALES = ["en", "zh-CN", "jp"] as const;
|
|
20
|
+
export type AppLocale = (typeof SUPPORTED_LOCALES)[number];
|
|
21
|
+
|
|
22
|
+
export const LOCALE_COOKIE_NAME = "NEXT_LOCALE";
|
|
23
|
+
|
|
24
|
+
export function isSupportedLocale(value: string): value is AppLocale {
|
|
25
|
+
return (SUPPORTED_LOCALES as readonly string[]).includes(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function normalizeLocale(value: string): AppLocale | null {
|
|
29
|
+
const normalized = value.trim().toLowerCase();
|
|
30
|
+
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
|
31
|
+
if (normalized === "zh" || normalized === "zh-cn" || normalized.startsWith("zh-")) return "zh-CN";
|
|
32
|
+
if (normalized === "ja" || normalized === "jp" || normalized.startsWith("ja-")) return "jp";
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getPreferredLocaleFromHeader(header: string | null): AppLocale | null {
|
|
37
|
+
if (!header) return null;
|
|
38
|
+
const locales = header.split(",").map((part) => part.split(";")[0]?.trim());
|
|
39
|
+
for (const locale of locales) {
|
|
40
|
+
if (!locale) continue;
|
|
41
|
+
const supported = normalizeLocale(locale);
|
|
42
|
+
if (supported) return supported;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
package/i18n/request.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { getRequestConfig } from "next-intl/server";
|
|
19
|
+
import { resolveRequestLocale } from "./resolve-locale";
|
|
20
|
+
|
|
21
|
+
export default getRequestConfig(async () => {
|
|
22
|
+
const locale = await resolveRequestLocale();
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
locale,
|
|
26
|
+
messages: (await import(`../messages/${locale}.json`)).default,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { cookies, headers } from "next/headers";
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_LOCALE,
|
|
21
|
+
LOCALE_COOKIE_NAME,
|
|
22
|
+
getPreferredLocaleFromHeader,
|
|
23
|
+
isSupportedLocale,
|
|
24
|
+
} from "./config";
|
|
25
|
+
|
|
26
|
+
export async function resolveRequestLocale() {
|
|
27
|
+
const cookieStore = await cookies();
|
|
28
|
+
const headerStore = await headers();
|
|
29
|
+
|
|
30
|
+
const cookieLocale = cookieStore.get(LOCALE_COOKIE_NAME)?.value;
|
|
31
|
+
if (cookieLocale && isSupportedLocale(cookieLocale)) {
|
|
32
|
+
return cookieLocale;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
getPreferredLocaleFromHeader(headerStore.get("accept-language")) || DEFAULT_LOCALE
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { NextResponse } from "next/server";
|
|
19
|
+
|
|
20
|
+
export interface ApiErrorResponse {
|
|
21
|
+
error: string;
|
|
22
|
+
code: string;
|
|
23
|
+
details?: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function apiError(
|
|
27
|
+
error: unknown,
|
|
28
|
+
status: number = 500,
|
|
29
|
+
): NextResponse<ApiErrorResponse> {
|
|
30
|
+
if (error instanceof Error) {
|
|
31
|
+
return NextResponse.json(
|
|
32
|
+
{
|
|
33
|
+
error: error.message,
|
|
34
|
+
code: status === 400 ? "VALIDATION_ERROR" : "INTERNAL_ERROR",
|
|
35
|
+
details: process.env.NODE_ENV === "development" ? error.stack : undefined,
|
|
36
|
+
},
|
|
37
|
+
{ status },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return NextResponse.json(
|
|
42
|
+
{
|
|
43
|
+
error: "An unexpected error occurred",
|
|
44
|
+
code: "INTERNAL_ERROR",
|
|
45
|
+
},
|
|
46
|
+
{ status },
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function validationError(
|
|
51
|
+
details?: unknown,
|
|
52
|
+
message: string = "Validation failed",
|
|
53
|
+
): NextResponse<ApiErrorResponse> {
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{
|
|
56
|
+
error: message,
|
|
57
|
+
code: "VALIDATION_ERROR",
|
|
58
|
+
details,
|
|
59
|
+
},
|
|
60
|
+
{ status: 400 },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { betterAuth } from "better-auth";
|
|
19
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
20
|
+
import * as schema from "../db/schema";
|
|
21
|
+
|
|
22
|
+
// CLI runs in Node (no Bun), so avoid importing the Bun SQL client here.
|
|
23
|
+
const cliDbStub = {} as unknown as Record<string, unknown>;
|
|
24
|
+
|
|
25
|
+
export const auth = betterAuth({
|
|
26
|
+
database: drizzleAdapter(cliDbStub, {
|
|
27
|
+
provider: "pg",
|
|
28
|
+
schema,
|
|
29
|
+
}),
|
|
30
|
+
emailAndPassword: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export default auth;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createAuthClient } from "better-auth/react";
|
|
19
|
+
|
|
20
|
+
export const authClient = createAuthClient();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { betterAuth } from "better-auth";
|
|
19
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
20
|
+
import { nextCookies } from "better-auth/next-js";
|
|
21
|
+
import { db } from "../db";
|
|
22
|
+
import * as schema from "../db/schema";
|
|
23
|
+
|
|
24
|
+
if (!db) {
|
|
25
|
+
throw new Error("DATABASE_URL is not configured");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!process.env.BETTER_AUTH_SECRET && process.env.NODE_ENV !== "production") {
|
|
29
|
+
console.warn(
|
|
30
|
+
"[auth] BETTER_AUTH_SECRET is not set. Session cookies will be invalidated on server restart. " +
|
|
31
|
+
"Set BETTER_AUTH_SECRET in .env.local to fix this."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const auth = betterAuth({
|
|
36
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
37
|
+
database: drizzleAdapter(db, {
|
|
38
|
+
provider: "pg",
|
|
39
|
+
schema,
|
|
40
|
+
}),
|
|
41
|
+
user: {
|
|
42
|
+
additionalFields: {
|
|
43
|
+
role: {
|
|
44
|
+
type: "string",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
emailAndPassword: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
},
|
|
51
|
+
session: {
|
|
52
|
+
expiresIn: 60 * 60 * 24 * 30,
|
|
53
|
+
},
|
|
54
|
+
plugins: [nextCookies()],
|
|
55
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Echo Team
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export function parseJwtIdentity(token: string) {
|
|
19
|
+
if (!token) return null;
|
|
20
|
+
return { token };
|
|
21
|
+
}
|