Showing preview only (6,172K chars total). Download the full file or copy to clipboard to get everything.
Repository: Supernova3339/changerawr
Branch: master
Commit: a72f1c9e9084
Files: 624
Total size: 5.8 MB
Directory structure:
gitextract_pand8s4o/
├── .dockerignore
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yml
│ │ └── feature-request.yml
│ └── workflows/
│ ├── build-platform.yml
│ └── docker-build.yml
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── changerawr.iml
│ ├── dataSources.xml
│ ├── data_source_mapping.xml
│ ├── db-forest-config.xml
│ ├── discord.xml
│ ├── inspectionProfiles/
│ │ └── Project_Default.xml
│ ├── jsLibraryMappings.xml
│ ├── modules.xml
│ ├── sqldialects.xml
│ └── vcs.xml
├── APIDOCGUIDE.md
├── CHANGELOG.md
├── Caddyfile
├── Dockerfile
├── Dockerfile.compose
├── LICENSE
├── PROJECT_INDEX.md
├── README.md
├── app/
│ ├── (auth)/
│ │ ├── forgot-password/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── login/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── oauth-callback/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── register/
│ │ │ └── [token]/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── reset-password/
│ │ │ └── [token]/
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ └── reset-password-form.tsx
│ │ ├── setup/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── two-factor/
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── (email)/
│ │ └── unsubscribed/
│ │ └── page.tsx
│ ├── .well-known/
│ │ └── acme-challenge/
│ │ ├── [token]/
│ │ │ ├── route.ts
│ │ │ └── route.ts.disabled
│ │ └── route.ts
│ ├── api/
│ │ ├── acme/
│ │ │ ├── cancel/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ ├── issue/
│ │ │ │ └── route.ts
│ │ │ ├── renew/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ ├── revoke/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ ├── status/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ └── verify-dns/
│ │ │ └── route.ts
│ │ ├── admin/
│ │ │ ├── ai-settings/
│ │ │ │ ├── route.ts
│ │ │ │ └── test-key/
│ │ │ │ └── route.ts
│ │ │ ├── analytics/
│ │ │ │ └── route.ts
│ │ │ ├── api-keys/
│ │ │ │ ├── [keyId]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── audit-logs/
│ │ │ │ ├── actions/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── config/
│ │ │ │ ├── route.ts
│ │ │ │ └── system-email/
│ │ │ │ └── route.ts
│ │ │ ├── dashboard/
│ │ │ │ └── route.ts
│ │ │ ├── oauth/
│ │ │ │ └── providers/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── saml/
│ │ │ │ └── providers/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── sponsor/
│ │ │ │ └── route.ts
│ │ │ ├── system/
│ │ │ │ └── slack/
│ │ │ │ └── route.ts
│ │ │ └── users/
│ │ │ ├── [userId]/
│ │ │ │ ├── role/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── invitations/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── ai/
│ │ │ ├── decrypt/
│ │ │ │ └── route.ts
│ │ │ └── settings/
│ │ │ └── route.ts
│ │ ├── analytics/
│ │ │ └── track/
│ │ │ └── route.ts
│ │ ├── auth/
│ │ │ ├── change-password/
│ │ │ │ └── route.ts
│ │ │ ├── cli/
│ │ │ │ ├── generate/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── refresh/
│ │ │ │ │ └── route.ts
│ │ │ │ └── token/
│ │ │ │ └── route.ts
│ │ │ ├── connections/
│ │ │ │ └── route.ts
│ │ │ ├── forgot-password/
│ │ │ │ └── route.ts
│ │ │ ├── invitation/
│ │ │ │ └── [token]/
│ │ │ │ └── route.ts
│ │ │ ├── login/
│ │ │ │ ├── route.ts
│ │ │ │ └── second-factor/
│ │ │ │ └── route.ts
│ │ │ ├── logout/
│ │ │ │ └── route.ts
│ │ │ ├── me/
│ │ │ │ └── route.ts
│ │ │ ├── oauth/
│ │ │ │ ├── authorize/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── callback/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── providers/
│ │ │ │ └── route.ts
│ │ │ ├── passkeys/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── authenticate/
│ │ │ │ │ ├── options/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── verify/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── register/
│ │ │ │ │ ├── options/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── verify/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── preview/
│ │ │ │ └── route.ts
│ │ │ ├── refresh/
│ │ │ │ └── route.ts
│ │ │ ├── register/
│ │ │ │ └── route.ts
│ │ │ ├── reset-password/
│ │ │ │ ├── [token]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── request/
│ │ │ │ └── route.ts
│ │ │ ├── saml/
│ │ │ │ ├── authorize/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── callback/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── metadata/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── providers/
│ │ │ │ └── route.ts
│ │ │ ├── security-settings/
│ │ │ │ └── route.ts
│ │ │ ├── settings/
│ │ │ │ └── route.ts
│ │ │ └── validate/
│ │ │ └── route.ts
│ │ ├── avatar/
│ │ │ └── [hash]/
│ │ │ └── route.ts
│ │ ├── changelog/
│ │ │ ├── [projectId]/
│ │ │ │ └── entries/
│ │ │ │ ├── all/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── entries/
│ │ │ │ └── [entryId]/
│ │ │ │ └── route.ts
│ │ │ ├── requests/
│ │ │ │ ├── [requestId]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── subscribe/
│ │ │ │ └── route.ts
│ │ │ ├── unsubscribe/
│ │ │ │ └── [token]/
│ │ │ │ └── route.ts
│ │ │ └── verify-domain/
│ │ │ └── route.ts
│ │ ├── check-setup/
│ │ │ └── route.ts
│ │ ├── config/
│ │ │ ├── runtime/
│ │ │ │ └── route.ts
│ │ │ └── timezone/
│ │ │ └── route.ts
│ │ ├── cron/
│ │ │ └── ssl-renewal/
│ │ │ └── route.ts
│ │ ├── custom-domains/
│ │ │ ├── [domain]/
│ │ │ │ ├── browser-rules/
│ │ │ │ │ ├── [id]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── dns-instructions/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ ├── ssl/
│ │ │ │ │ ├── mode/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── revoke/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── toggle-https/
│ │ │ │ │ └── route.ts
│ │ │ │ └── throttle/
│ │ │ │ └── route.ts
│ │ │ ├── add/
│ │ │ │ └── route.ts
│ │ │ ├── list/
│ │ │ │ └── route.ts
│ │ │ └── verify/
│ │ │ └── route.ts
│ │ ├── dashboard/
│ │ │ └── stats/
│ │ │ └── route.ts
│ │ ├── domain-check/
│ │ │ └── route.ts
│ │ ├── health/
│ │ │ └── route.ts
│ │ ├── integrations/
│ │ │ ├── slack/
│ │ │ │ ├── callback/
│ │ │ │ │ └── route.ts
│ │ │ │ └── manifest/
│ │ │ │ └── route.ts
│ │ │ └── widget/
│ │ │ └── [projectId]/
│ │ │ ├── [widgetId]/
│ │ │ │ └── route.ts
│ │ │ ├── list/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── internal/
│ │ │ ├── agent/
│ │ │ │ └── version/
│ │ │ │ └── route.ts
│ │ │ ├── cert/
│ │ │ │ └── [domain]/
│ │ │ │ └── route.ts
│ │ │ └── ip-config/
│ │ │ └── route.ts
│ │ ├── projects/
│ │ │ ├── [projectId]/
│ │ │ │ ├── analytics/
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── api-keys/
│ │ │ │ │ ├── [keyId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── catch-up/
│ │ │ │ │ ├── ai-summary/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── changelog/
│ │ │ │ │ ├── [entryId]/
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ └── schedule/
│ │ │ │ │ │ ├── approval/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── route.ts
│ │ │ │ │ └── tags/
│ │ │ │ │ ├── [tagId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── cli/
│ │ │ │ │ ├── link/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── sync/
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ └── status/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── unlink/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── integrations/
│ │ │ │ │ ├── email/
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ ├── send/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── test/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── github/
│ │ │ │ │ │ ├── generate/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ ├── tags/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── test/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── slack/
│ │ │ │ │ ├── channels/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ ├── settings/
│ │ │ │ │ └── route.ts
│ │ │ │ └── versions/
│ │ │ │ └── route.ts
│ │ │ ├── import/
│ │ │ │ ├── canny/
│ │ │ │ │ ├── fetch/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── validate/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── parse/
│ │ │ │ │ └── route.ts
│ │ │ │ └── process/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── requests/
│ │ │ └── route.ts
│ │ ├── search/
│ │ │ └── route.ts
│ │ ├── setup/
│ │ │ ├── admin/
│ │ │ │ └── route.ts
│ │ │ ├── invitations/
│ │ │ │ └── route.ts
│ │ │ ├── oauth/
│ │ │ │ ├── auto/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── debug/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ ├── settings/
│ │ │ │ └── route.ts
│ │ │ ├── setupProgressSchema.ts
│ │ │ ├── status/
│ │ │ │ └── route.ts
│ │ │ └── types.ts
│ │ ├── subscribers/
│ │ │ ├── [subscriberId]/
│ │ │ │ └── route.ts
│ │ │ ├── generate-mock/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── system/
│ │ │ ├── agent-version/
│ │ │ │ └── route.ts
│ │ │ ├── easypanel/
│ │ │ │ └── status/
│ │ │ │ └── route.ts
│ │ │ ├── perform-update/
│ │ │ │ └── route.ts
│ │ │ ├── update-status/
│ │ │ │ └── route.ts
│ │ │ └── version/
│ │ │ └── route.ts
│ │ └── telemetry/
│ │ ├── config/
│ │ │ └── route.ts
│ │ └── debug/
│ │ └── route.ts
│ ├── api-docs/
│ │ └── route.ts
│ ├── changelog/
│ │ ├── [projectId]/
│ │ │ ├── [entryId]/
│ │ │ │ └── page.tsx
│ │ │ ├── changelog-view.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── loading.tsx
│ │ │ ├── not-found.tsx
│ │ │ ├── page.tsx
│ │ │ └── rss.xml/
│ │ │ └── route.ts
│ │ └── custom-domain/
│ │ └── [domain]/
│ │ ├── [entryId]/
│ │ │ ├── EntryContent.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── rss.xml/
│ │ │ └── route.ts
│ │ └── unsubscribed/
│ │ └── page.tsx
│ ├── cli/
│ │ └── auth/
│ │ └── page.tsx
│ ├── dashboard/
│ │ ├── admin/
│ │ │ ├── about/
│ │ │ │ └── page.tsx
│ │ │ ├── ai-settings/
│ │ │ │ └── page.tsx
│ │ │ ├── analytics/
│ │ │ │ └── page.tsx
│ │ │ ├── api-keys/
│ │ │ │ └── page.tsx
│ │ │ ├── audit-logs/
│ │ │ │ └── page.tsx
│ │ │ ├── domains/
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── oauth/
│ │ │ │ └── page.tsx
│ │ │ ├── page.tsx
│ │ │ ├── requests/
│ │ │ │ └── page.tsx
│ │ │ ├── system/
│ │ │ │ ├── email/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── slack/
│ │ │ │ │ └── page.tsx
│ │ │ │ └── templates/
│ │ │ │ └── page.tsx
│ │ │ └── users/
│ │ │ └── page.tsx
│ │ ├── bookmarks/
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── projects/
│ │ │ ├── [projectId]/
│ │ │ │ ├── analytics/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── api-keys/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── catch-up/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── changelog/
│ │ │ │ │ ├── [entryId]/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── new/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── domains/
│ │ │ │ │ ├── [domain]/
│ │ │ │ │ │ ├── client.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── components/
│ │ │ │ │ │ ├── DomainCard.tsx
│ │ │ │ │ │ ├── InlineSSLSetup.tsx
│ │ │ │ │ │ ├── SSLCertificateCard.tsx
│ │ │ │ │ │ ├── SSLCertificateWizard.tsx
│ │ │ │ │ │ ├── SSLManagement.tsx
│ │ │ │ │ │ └── ssl/
│ │ │ │ │ │ ├── ExternalSSLManagement.tsx
│ │ │ │ │ │ ├── SSLCertificateActions.tsx
│ │ │ │ │ │ ├── SSLCertificateStatus.tsx
│ │ │ │ │ │ ├── SSLDNSInstructions.tsx
│ │ │ │ │ │ ├── SSLModeSelector.tsx
│ │ │ │ │ │ ├── SSLVerificationMethod.tsx
│ │ │ │ │ │ ├── SSLVerificationProgress.tsx
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── page.tsx.backup
│ │ │ │ ├── import/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── integrations/
│ │ │ │ │ ├── email/
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ └── subscribers/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── github/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── slack/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── widget/
│ │ │ │ │ ├── [widgetId]/
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ └── widget-editor.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── widget-config.tsx
│ │ │ │ │ └── widget-list.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── settings/
│ │ │ │ └── page.tsx
│ │ │ ├── new/
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── providers.tsx
│ │ ├── requests/
│ │ │ └── page.tsx
│ │ └── settings/
│ │ └── page.tsx
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ └── startup.ts
├── components/
│ ├── CommandPalette.tsx
│ ├── DinoGame.tsx
│ ├── Logo.tsx
│ ├── MarkdownEditor.tsx
│ ├── UpdateIndicatorBadge.tsx
│ ├── UpdateStatus.tsx
│ ├── admin/
│ │ ├── api/
│ │ │ ├── PermissionsModal.tsx
│ │ │ ├── Rename.tsx
│ │ │ ├── sdk-showcase-compact.tsx
│ │ │ └── sdk-showcase.tsx
│ │ ├── audit-logs/
│ │ │ └── VirtualizedList.tsx
│ │ └── requests/
│ │ └── Management.tsx
│ ├── analytics/
│ │ ├── analytics-chart.tsx
│ │ ├── analytics-metric-card.tsx
│ │ ├── country-analytics-table.tsx
│ │ ├── entry-analytics-table.tsx
│ │ ├── project-analytics-table.tsx
│ │ └── referrer-analytics-table.tsx
│ ├── changelog/
│ │ ├── ButtonGroup.tsx
│ │ ├── ChangelogActionRequest.tsx
│ │ ├── ChangelogEditor.tsx
│ │ ├── ChangelogEntries.tsx
│ │ ├── RequestHandler.tsx
│ │ ├── ScrollToTopButton.tsx
│ │ ├── ShareButton.tsx
│ │ ├── ThemeToggle.tsx
│ │ ├── WidgetPreview.tsx
│ │ └── editor/
│ │ ├── AITitleGenerator.tsx
│ │ ├── EditorHeader.tsx
│ │ ├── TagColorPicker.tsx
│ │ ├── TagSelector.tsx
│ │ ├── TagSuggester.tsx
│ │ ├── VersionSelector.tsx
│ │ └── scheduler/
│ │ └── ScheduleEntryDialog.tsx
│ ├── dashboard/
│ │ └── WhatsNewModal.tsx
│ ├── github/
│ │ ├── GitHubGenerateDialog.tsx
│ │ └── GitHubIntegrationSettings.tsx
│ ├── loading-spinner.tsx
│ ├── markdown-editor/
│ │ ├── MarkdownEditor.tsx
│ │ ├── MarkdownEditorArea.tsx
│ │ ├── MarkdownPreview.tsx
│ │ ├── MarkdownToolbar.tsx
│ │ ├── RenderMarkdown.tsx
│ │ ├── StatusBar.tsx
│ │ ├── ai/
│ │ │ └── AIAssistantPanel.tsx
│ │ ├── hooks/
│ │ │ └── useCUMModals.ts
│ │ ├── index.ts
│ │ ├── modals/
│ │ │ ├── CUMAlertModal.tsx
│ │ │ ├── CUMButtonModal.tsx
│ │ │ ├── CUMEmbedModal.tsx
│ │ │ ├── CUMTableModal.tsx
│ │ │ └── index.ts
│ │ ├── types/
│ │ │ └── cum-extensions.ts
│ │ └── utils/
│ │ ├── formatting.ts
│ │ ├── keyboard-shortcuts.ts
│ │ ├── renderer.ts
│ │ └── safe-keyboard-shortcuts.tsx
│ ├── project/
│ │ ├── ProjectNavItem.tsx
│ │ ├── ProjectSettingsPage.tsx
│ │ ├── ProjectSidebar.tsx
│ │ ├── RecentChangelogItem.tsx
│ │ ├── catch-up/
│ │ │ ├── CatchUpEntry.tsx
│ │ │ ├── CatchUpView.tsx
│ │ │ └── SinceSelector.tsx
│ │ └── settings/
│ │ └── TagManagement.tsx
│ ├── projects/
│ │ └── importing/
│ │ ├── ChangelogImportModal.tsx
│ │ ├── ImportDataPrompt.tsx
│ │ └── integrations/
│ │ └── CannyImportStep.tsx
│ ├── providers/
│ │ ├── CommandPaletteProvider.tsx
│ │ └── setup-context.tsx
│ ├── settings/
│ │ ├── connected-sso-section.tsx
│ │ ├── passkeys-section.tsx
│ │ └── security-settings.tsx
│ ├── setup/
│ │ ├── TeamImportModal.tsx
│ │ ├── setup-context.tsx
│ │ ├── setup-step.tsx
│ │ └── steps/
│ │ ├── admin-step.tsx
│ │ ├── completion-step.tsx
│ │ ├── oauth-step.tsx
│ │ ├── settings-step.tsx
│ │ ├── team-step.tsx
│ │ └── welcome-step.tsx
│ ├── sso/
│ │ └── ProviderLogo.tsx
│ ├── subscription-form.tsx
│ ├── telemetry/
│ │ └── PromptModal.tsx
│ ├── theme-provider.tsx
│ └── ui/
│ ├── accordion.tsx
│ ├── alert-dialog.tsx
│ ├── alert.tsx
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── breadcrumb.tsx
│ ├── breadcrumbs.tsx
│ ├── button.tsx
│ ├── calendar.tsx
│ ├── card.tsx
│ ├── checkbox.tsx
│ ├── collapsible.tsx
│ ├── command.tsx
│ ├── confetti.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── error-alert.tsx
│ ├── form.tsx
│ ├── hover-card.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── popover.tsx
│ ├── progress.tsx
│ ├── radio-group.tsx
│ ├── scroll-area.tsx
│ ├── searchable-select.tsx
│ ├── select.tsx
│ ├── separator.tsx
│ ├── sheet.tsx
│ ├── sidebar.tsx
│ ├── skeleton.tsx
│ ├── slider.tsx
│ ├── switch.tsx
│ ├── table.tsx
│ ├── tabs.tsx
│ ├── textarea.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ └── tooltip.tsx
├── components.json
├── context/
│ └── auth.tsx
├── docker-compose-online.yml
├── docker-compose.yml
├── docker-entrypoint-compose.sh
├── docker-entrypoint.sh
├── emails/
│ ├── approval-notification.tsx
│ ├── changelog.tsx
│ ├── password-reset.tsx
│ ├── rejection-notification.tsx
│ └── schedule-published.tsx
├── eslint.config.mjs
├── hooks/
│ ├── use-local-storage.ts
│ ├── use-media-query.ts
│ ├── use-timezone.ts
│ ├── use-toast.ts
│ ├── useAIAssistant.ts
│ ├── useBookmarks.ts
│ ├── useChunkedData.ts
│ ├── useCommandPalette.ts
│ ├── useEditorHistory.ts
│ ├── useMarkdownState.ts
│ ├── useSlashCommands.ts
│ ├── useTelemetry.ts
│ └── useWhatsNew.ts
├── ideas.md
├── instrumentation.ts
├── issues.md
├── jsdoc.json
├── lib/
│ ├── api/
│ │ ├── README.md
│ │ ├── middleware.ts
│ │ ├── permissions.ts
│ │ └── route-permissions.ts
│ ├── app-info.ts
│ ├── auth/
│ │ ├── api-key.ts
│ │ ├── authorization.ts
│ │ ├── claim-validator.ts
│ │ ├── cli-auth.ts
│ │ ├── email-domain-validator.ts
│ │ ├── jwt-utils.ts
│ │ ├── oauth.ts
│ │ ├── password.ts
│ │ ├── providers/
│ │ │ ├── easypanel/
│ │ │ │ ├── auto-setup.ts
│ │ │ │ └── client.ts
│ │ │ ├── easypanel.ts
│ │ │ └── pocketid.ts
│ │ ├── saml.ts
│ │ ├── tokens.ts
│ │ └── webauthn.ts
│ ├── constants/
│ │ └── timezones.ts
│ ├── custom-domains/
│ │ ├── constants.ts
│ │ ├── dns.ts
│ │ ├── service.ts
│ │ ├── ssl/
│ │ │ ├── acme-account.ts
│ │ │ ├── auto-renewal.ts
│ │ │ ├── cron-setup.md
│ │ │ ├── encryption.ts
│ │ │ ├── is-supported.ts
│ │ │ ├── service.ts
│ │ │ ├── setup-renewal-job.ts
│ │ │ ├── ssrf-guard.ts
│ │ │ └── webhook.ts
│ │ ├── utils.ts
│ │ └── validation.ts
│ ├── db.ts
│ ├── middleware/
│ │ └── analytics.ts
│ ├── services/
│ │ ├── analytics/
│ │ │ └── geolocation.ts
│ │ ├── auth/
│ │ │ ├── password-breach.ts
│ │ │ └── password-reset.ts
│ │ ├── bookmarks/
│ │ │ └── bookmark.service.ts
│ │ ├── changelog/
│ │ │ └── rss.ts
│ │ ├── core/
│ │ │ ├── markdown/
│ │ │ │ ├── EXTENSIONS.md
│ │ │ │ ├── EXTENSIONS_SETUP.md
│ │ │ │ ├── extensions/
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── subtext/
│ │ │ │ │ │ └── subtext.ts
│ │ │ │ │ └── table/
│ │ │ │ │ └── table.ts
│ │ │ │ └── useCustomExtensions.ts
│ │ │ └── system-user/
│ │ │ ├── README.md
│ │ │ └── service.ts
│ │ ├── easypanel/
│ │ │ └── index.ts
│ │ ├── email/
│ │ │ ├── notification.ts
│ │ │ └── schedule-notification.ts
│ │ ├── github/
│ │ │ ├── changelog-generator.ts
│ │ │ └── client.ts
│ │ ├── jobs/
│ │ │ ├── executors/
│ │ │ │ ├── changelog-publish.executor.ts
│ │ │ │ ├── ssl-renewal.executor.ts
│ │ │ │ └── telemetry-send.executor.ts
│ │ │ ├── job-runner.service.ts
│ │ │ └── scheduled-job.service.ts
│ │ ├── projects/
│ │ │ ├── catch-up/
│ │ │ │ └── catch-up.service.ts
│ │ │ └── importing/
│ │ │ ├── index.ts
│ │ │ ├── integrations/
│ │ │ │ └── canny.service.ts
│ │ │ ├── markdown-parser.service.ts
│ │ │ ├── processor.service.ts
│ │ │ └── validation.service.ts
│ │ ├── request/
│ │ │ └── changelog-request.ts
│ │ ├── search/
│ │ │ └── service.ts
│ │ ├── slack/
│ │ │ ├── index.ts
│ │ │ ├── logo.tsx
│ │ │ └── post-message.ts
│ │ ├── sponsor/
│ │ │ └── service.ts
│ │ └── telemetry/
│ │ └── service.ts
│ ├── types/
│ │ ├── analytics.ts
│ │ ├── auth.ts
│ │ ├── changelog.ts
│ │ ├── cli/
│ │ │ └── project-api.ts
│ │ ├── custom-domains.ts
│ │ ├── dashboard.ts
│ │ ├── easypanel.ts
│ │ ├── oauth.ts
│ │ ├── projects/
│ │ │ ├── catch-up/
│ │ │ │ └── types.ts
│ │ │ ├── importing/
│ │ │ │ └── canny.ts
│ │ │ └── importing.ts
│ │ ├── saml.ts
│ │ ├── settings.ts
│ │ ├── sso.ts
│ │ └── telemetry.ts
│ ├── utils/
│ │ ├── ai/
│ │ │ ├── prompts.ts
│ │ │ ├── secton.ts
│ │ │ └── types.ts
│ │ ├── analytics.ts
│ │ ├── api.ts
│ │ ├── auditLog.ts
│ │ ├── changelog.ts
│ │ ├── cookies.ts
│ │ ├── docker.ts
│ │ ├── encryption.ts
│ │ ├── format-date.ts
│ │ ├── gravatar.ts
│ │ ├── rate-limit.ts
│ │ └── text.ts
│ └── utils.ts
├── next.config.ts
├── next.openapi.json
├── nginx.conf
├── package-lock.json.backup
├── package.json
├── package.json.backup
├── postcss.config.mjs
├── prisma/
│ ├── migrations/
│ │ ├── 20250215042251_init_authentication/
│ │ │ └── migration.sql
│ │ ├── 20250215051621_add_invitation_links/
│ │ │ └── migration.sql
│ │ ├── 20250215063030_add_last_logged_in_at_field/
│ │ │ └── migration.sql
│ │ ├── 20250215075317_add_changelog_models/
│ │ │ └── migration.sql
│ │ ├── 20250215080528_add_viewer_role/
│ │ │ └── migration.sql
│ │ ├── 20250215180005_add_project_settings/
│ │ │ └── migration.sql
│ │ ├── 20250215232640_add_api_keys/
│ │ │ └── migration.sql
│ │ ├── 20250216005356_add_audit_logs/
│ │ │ └── migration.sql
│ │ ├── 20250216050812_add_system_configuration/
│ │ │ └── migration.sql
│ │ ├── 20250216053424_update_changelog_requests_schema/
│ │ │ └── migration.sql
│ │ ├── 20250221000121_add_new_request_types/
│ │ │ └── migration.sql
│ │ ├── 20250305225249_add_oauth/
│ │ │ └── migration.sql
│ │ ├── 20250418044737_add_email_integration/
│ │ │ └── migration.sql
│ │ ├── 20250418045834_add_email_logs/
│ │ │ └── migration.sql
│ │ ├── 20250418161805_add_email_log_enums/
│ │ │ └── migration.sql
│ │ ├── 20250418163601_add_email_subscription/
│ │ │ └── migration.sql
│ │ ├── 20250419000001_add_password_reset/
│ │ │ └── migration.sql
│ │ ├── 20250421000001_add_notification_preferences/
│ │ │ └── migration.sql
│ │ ├── 20250504203021_passkey_support/
│ │ │ └── migration.sql
│ │ ├── 20250504203707_add_last_challenge/
│ │ │ └── migration.sql
│ │ ├── 20250504211358_add_two_factor_mode/
│ │ │ └── migration.sql
│ │ ├── 20250504212400_add_two_factor_session/
│ │ │ └── migration.sql
│ │ ├── 20250511204818_add_ai_assistant_config/
│ │ │ └── migration.sql
│ │ ├── 20250512000000_add_github_integration/
│ │ │ └── migration.sql
│ │ ├── 20250610120000_fix_user_deletion_constraints/
│ │ │ └── migration.sql
│ │ ├── 20250613040144_changelog_analytics/
│ │ │ └── migration.sql
│ │ ├── 20250624024525_add_custom_domains/
│ │ │ └── migration.sql
│ │ ├── 20250625030954_add_custom_domains_to_email_notifications/
│ │ │ └── migration.sql
│ │ ├── 20250625120000_add_changelog_scheduling/
│ │ │ └── migration.sql
│ │ ├── 20250627020135_add_color_field_to_changelog_tag/
│ │ │ └── migration.sql
│ │ ├── 20250628171029_add_telemetry/
│ │ │ └── migration.sql
│ │ ├── 20250628175000_preemptive_telemetry_setup/
│ │ │ └── migration.sql
│ │ ├── 20250628180800_add_telemetry_support/
│ │ │ └── migration.sql
│ │ ├── 20250628215000_complete_telemetry_fix/
│ │ │ └── migration.sql
│ │ ├── 20250628215100_fix_telemetry_casting/
│ │ │ └── migration.sql
│ │ ├── 20250628230000_add_changelogentryid_back/
│ │ │ └── migration.sql
│ │ ├── 20250702092443_add_cli_auth_codes/
│ │ │ └── migration.sql
│ │ ├── 20250702121802_add_synced_cli_migrations_and_metadata/
│ │ │ └── migration.sql
│ │ ├── 20250703092000_fix_missing_changelogentryid_column/
│ │ │ └── migration.sql
│ │ ├── 20251031_add_metadata_to_changelog_request/
│ │ │ └── migration.sql
│ │ ├── 20251111210147_add_api_key_and_widgets/
│ │ │ └── migration.sql
│ │ ├── 20251127011835_add_slack_integration/
│ │ │ └── migration.sql
│ │ ├── 20251127015242_add_slack_oauth_to_system_config/
│ │ │ └── migration.sql
│ │ ├── 20251127_add_slack_signing_secret/
│ │ │ └── migration.sql
│ │ ├── 20260219214321_add_license_fields/
│ │ │ └── migration.sql
│ │ ├── 20260219215954_fix_changelog_request_cascade_delete/
│ │ │ └── migration.sql
│ │ ├── 20260220120000_add_timezone_config/
│ │ │ └── migration.sql
│ │ ├── 20260220140000_add_custom_date_templates/
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema/
│ │ ├── README.md
│ │ ├── base.prisma
│ │ ├── enums.prisma
│ │ ├── integrations.prisma
│ │ ├── projects.prisma
│ │ ├── system.prisma
│ │ └── users.prisma
│ ├── schema.prisma.backup
│ └── seed.ts
├── proxy.ts
├── public/
│ └── swagger.json
├── scripts/
│ ├── api/
│ │ ├── generateSwagger.js
│ │ └── generateSwagger.ts
│ ├── ftb/
│ │ ├── index.html
│ │ └── server.js
│ ├── maintenance/
│ │ ├── index.html
│ │ └── server.js
│ ├── nginx-reload.sh
│ ├── utils/
│ │ └── scan.ts
│ └── widget/
│ └── build.ts
├── tailwind.config.ts
├── temp_migration.sql
├── tsconfig.json
├── useful-information-for-development/
│ └── slack_scopes.md
└── widgets/
├── changelog/
│ └── index.js
├── core/
│ └── styles/
│ ├── announcement.css
│ ├── common.css
│ ├── floating.css
│ ├── modal.css
│ ├── reset.css
│ └── variables.css
└── variants/
├── announcement.js
├── classic.js
├── floating.js
└── modal.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
.git
.github
.next
node_modules
.env
.env.*
*.log
README.md
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
.vscode
.eslintcache
coverage
dist
.turbo
public/swagger.json
public/widget-bundle.js
public/widget-bundle.js.map
useful-information-for-development
APIDOCGUIDE.md
CHANGELOG.md
docker-compose-online.yml
docker-compose.yml
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: supernova3339 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: superdevofficial # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yml
================================================
name: 🐛 Bug Report
description: Found a bug? Help us squash it! Every bug report makes Changerawr better.
title: "[Bug]: "
labels: ["bug", "triage"]
assignees:
- Supernova3339
body:
- type: markdown
attributes:
value: |
# 🐛 Thanks for reporting a bug!
Bug reports are super valuable - they help us make Changerawr rock solid for everyone. The more details you provide, the faster we can track down and fix the issue.
**Please search existing issues first** to see if this bug has already been reported. If you find it, give it a 👍!
- type: input
id: contact
attributes:
label: 📧 Contact Details (Optional)
description: How can we reach you if we need more info about this bug?
placeholder: email@example.com or @username
validations:
required: false
- type: textarea
id: bug-description
attributes:
label: 🐛 What happened?
description: Describe the bug clearly and concisely
placeholder: Tell us what went wrong...
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: ✅ What should have happened?
description: What did you expect to happen instead?
placeholder: Describe what you thought would happen...
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: 🔄 Steps to Reproduce
description: How can we reproduce this bug? Be as specific as possible!
placeholder: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
value: |
1.
2.
3.
4.
validations:
required: true
- type: dropdown
id: frequency
attributes:
label: 📊 How often does this happen?
description: Is this bug consistent or intermittent?
options:
- "Every time (100%)"
- "Most of the time (75%+)"
- "Sometimes (25-75%)"
- "Rarely (less than 25%)"
- "Only happened once"
default: 0
validations:
required: true
- type: dropdown
id: browser
attributes:
label: 🌐 Browser
description: Which browser are you using?
multiple: true
options:
- Chrome
- Firefox
- Safari
- Microsoft Edge
- Opera
- Other
- type: dropdown
id: device-type
attributes:
label: 📱 Device Type
description: What device were you using?
options:
- Desktop (Windows)
- Desktop (macOS)
- Desktop (Linux)
- Mobile (iOS)
- Mobile (Android)
- Tablet (iOS)
- Tablet (Android)
default: 0
validations:
required: true
- type: input
id: screen-resolution
attributes:
label: 🖥️ Screen Resolution (Optional)
description: What's your screen resolution? Helpful for UI bugs
placeholder: "e.g., 1920x1080, 1366x768"
validations:
required: false
- type: dropdown
id: changerawr-area
attributes:
label: 🎯 Which part of Changerawr?
description: Where in the app did this bug occur?
options:
- Dashboard / Home
- Changelog Editor
- Project Settings
- User Settings / Profile
- Authentication / Login
- Embeddable Widget
- API / Integrations
- Email Notifications
- GitHub Integration
- Mobile Interface
- Deployment/Pre-Deployment
- Other / Not Sure
default: 10
validations:
required: true
- type: dropdown
id: severity
attributes:
label: ⚡ Bug Severity
description: How much does this impact your use of Changerawr?
options:
- "Low - Minor annoyance, doesn't block my work"
- "Medium - Inconvenient but I can work around it"
- "High - Significantly impacts my workflow"
- "Critical - Completely blocks me from using Changerawr"
default: 0
validations:
required: true
- type: textarea
id: error-messages
attributes:
label: ❌ Error Messages
description: Any error messages, console errors, or warnings you saw?
placeholder: Copy and paste any error messages here...
render: text
validations:
required: false
- type: textarea
id: console-logs
attributes:
label: 🔍 Console Output (For Developers)
description: If you're comfortable with dev tools, paste any relevant console output
placeholder: Right-click → Inspect → Console tab → copy any red errors
render: shell
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: 📝 Additional Context
description: Anything else that might be relevant? Screenshots, network conditions, etc.
placeholder: Any other details that might help us understand this bug...
validations:
required: false
- type: input
id: changerawr-version
attributes:
label: 🦖 Changerawr Version (Optional)
description: If you know the version, it helps! Check the about page in administration settings, or your lib/app-info.ts
placeholder: "e.g., v0.3.9"
validations:
required: false
- type: checkboxes
id: bug-checklist
attributes:
label: 📋 Bug Report Checklist
description: Quick checks to help us process your report
options:
- label: I've searched existing issues and this isn't a duplicate
required: true
- label: I can reproduce this bug consistently
required: false
- label: I've included steps to reproduce the issue
required: true
- label: I'd be willing to test a fix if provided
required: false
- label: I am able to provide a reproduction video in the event that I can not provide reproduction steps ( youtube, loom, etc )
required: true
- label: I have all required ENV variables configured.
required: true
- label: I am using docker ( only check this if you are deploying with docker! )
required: false
- label: I am not using Cloudflare Zero Trust ( it breaks everything! )
required: true
- type: markdown
attributes:
value: |
---
## 🛠️ What happens next?
1. **We'll investigate** - Usually within 24-72 hours
2. **Reproduce the bug** - We'll try to recreate the issue
3. **Ask questions** - We might need more details
4. **Fix it** - Once confirmed, we'll work on a solution
5. **Test & Release** - Fix gets tested and deployed
**Pro tip**: The best bug reports include clear steps to reproduce the issue. If we can reproduce it, we can fix it!
Thanks for helping make Changerawr more stable! 🦖💪
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yml
================================================
name: 🦖 Feature Request
description: Got an idea to make Changerawr even better? We'd love to hear it!
title: "[Feature]: "
labels: ["enhancement", "feature-request"]
assignees:
- Supernova3339
body:
- type: markdown
attributes:
value: |
# 🎉 Thanks for helping make Changerawr better!
We built Changerawr because existing tools didn't cut it. Your ideas help us build exactly what developers and teams actually need.
**Please search existing issues first** to avoid duplicates. If you find a similar request, give it a 👍 instead!
- type: input
id: contact
attributes:
label: 📧 Contact Details (Optional)
description: How can we reach you if we need to discuss this feature?
placeholder: email@example.com or @username
validations:
required: false
- type: textarea
id: feature-description
attributes:
label: 🚀 Feature Description
description: What feature would you like to see added to Changerawr?
placeholder: Be as detailed as you can! What should this feature do?
validations:
required: true
- type: textarea
id: problem-solving
attributes:
label: 🎯 Problem This Solves
description: What problem or pain point would this feature address?
placeholder: Describe the current limitation or frustration this would solve
validations:
required: true
- type: textarea
id: use-case
attributes:
label: 📝 Use Case / User Story
description: How would you (or others) use this feature?
placeholder: "As a [type of user], I want [feature] so that [benefit]"
validations:
required: true
- type: dropdown
id: user-type
attributes:
label: 👤 What best describes you?
description: This helps us understand our user base and prioritize features
options:
- Solo Developer
- Small Team (2-10 people)
- Medium Team (11-50 people)
- Large Team (50+ people)
- Open Source Project Maintainer
- Product Manager
- Other
default: 0
validations:
required: true
- type: dropdown
id: priority
attributes:
label: ⚡ How important is this to you?
description: Help us understand the impact this would have
options:
- "Nice to have - would be cool but not urgent"
- "Useful - would improve my workflow"
- "Important - would solve a real problem I face"
- "Critical - blocking me from using Changerawr effectively"
default: 0
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 🔄 Current Workarounds
description: How are you currently handling this without the feature?
placeholder: Describe any workarounds you're using or tools you're missing
validations:
required: false
- type: textarea
id: implementation-ideas
attributes:
label: 💡 Implementation Ideas (Optional)
description: Any thoughts on how this could work? UI mockups, API endpoints, etc.
placeholder: Don't worry about technical details - but if you have ideas, we'd love to hear them!
validations:
required: false
- type: dropdown
id: feature-area
attributes:
label: 🎨 What area of Changerawr is this for?
description: Which part of the platform would this feature affect?
multiple: true
options:
- Dashboard / UI
- Content Editor
- API / Integrations
- Embeddable Widget
- Email Notifications
- Authentication / Users
- GitHub Integration
- Analytics / Reporting
- Mobile Experience
- Performance
- Other / Not Sure
- type: checkboxes
id: additional-info
attributes:
label: 📋 Additional Information
description: A few quick checks to help us out
options:
- label: I've searched existing issues and this isn't a duplicate
required: true
- label: I'd be willing to test this feature if implemented
required: false
- label: I'd be interested in contributing to this feature's development
required: false
- type: markdown
attributes:
value: |
---
## 🙏 What happens next?
1. **We'll review your request** - Usually within a few days
2. **Community feedback** - Others can vote with 👍 and add comments
3. **Discussion** - We might ask follow-up questions
4. **Prioritization** - Popular requests with clear use cases get priority
5. **Development** - If accepted, we'll add it to our roadmap
**Remember**: Changerawr is built by developers for developers. The best feature requests come from real problems you're facing!
Thanks for helping make Changerawr rawrsome! 🦖
================================================
FILE: .github/workflows/build-platform.yml
================================================
name: Build Platform Image
on:
workflow_call:
inputs:
platform:
required: true
type: string
description: 'Platform to build (linux/amd64 or linux/arm64)'
image_name:
required: true
type: string
description: 'Image name'
version:
required: true
type: string
description: 'Version label for the Docker image tag'
no_cache:
required: false
type: boolean
default: false
description: 'Disable Docker layer cache'
env:
REGISTRY: ghcr.io
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
packages: write
actions: write
steps:
- name: Checkout (HEAD of current branch)
uses: actions/checkout@v4
- name: Set up QEMU (for ARM builds)
if: inputs.platform == 'linux/arm64'
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set platform suffix
id: platform
run: |
PLATFORM_SUFFIX=$(echo "${{ inputs.platform }}" | sed 's|linux/||')
echo "suffix=$PLATFORM_SUFFIX" >> $GITHUB_OUTPUT
- name: Delete GHA Docker cache (force rebuild)
if: inputs.no_cache
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "🗑️ Deleting cached Docker layers for scope: ${{ steps.platform.outputs.suffix }}"
gh cache list --repo ${{ github.repository }} --json id,key \
| jq -r '.[] | select(.key | contains("${{ steps.platform.outputs.suffix }}")) | .id' \
| xargs -I{} gh cache delete {} --repo ${{ github.repository }} \
|| echo "No cache entries found or deletion skipped"
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: ${{ inputs.platform }}
push: true
no-cache: ${{ inputs.no_cache }}
build-args: |
CACHEBUST=${{ inputs.no_cache && github.run_id || '1' }}
tags: ${{ env.REGISTRY }}/${{ inputs.image_name }}:${{ inputs.version }}-${{ steps.platform.outputs.suffix }}
cache-from: ${{ inputs.no_cache == false && format('type=gha,scope={0}', steps.platform.outputs.suffix) || '' }}
cache-to: ${{ inputs.no_cache == false && format('type=gha,mode=max,scope={0}', steps.platform.outputs.suffix) || '' }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.version=${{ inputs.version }}
- name: Build complete
run: |
echo "✅ Built ${{ inputs.platform }} image: ${{ env.REGISTRY }}/${{ inputs.image_name }}:${{ inputs.version }}-${{ steps.platform.outputs.suffix }}"
================================================
FILE: .github/workflows/docker-build.yml
================================================
name: Build and Push Docker Image
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., v1.0.4)'
required: true
type: string
force_rebuild:
description: 'Force rebuild (ignore cache)'
required: false
type: boolean
default: false
env:
REGISTRY: ghcr.io
jobs:
info:
runs-on: ubuntu-latest
outputs:
image_name: ${{ steps.meta.outputs.image_name }}
version: ${{ steps.meta.outputs.version }}
steps:
- name: Show build info
id: meta
run: |
IMAGE_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
TRIGGER="Manual (workflow_dispatch)"
else
VERSION="${{ github.ref_name }}"
TRIGGER="Tag push"
fi
echo "🚀 Docker Build Starting"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📦 Repository: ${{ github.repository }}"
echo "🏷️ Version: $VERSION"
echo "🎯 Trigger: $TRIGGER"
echo "🐳 Registry: ${{ env.REGISTRY }}"
echo ""
echo "📋 Will be tagged as:"
echo " • ${{ env.REGISTRY }}/$IMAGE_NAME:$VERSION"
echo " • ${{ env.REGISTRY }}/$IMAGE_NAME:latest"
echo ""
echo "🏗️ Platforms: linux/amd64, linux/arm64"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "image_name=$IMAGE_NAME" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
build-amd64:
needs: info
uses: ./.github/workflows/build-platform.yml
with:
platform: 'linux/amd64'
image_name: ${{ needs.info.outputs.image_name }}
version: ${{ needs.info.outputs.version }}
no_cache: ${{ inputs.force_rebuild || false }}
build-arm64:
needs: info
uses: ./.github/workflows/build-platform.yml
with:
platform: 'linux/arm64'
image_name: ${{ needs.info.outputs.image_name }}
version: ${{ needs.info.outputs.version }}
no_cache: ${{ inputs.force_rebuild || false }}
create-manifest:
needs: [info, build-amd64, build-arm64]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
packages: write
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push multi-arch manifest
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}:${{ needs.info.outputs.version }} \
${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}:${{ needs.info.outputs.version }}-amd64 \
${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}:${{ needs.info.outputs.version }}-arm64
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}:latest \
${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}:${{ needs.info.outputs.version }}-amd64 \
${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}:${{ needs.info.outputs.version }}-arm64
- name: Summary
run: |
echo "## 🚀 Docker Image Built Successfully!" >> $GITHUB_STEP_SUMMARY
echo "**Image:** \`${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Version:** \`${{ needs.info.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Tags:** \`${{ needs.info.outputs.version }}\`, \`latest\`" >> $GITHUB_STEP_SUMMARY
echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Pull:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.REGISTRY }}/${{ needs.info.outputs.image_name }}:${{ needs.info.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.idea/dbnavigator.xml
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env
.api-keys-for-development.sensitive.donotcommitthisever.env.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# mistral experiments
/app/api/experiments/mistral-ai/chat/route.ts
/app/experiments/mistral-ai/page.tsx
# widget build files
/public/widgets
/public/widget-announcement.js
/public/widget-bundle.js
/public/widget-classic.js
/public/widget-floating.js
/public/widget-modal.js
# mapping files ( generated )
/public/widget-announcement.js.map
/public/widget-bundle.js.map
/public/widget-classic.js.map
/public/widget-floating.js.map
/public/widget-modal.js.map
# sensitive changerawr shenanigans
.changerawr
.changerawr/donotdeletethisfolder/donotdeletethisfile.chrcli.json
nul
null
.chtsredevmscli
.claude
# sponsorer server
/sponsor-server
# user requests to add
CLAUDE.md
================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
================================================
FILE: .idea/changerawr.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
================================================
FILE: .idea/dataSources.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="postgres@localhost" uuid="4fcad316-be2f-4d50-a0f9-b7d894b57b4d">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="changerawr@localhost" uuid="0a8987b9-d685-49ea-87ac-49c168eafa51">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/changerawr</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>
================================================
FILE: .idea/data_source_mapping.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$PROJECT_DIR$/prisma/migrations/20251111210147_add_api_key_and_widgets/migration.sql" value="0a8987b9-d685-49ea-87ac-49c168eafa51" />
</component>
</project>
================================================
FILE: .idea/db-forest-config.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="db-tree-configuration">
<option name="data" value="---------------------------------------- 1:0:4fcad316-be2f-4d50-a0f9-b7d894b57b4d 2:0:0a8987b9-d685-49ea-87ac-49c168eafa51 " />
</component>
</project>
================================================
FILE: .idea/discord.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>
================================================
FILE: .idea/inspectionProfiles/Project_Default.xml
================================================
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="106" name="TypeScript" />
</Languages>
</inspection_tool>
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IncorrectFormatting" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>
================================================
FILE: .idea/jsLibraryMappings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/changerawr.iml" filepath="$PROJECT_DIR$/.idea/changerawr.iml" />
</modules>
</component>
</project>
================================================
FILE: .idea/sqldialects.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/prisma/migrations/20251111210147_add_api_key_and_widgets/migration.sql" dialect="PostgreSQL" />
</component>
</project>
================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
================================================
FILE: APIDOCGUIDE.md
================================================
# API Documentation Generation Guide
## Overview
When writing API documentation for routes, focus on creating clear, consistent, and comprehensive JSDoc comments that can be automatically parsed into OpenAPI/Swagger specifications. This guide outlines the required format and components for documenting API endpoints.
## Documentation Format
Each route handler should be documented using JSDoc comments with specific tags that map to OpenAPI/Swagger fields.
### Basic Structure
```typescript
/**
* Brief description of the endpoint
* @method HTTP_METHOD
* @description Detailed description of what the endpoint does
* @body Request body schema in JSON format
* @response Status code and response schema
* @error Error status codes and descriptions
* @secure (optional) Security requirements
*/
```
## Required Tags
### @method
Specifies the HTTP method. Must be one of: GET, POST, PUT, DELETE, PATCH
```typescript
@method POST
```
### @description
Detailed description of the endpoint's functionality
```typescript
@description Authenticates a user and returns JWT tokens for subsequent requests
```
### @body (for POST/PUT/PATCH)
JSON schema of the request body. Must be valid JSON format.
```typescript
@body {
"type": "object",
"required": ["email", "password"],
"properties": {
"email": {
"type": "string",
"format": "email",
"description": "User's email address"
},
"password": {
"type": "string",
"minLength": 8,
"description": "User's password"
}
}
}
```
### @response
Response schema for successful requests. Include status code and JSON schema.
```typescript
@response 200 {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"email": { "type": "string" }
}
}
```
### @error
Error responses with status codes and descriptions.
```typescript
@error 400 Validation failed - Invalid input format
@error 401 Unauthorized - Invalid credentials
@error 404 Resource not found
```
### @secure (Optional)
Authentication requirements. Default is 'cookieAuth' for cookie-based authentication.
```typescript
@secure bearerAuth
```
## Examples
### GET Endpoint
```typescript
/**
* @method GET
* @description Retrieves user profile information
* @response 200 {
* "type": "object",
* "properties": {
* "id": { "type": "string" },
* "name": { "type": "string" },
* "email": { "type": "string" },
* "role": { "type": "string" }
* }
* }
* @error 401 Unauthorized - Please log in
* @error 404 User not found
* @secure cookieAuth
*/
export async function GET() {
// Implementation
}
```
### POST Endpoint
```typescript
/**
* @method POST
* @description Creates a new user account
* @body {
* "type": "object",
* "required": ["email", "password", "name"],
* "properties": {
* "email": {
* "type": "string",
* "format": "email",
* "description": "User's email address"
* },
* "password": {
* "type": "string",
* "minLength": 8,
* "description": "User's password"
* },
* "name": {
* "type": "string",
* "description": "User's full name"
* }
* }
* }
* @response 201 {
* "type": "object",
* "properties": {
* "id": { "type": "string" },
* "email": { "type": "string" },
* "name": { "type": "string" }
* }
* }
* @error 400 Validation failed
* @error 409 Email already exists
*/
export async function POST() {
// Implementation
}
```
## Common Response Types
### Paginated Response
```typescript
@response 200 {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" }
}
}
},
"pagination": {
"type": "object",
"properties": {
"total": { "type": "number" },
"page": { "type": "number" },
"pageSize": { "type": "number" },
"totalPages": { "type": "number" }
}
}
}
}
```
### Error Response
```typescript
@error 400 {
"type": "object",
"properties": {
"error": { "type": "string" },
"details": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": { "type": "string" },
"message": { "type": "string" }
}
}
}
}
}
```
## Best Practices
1. **Consistency**: Use consistent naming and formatting across all endpoints
2. **Completeness**: Document all possible response codes and edge cases
3. **Validation**: Always include validation requirements in the schema
4. **Security**: Specify authentication requirements for protected routes
5. **Examples**: Provide examples for complex request/response schemas
6. **Descriptions**: Write clear, concise descriptions for all parameters
## Common Schemas
Consider extracting common schemas to reduce duplication:
### User Schema
```typescript
const UserSchema = {
"type": "object",
"properties": {
"id": { "type": "string" },
"email": { "type": "string", "format": "email" },
"name": { "type": "string" },
"role": { "type": "string", "enum": ["user", "admin"] }
}
}
```
### Pagination Schema
```typescript
const PaginationSchema = {
"type": "object",
"properties": {
"page": { "type": "number", "minimum": 1 },
"pageSize": { "type": "number", "minimum": 1 },
"total": { "type": "number" },
"totalPages": { "type": "number" }
}
}
```
Remember to validate your documentation against the OpenAPI specification to ensure compatibility with Swagger UI and other API documentation tools.
================================================
FILE: CHANGELOG.md
================================================
<!-- Generated by Changerawr CLI on 2026-04-16T07:21:32.297Z -->
# Changelog
All notable changes to this project will be documented in this file.
## v1.0.6
- **Version 1.0.6 - 2026-04-16**: :::warning
There are security vulnerabilities in previous releases, it is recommended to upgrade as soon as possible to this one. There are also a breaking change, set `SETUP_COMPLETE=true` in your .env file as this is how to complete the setup wizard now. I strongly recommend re-rolling all API keys.
:::
## Features
- **Slack Integration** - You can now automatically post published entries to your Slack!
- **Timezones** - Added timezone support per-user and globally, allowing for much more efficient collaboration between users with different time zones!
- **SAML** - Adds support for SAML to SSO, adding a new method available for configuring single-sign-on.
- *SSL Certificates for custom domains** - You can now get SSL certificates for custom domains! Just follow the nginx-sidecar documentation to get set up.
- **IP Whitelisting** - You can now whitelist IP addresses to access the main panel. Note that this requires the nginx-sidecar to be set up.
- **Version Templates** - Configure custom version format templates
## Improvements
- **Extended Project Limits ** - Project limits have been extended for entries. If you wish to go beyond, please sponsor my work.
- **UI Improvements** - Did various UI improvements to the administrative interface & fully redesigned the version picker
- **Update Bar** - Whenever an update is available, you'll be much more likely to know thanks to the new pop-up.
## Bug Fixes
- **Redirect Loop** - fixed issues with an infinite redirect loop after setup
- **Strict Validation Local Domain** - Fixed issues with any domain being blocked for requests, preventing e.g., cloudflareinsghts from working, which subsequently blocked the panel from loading alltogether itself.
- **System user show-up in admin dashboard** - Fixed an issue where the system user used for audit logs was being counted as a user in the total user count.
## Other
- **Security Vulnerability patches** - A few vulnerabilities were disclosed to me, these have been patched in this release.
- **Dependency Updates** - Updated to the latest dependencies, Nodemailer 8, and changerawr universal markdown 1.2.0!
## v1.0.5
- **Version 1.0.5 - 2025-11-13**: :::warning
There are breaking changes in this release.
:::
If you are using widgets at all, you will have to recreate them. If you are not using widgets, this does not affect you at all.
---
## Bug Fixes
- **Email validation allowed for uppercase emails** - If you invited a email with any uppercase characters, you would not be able to login using it. This has been fixed!
- **undefined showing up in the create SSO provider modal** - Sometimes, this would show up when you get a callback URL. This has been fixed.
### Features
- **Catch Up!** - Missed too much while you were gone? Now you can catch up! `feature requested by my founder friends @ forento`
- **Manually set when entry has been published** - I'll admit, I hated adding this request. Being transparent with your users is extremely important. However, it is now available for those who need it. Have fun!
- **API Key Permissions + Project-Level API Keys** - Added a full permission system to API keys as well as making the ability to have them be project-level.
- **Migrated from internal engine to package** - Migrated from the internal CUM engine to the package. Also adds support for Tables and discord-flavored SubText!
- **Redid widgets entirely, proving four variants that can be customized to your hearts content!**
## Improvements
- **Share on publish** - You can now share your changelog to your email subscribers when you publish an entry!
- **Improved bookmarks usage** - Dedicated pages for bookmarking + QOL controls!
- **You can now view changelog entries individually** - A dedicated page is available now for viewing a specific changelog entry.
- **NextJS Upgrade** - Upgraded to NextJS 16!
## 1.0.4
- **Version 1.0.4 - 2025-?-?**: :::warning
There are no breaking changes in this release.
:::
### Features
- **Data Importing** - You can now import data from various sources to jump-start your Changerawr project!
- **CUM** - Changerawr Universal Markdown engine introduces more functionality for the content editor.As of writing, Embeds, Buttons, and Alerts are available!
Provides a better experience overall, improving the parsing engine, adding tokenization, and improving overall usability!
### Improvements
- **Redesigned Projects Dashboard** - Updated the dashboard at (/dashboard/projects/projectId) to be less cluttered and more professional.
- **Updated Projects Fetch Pagination** - Updated the default fetch for projects to 50 entries when listing.
- **Redesigned Main Dashboard** - Redesigned the dashboard at (/dashboard), it needed a facelift and has been the same since 0.3.0 :(
- **Content Editor Upgrades** - Introduces the CUM ( Changerawr Universal Markdown ) rendering engine for a better changelog experience.
- **Improved Modal UI** - Added depth and optical borders to the modal UI. Also adds a "disableClose" prop for the rare case in which Changerawr is doing something and needs to let you know of its progress.
hello world!
## 1.0.3
- **Version 1.0.3 - 2025-07-02**: **Features**
- **Changerawr CLI** - Now it's easier then ever to get started with Changerawr! just do:
```shell
npm install -g changerawr
```
You can find the source code at <https://github.com/changerawr/cli> - would super appreciate a star!
- Native [Pocket ID](https://pocket-id.org) Support. I really love [Pocket ID](https://pocket-id.org) so now you can configure it in three clicks from the installation wizard!
**Bug Fixes**
- **Migration Fixes** - Fixed an issue that broke deployment
- **Setup Wizard Fixes** - Rewrote logic that broke older APIs, wizard will no longer skip steps, allowing you to finish setup completely.
## 1.0.2
- **Version 1.0.2 - 2025-06-24**: **Features**
- **Custom Domains** - Added support for custom domains for a public changelog page. This was painful to implement :(
- **Scheduled Publishing** - You can now schedule your changelog to be posted at a later date.
- **Full-Text search** - Adds a command palette ( Ctrl + K / Cmd + K ) to search EVERYTHING :)
- **Colored Tags** - Add color to your tags to make them pop!
- **Telemetry** - Adds telemetry so the Changerawr team can collect anonymous usage statistics.
**Improvements**
- **UI Improvements** - Updated Request Management interface to provide more information to assist administrators with approving/denying a staff request.
- **SSO Improvements** - Added a section to user settings so you can see what providers you have connected with!
## 1.0.1
- **Version 1.0.1 - 2025-06-22**: **Bug Fixes**
- **Fixed LOGIN_ATTEMPT issue** - Thanks @aixxo ( on GitHub ) for reporting this! It has been fixed in this release.
- **Security Vulnerability Patched** - AI assistant API key could be found without authenticating, this has been encrypted and fixed. Thanks to Codycody31 on GitHub for reporting this! You should set a brand new API key if you are using the AI integration.
- **GitHub Integration scroll fix** - Fixed an issue that prevented you from seeing all of the content, which would get cut off and prevent interaction with the modal. ( you could not insert the content, as an example )
**Improvements**
- **HIBP Integration** - When logging in, you will be notified if your password was found in a data breach.
## 1.0.0
- **Version 1.0.0 - 2025-06-15**: **Other**
- **1.0.0 Release** - Thanks for your support of Changerawr! Have fun!
## 0.4.0
- **Version 0.4.0 - 2025-06-13**: **Features**
- **Changelog Analytics** - Detailed analytics of your overall instance and projects! Cookieless, GDPR compliant, and 100% anonymized
- **Easypanel Integration** - Changerawr can now update automagically if you use Easypanel for deployment!
- **Automatic OAuth2 Setup** - If you use Easypanel OAuth2, you have the option to automatically setup OAuth2 for SSO if you have all the required variables set beforehand.
**Bug Fixes**
- **Bug Fixes** - A multitude of fixes and quirks that sometimes don't work correctly. Hopefully, this is every issue as of now fixed!
**Improvements**
- **Redesigned Components** - Redesigned some components
## 0.3.9
- **Version 0.3.9 - 2025-06-10**: **Features**
- **Code Analyzation for GitHub Integration** - Changerawr can now look at your changed code and write a better changelog!
**Bug Fixes**
- **Account Deletion Fix** - Fixed an issue that prevented administrators from deleting a user's account. This process will anonymize all their data and actions while preserving their contributions.
**Improvements**
- **Redesigned the New Project page** - Gave a much-needed facelift to the new project page so it feels more roomy. Has confetti now, success messages, and more excitement!
## 0.3.8
- **Version 0.3.8 - 2025-06-10**: **Features**
- **Invite Your Team!** - You can now invite your team early in the initial setup wizard, rawrsome!
**Bug Fixes**
- **Changelog Editor Fixes** - Fixed the version picker which would let you select versions already used, leading to issues. It will now check for overlapping before allowing you to set a version for your changelog.
**Improvements**
- **Component Redesigns** - Redesigned the Version picker and Editor header
## 0.3.7
- **Version 0.3.7 - 2025-06-09**: **Bug Fixes**
- **Theme Switcher Fixes** - Fixed broken theme switcher logic, proper sync and no refreshing now!
**Improvements**
- **SSO Improvements** - You can now set all of the URLs for an SSO provider!
## 0.3.6
- **Version 0.3.6 - 2025-06-08**: **Bug Fixes**
- **Fix Audit Log Issues** - Fixed any remaining bugs with audit logs.
**Improvements**
- **AI URL Change** - Changed some of the URLs the AI Assistant page links to.
- **Improved Generation UI For GitHub** - Improved the generation UI and added more options
## 0.3.5
- **Version 0.3.5 - 2025-06-07**: **Features**
- **GitHub integration** - You can now use GitHub with Changerawr!
**Other**
- **Routine Maintenance** - Package Updates and Bug Fixes have been triaged successfully with this release.
## 0.3.4
- **Version 0.3.4 - 2025-05-15**: **Features**
- **Requests Manager** - Staff users can now view their permission requests!
**Bug Fixes**
- **TagAI Fixes** - Fixed TagAI to be more successful when recommending tags for a user's changelog entry ( e.g. fails less )
## 0.3.3
- **Version 0.3.3 - 2025-05-13**: **Features**
- **Title Generation** - AI can now analyze your writing to pick out the perfect title for your next changelog
- **AI Assisted Tagging** - AI can now assist you with tagging your changelog
- **Add New Tags** - You can now add a tag if it doesn't exist
**Improvements**
- **Better AI Assistant Design** - Redesigned AI Assistant
## 0.3.1
- **Version 0.3.1 - 2025-05-12**: **Features**
- **New Feature** - Added "What's New" model to inform of latest changes from the most recent Changerawr update
**Bug Fixes**
- **Bug Fixes** - Fixed a few bugs with tokenization and the AI assistant
## 0.3.2
- **Version 0.3.2 - 2025-05-12**: **Features**
- **'What's New' Modal Available on the About Page** - The "What's New" modal is now accessible from the About page.
**Improvements**
- **Redesigns** - Redesigned "What's New" modal.
## 0.3.0
- **Version 0.3.0 - 2025-05-11**: **Features**
- **AI Assistant** - Adds an AI assistant to Changerawr!
- **"What's New" model added** - Allow administrators to see what's new in Changerawr for the latest update!
**Bug Fixes**
- **Fixed Authentication Redirect** - Fixed authentication pages not redirecting once a session was obtained
**Improvements**
- **Redone Content Editor** - Redid the entire Content Editor from scratch
================================================
FILE: Caddyfile
================================================
# Caddyfile for Changerawr with on-demand TLS
# Caddy automatically obtains and renews Let's Encrypt certificates
{
# Global options
email {$ACME_EMAIL}
# Enable on-demand TLS with ask endpoint
on_demand_tls {
ask http://app:3000/api/domain-check
}
}
# Catch-all for custom domains
:80, :443 {
# Enable on-demand TLS for all incoming requests
tls {
on_demand
}
# Reverse proxy all requests to the Next.js app
reverse_proxy app:3000 {
# Pass original host header
header_up Host {host}
# Pass real IP and protocol
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
}
# Enable compression
encode gzip
# Logging
log {
output stdout
format console
}
}
================================================
FILE: Dockerfile
================================================
FROM node:22-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Copy package files
COPY package.json package-lock.json* ./
# CACHEBUST forces npm install to re-run even when package files are unchanged
ARG CACHEBUST=1
RUN echo "Cache bust: $CACHEBUST" && npm install --legacy-peer-deps
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
# Copy the entire project
COPY . .
# Generate Prisma client
RUN npx prisma generate
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
ENV CI_BUILD_MODE 1
ENV DOCKER_BUILD 1
# Build the app
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Install all dependencies to satisfy entrypoint requirements
COPY package.json package-lock.json* ./
RUN npm install --legacy-peer-deps
# Install Prisma client with exact version match
RUN npm uninstall prisma @prisma/client --legacy-peer-deps
RUN npm install prisma@6.7.0 @prisma/client@6.7.0 --legacy-peer-deps
# Install tsx explicitly
RUN npm install -g tsx
# Install esbuild for widget
RUN npm install esbuild --legacy-peer-deps
# Install JSDOC
RUN npm install -g jsdoc
# Add bash, nginx, and other dependencies for the entry script
RUN apk add --no-cache bash wget nginx
# Install nginx-agent from GitHub
RUN wget -q https://github.com/Changerawr/nginx-agent/archive/refs/heads/master.tar.gz -O /tmp/nginx-agent.tar.gz && \
mkdir -p /nginx-agent && \
tar -xzf /tmp/nginx-agent.tar.gz -C /nginx-agent --strip-components=1 && \
rm /tmp/nginx-agent.tar.gz && \
cd /nginx-agent && \
npm install --production
# Create nginx directories
RUN mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/ssl/changerawr /var/log/nginx /var/lib/nginx/tmp /run/nginx
# Copy the entire project from the builder stage
COPY --from=builder /app .
# Copy maintenance page and server script
COPY scripts/maintenance/index.html ./index.html
COPY scripts/maintenance/server.js ./scripts/maintenance/server.js
# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Copy and make nginx reload script executable
COPY scripts/nginx-reload.sh /usr/local/bin/nginx-reload.sh
RUN chmod +x /usr/local/bin/nginx-reload.sh
# Ensure the entrypoint script is executable
RUN chmod +x ./docker-entrypoint.sh
EXPOSE 3000 80 443
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
# Use entrypoint for running the build scripts before starting the server
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["npm", "start"]
================================================
FILE: Dockerfile.compose
================================================
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Copy package files
COPY package.json package-lock.json* ./
RUN npm install --legacy-peer-deps
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
# Copy the entire project
COPY . .
# Generate Prisma client
RUN npx prisma generate
# Next.js collects completely anonymous telemetry data about general usage.
ENV NEXT_TELEMETRY_DISABLED 1
ENV DOCKER_BUILD 1
ENV CI_BUILD_MODE 1
# Build the app
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Install all dependencies to satisfy entrypoint requirements
COPY package.json package-lock.json* ./
RUN npm install --legacy-peer-deps
# Install Prisma client with exact version match
RUN npm uninstall prisma @prisma/client --legacy-peer-deps
RUN npm install prisma@6.3.1 @prisma/client@6.3.1 --legacy-peer-deps
# Install tsx explicitly
RUN npm install -g tsx
# Install esbuild for widget
RUN npm install esbuild --legacy-peer-deps
# Install JSDOC
RUN npm install -g jsdoc
# Add bash and curl for the entry script and health checks
RUN apk add --no-cache bash curl
# Copy the entire project from the builder stage
COPY --from=builder /app .
# Copy maintenance page and server script
COPY scripts/maintenance/index.html ./index.html
COPY scripts/maintenance/server.js ./scripts/maintenance/server.js
# Copy entrypoint script and fix line endings
COPY docker-entrypoint-compose.sh /app/docker-entrypoint-compose.sh
RUN sed -i 's/\r$//' /app/docker-entrypoint-compose.sh && chmod +x /app/docker-entrypoint-compose.sh
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
# Use npm script that explicitly calls bash
ENTRYPOINT ["bash", "docker-entrypoint-compose.sh"]
================================================
FILE: LICENSE
================================================
CHANGERAWR NON-COMMERCIAL OPEN SOURCE LICENSE
Copyright (c) 2025 Supernova Software, LLC. All rights reserved.
This software and associated documentation files (the "Software") are made available
under the terms of this license. The source code is publicly accessible and may be
freely used, modified, and distributed subject to the restrictions outlined below.
GRANT OF LICENSE
Supernova Software, LLC grants you a worldwide, royalty-free, non-exclusive license
to use, copy, modify, merge, publish, and distribute the Software and derivative
works thereof, subject to the following conditions.
PERMITTED USES
You may freely:
1. Use the Software for any purpose, including commercial purposes, provided you
do not profit from the Software itself.
2. Modify the Software and create derivative works, including closed-source versions,
provided you comply with the terms of this license.
3. Fork the repository and maintain your own version of the Software.
4. Distribute the Software, modified or unmodified, in source or binary form.
5. Deploy the Software on your own infrastructure, including for use in commercial
organizations and business operations.
6. Contribute improvements, bug fixes, and features back to the project.
PROHIBITED USES
The following uses are strictly prohibited unless you are Supernova Software, LLC
or an entity explicitly authorized in writing by Supernova Software, LLC:
1. Profiting from the Software: You may not sell, rent, lease, license, or otherwise
monetize the Software or any derivative work. This includes but is not limited to:
- Selling access to the Software as a product or service
- Charging fees for hosted instances of the Software
- Offering the Software as part of a paid subscription or service
- Selling modified or unmodified versions of the Software
- Generating revenue directly from providing access to the Software's functionality
2. Billing System Integration: You may not integrate any billing system, payment
processor, paywall, subscription mechanism, or any other monetization feature
into the Software or derivative works that would allow charging users for the
Software itself or its features. This prohibition applies regardless of whether
the fork is open source or closed source.
3. Trademark Misuse: You may not use the names "Changerawr," "Supernova3339," or
"Supernova Software, LLC," along with any associated logos or branding, in a
manner that suggests endorsement or affiliation without express written permission.
4. Copyright Removal: You may not remove, obscure, or alter any copyright notices,
license terms, or attribution present in the Software or documentation under any
circumstances. This applies even if you use only a portion of the code.
CLARIFICATION OF COMMERCIAL USE
You MAY use the Software in a commercial environment (e.g., within your business,
as a tool for your operations, or as part of your internal infrastructure). What
you CANNOT do is make money by selling the Software itself or charging others for
access to it.
CONTRIBUTIONS
By submitting code, documentation, or other materials to this project:
1. You grant Supernova Software, LLC an irrevocable, perpetual, worldwide, royalty-free
license to use, reproduce, modify, display, distribute, and sublicense your
contributions in any manner, including in commercial products.
2. You represent that you have all necessary rights to grant this license and that
your contributions do not infringe any third party rights.
3. You acknowledge that Supernova Software, LLC has no obligation to incorporate
your contributions and may use them without restriction.
INTELLECTUAL PROPERTY AND ATTRIBUTION
All copyright and intellectual property rights in the original Software remain with
Supernova Software, LLC. While the Software is open source and freely available,
this license does not transfer ownership of the intellectual property.
All copies and derivative works must retain the original copyright notice and this
license. Modified versions should clearly indicate what changes were made and by whom.
CRITICAL: Even if you extract or use only a portion of the code from this Software,
you MUST retain all original copyright notices in your project. This requirement
applies to all uses without exception, including closed-source forks, commercial
applications, and packages published on any registry.
ENFORCEMENT
Violation of this license constitutes copyright infringement and breach of contract.
Supernova Software, LLC reserves the right to pursue all available legal remedies,
including injunctive relief and monetary damages, against any party that violates
these terms.
TERMINATION
This license is effective until terminated. Supernova Software, LLC may terminate
your rights under this license immediately if you fail to comply with any term.
Upon termination:
1. You must immediately cease all use of the Software.
2. You must destroy all copies of the Software in your possession or control.
3. You must certify in writing to Supernova Software, LLC that you have complied
with these requirements, if requested.
Termination does not limit any other rights or remedies available to Supernova
Software, LLC.
FEATURE GUARANTEE
Supernova Software, LLC guarantees that all features of Changerawr will remain free
and accessible at no cost, regardless of future developments or service offerings.
This guarantee applies to the core Software as distributed in the public repository.
The sole exception to this guarantee is if Supernova Software, LLC creates an official
managed cloud hosting service. In such case, fees may be charged exclusively to cover:
- Server and infrastructure costs for the client's virtual private server (VPS)
- Management and maintenance services
- Service-based add-ons that do not affect the core Software functionality
Service-based add-ons are limited to operational services such as prioritized support,
dedicated single sign-on (SSO) infrastructure, enhanced SLA agreements, or professional
services. Add-ons will NEVER include software features, integrations, or functionality
that affects the Changerawr core itself. All software features and integrations remain
free in both self-hosted and any potential cloud offerings.
Any such cloud offering would be optional, and the self-hosted version of Changerawr
would remain completely free with all features intact.
WARRANTY DISCLAIMER
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED,
STATUTORY, OR OTHERWISE. THIS INCLUDES, WITHOUT LIMITATION, ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT.
SUPERNOVA SOFTWARE, LLC MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY,
RELIABILITY, AVAILABILITY, TIMELINESS, SECURITY, OR ACCURACY OF THE SOFTWARE.
NO WARRANTIES ARE PROVIDED UNLESS EXPLICITLY STATED IN A SEPARATE WRITTEN AGREEMENT
BETWEEN YOU AND SUPERNOVA SOFTWARE, LLC. THE ABSENCE OF SUCH AN AGREEMENT MEANS
YOU RECEIVE THE SOFTWARE WITH ABSOLUTELY NO WARRANTY WHATSOEVER.
LIMITATION OF LIABILITY
IN NO EVENT SHALL SUPERNOVA SOFTWARE, LLC BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES
FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION,
OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
SOFTWARE, EVEN IF SUPERNOVA SOFTWARE, LLC HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
GOVERNING LAW
This license shall be governed by and construed in accordance with the laws of the
jurisdiction in which Supernova Software, LLC is registered, without regard to its
conflict of law provisions. Any legal action or proceeding arising under this license
shall be brought exclusively in the courts of that jurisdiction.
SEVERABILITY
If any provision of this license is found to be unenforceable or invalid, that
provision shall be limited or eliminated to the minimum extent necessary so that
this license shall otherwise remain in full force and effect.
ENTIRE AGREEMENT
This license constitutes the entire agreement between you and Supernova Software, LLC
concerning the Software and supersedes any prior agreements or understandings.
CONTACT
For licensing inquiries, alternative licensing arrangements, or clarification of
these terms, contact Supernova Software, LLC through
https://github.com/Supernova3339/changerawr.
NO WAIVER
The failure of Supernova Software, LLC to enforce any right or provision of this
license shall not constitute a waiver of such right or provision.
================================================
FILE: PROJECT_INDEX.md
================================================
# Project Index: Changerawr
Generated: 2026-04-15
## Overview
Changerawr is a self-hosted changelog management platform with AI assistance, custom domains, SSO/SAML, Slack/GitHub integrations, and embeddable widgets.
**License**: CNC OSL (Non-Commercial Open Source)
---
## 📁 Project Structure
```
changerawr/
├── app/ # Next.js App Router (pages + API routes)
│ ├── (auth)/ # Auth page group (login, register, setup, 2FA)
│ ├── (email)/ # Email-related pages (unsubscribed)
│ ├── api/ # 145+ API route handlers
│ ├── dashboard/ # Authenticated dashboard
│ │ ├── admin/ # Admin panel (users, system config, SSO, audit)
│ │ ├── projects/ # Project management & changelog editor
│ │ └── settings/ # User settings
│ ├── changelog/ # Public changelog pages + RSS feeds
│ └── .well-known/ # ACME challenge routes
├── components/ # React UI components by feature
├── lib/ # Business logic, utils, auth, services
├── hooks/ # 13 custom React hooks
├── prisma/schema/ # Modular Prisma schema (6 files)
├── emails/ # Email template components
├── context/ # React context providers (auth, setup)
├── widgets/ # Embeddable changelog widget source
├── scripts/ # Build & maintenance scripts
└── public/ # Static assets
```
---
## 🚀 Entry Points
| Path | Purpose |
|------|---------|
| `app/layout.tsx` | Root layout with providers |
| `app/page.tsx` | Root redirect |
| `app/(auth)/setup/page.tsx` | First-run setup wizard |
| `lib/api/middleware.ts` | API request middleware (auth, permissions) |
| `next.config.ts` | Next.js config (React Compiler, Turbopack) |
| `docker-entrypoint.sh` | Container startup |
---
## 🔐 Authentication System
- **Token**: JWT in `accessToken` cookie — verified via `verifyAccessToken` from `lib/auth/tokens`
- **Methods**: Password, OAuth, SAML/SSO, Passkey/WebAuthn
- **Roles**: `ADMIN`, `STAFF`, `VIEWER`
- **2FA Modes**: `NONE`, `PASSKEY_PLUS_PASSWORD`, `PASSWORD_PLUS_PASSKEY`
- **CLI Auth**: Token-based code flow at `/api/auth/cli/*`
Key auth files:
- `lib/auth/tokens.ts` — JWT generation/verification
- `lib/auth/oauth.ts` — OAuth 2.0 provider integration
- `lib/auth/saml.ts` — SAML implementation
- `lib/auth/webauthn.ts` — Passkey/WebAuthn
- `lib/api/permissions.ts` — Permission checking
- `lib/api/route-permissions.ts` — Route-level permission config
---
## 📦 Core Modules
### API Layer (`app/api/`)
Route groups:
- `auth/` — Login, register, OAuth, SAML, passkeys, CLI, password reset
- `admin/` — Config, users, AI settings, SSO providers, API keys, audit logs
- `projects/[projectId]/` — CRUD, changelog entries, integrations, analytics
- `changelog/` — Public access, subscriptions, RSS
- `custom-domains/` — Domain verify, SSL management, browser rules
- `acme/` — Let's Encrypt certificate issuance/renewal
- `integrations/slack/` — Slack OAuth callback
- `config/timezone` — Public effective timezone (no auth)
- `health` — Health check
### Services Layer (`lib/services/`)
Business logic separated from API handlers:
- `analytics/` — Analytics data processing
- `changelog/` — Entry CRUD, publishing, scheduling
- `email/` — SMTP sending, newsletter management
- `github/` — Commit sync, tag creation
- `slack/` — Slack bot notifications
- `jobs/` — Background job execution (ScheduledJob queue)
- `projects/` — Project operations
- `sponsor/` — License/sponsor management
- `telemetry/` — Telemetry tracking
- `search/` — Full-text PostgreSQL search
- `core/markdown/` — Markdown parsing & custom extensions
### Auth (`lib/auth/`)
See Authentication System above.
### Utils (`lib/utils/`)
- `format-date.ts` — Timezone-aware date formatting
- `cookies.ts` — Cookie helpers
- `encryption.ts` — Encryption utilities
- `auditLog.ts` — Audit log helpers
- `api.ts` — API utilities
### Custom Domains (`lib/custom-domains/`)
- `service.ts` — Domain management
- `ssl/` — ACME/Let's Encrypt logic
- `dns.ts` — DNS verification utilities
---
## 🗄️ Database (Prisma + PostgreSQL)
Schema split across `prisma/schema/`:
- `base.prisma` — Datasource & generator
- `users.prisma` — User, OAuth, SAML, Passkey, 2FA, Invite, PasswordReset
- `projects.prisma` — Project, Changelog, ChangelogEntry, ChangelogTag, Widget
- `system.prisma` — SystemConfig, ApiKey, AuditLog, ScheduledJob, Analytics, CustomDomain
- `integrations.prisma` — EmailConfig, SlackIntegration, GitHubIntegration, Subscribers
- `enums.prisma` — All enum definitions
Key models:
- `User` — Core user with role, timezone
- `SystemConfig` — Global app config (timezone, email, AI, Slack OAuth, customDateTemplates as JSONB)
- `Project` / `Changelog` / `ChangelogEntry` — Main content models
- `ScheduledJob` — Background job queue (publish, email, SSL renewal, telemetry)
- `CustomDomain` / `DomainCertificate` — Domain management with SSL
---
## 🧩 Components
```
components/
├── changelog/editor/ # Entry editor (AI, versioning, scheduling)
│ └── VersionSelector.tsx # Version/date template picker
├── markdown-editor/ # Custom markdown editor with AI
│ ├── MarkdownEditor.tsx
│ ├── MarkdownToolbar.tsx
│ ├── MarkdownPreview.tsx
│ └── ai/ # AI assistant panel
├── admin/ # Admin UI (API keys, audit logs, requests)
├── analytics/ # Chart components
├── project/ # Project sidebar, navigation, settings
│ └── catch-up/ # Feature recap display
├── sso/ # SSO configuration UI
├── setup/ # First-run setup wizard
├── settings/ # User security settings
├── ui/ # Shadcn/Radix UI primitives
├── CommandPalette.tsx # Global command palette
└── Logo.tsx
```
---
## 🪝 Hooks (`hooks/`)
| Hook | Purpose |
|------|---------|
| `use-timezone.ts` | Resolves effective timezone (user → system → UTC) |
| `useAIAssistant.ts` | AI writing assistant |
| `useMarkdownState.ts` | Markdown editor state management |
| `useEditorHistory.ts` | Undo/redo for editor |
| `useSlashCommands.ts` | Slash command handling |
| `useCommandPalette.ts` | Command palette state |
| `useBookmarks.ts` | Bookmark management |
| `useChunkedData.ts` | Data chunking for large lists |
| `useTelemetry.ts` | Telemetry tracking |
| `useWhatsNew.ts` | What's new modal |
---
## ⚙️ Configuration
| File | Purpose |
|------|---------|
| `next.config.ts` | Next.js (React Compiler on, strictMode off, Turbopack) |
| `tailwind.config.ts` | Tailwind CSS |
| `components.json` | shadcn/ui component config |
| `tsconfig.json` | TypeScript (strict, `@/*` alias) |
| `prisma/schema/` | Database schema |
| `.env.example` | Required environment variables |
| `Dockerfile` + `docker-compose.yml` | Container deployment |
| `Caddyfile` / `nginx.conf` | Reverse proxy configs |
---
## 📚 Documentation
| File | Topic |
|------|-------|
| `README.md` | Features, quick start, deployment |
| `CHANGELOG.md` | Version history |
| `APIDOCGUIDE.md` | API documentation guide |
| `ideas.md` | Feature ideas/roadmap |
| `issues.md` | Known issues |
| `useful-information-for-development/` | Dev notes (Slack scopes, etc.) |
---
## 🔗 Key Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| next | 16.1.6 | Framework |
| react | 19.2.4 | UI library |
| prisma | 6.7.0 | ORM |
| jose | — | JWT tokens |
| @node-saml/node-saml | — | SAML/SSO |
| @simplewebauthn/* | — | Passkeys |
| @tiptap/react | — | Rich text editing |
| @tanstack/react-query | — | Server state |
| @scalar/nextjs-api-reference | — | API docs UI |
| recharts | — | Analytics charts |
| framer-motion | — | Animations |
| zod | — | Schema validation |
| react-hook-form | — | Form handling |
| nodemailer | — | Email sending |
| @slack/bolt | — | Slack integration |
---
## 📝 Quick Start
1. `cp .env.example .env` and fill in required vars
2. `npm install`
3. `npx prisma migrate dev` — run DB migrations
4. `npx prisma generate` — generate Prisma client
5. `npm run dev` — starts on port 3001
6. Visit `/setup` on first run
**Build widget**: `npm run build:widget`
**API docs**: `npm run generate-swagger` → visit `/api-docs`
---
## 🏗️ Architectural Patterns
1. **Auth**: JWT in `accessToken` cookie; `verifyAccessToken` from `lib/auth/tokens`
2. **Permissions**: Role-based, configured in `lib/api/route-permissions.ts`
3. **Services**: Business logic in `lib/services/`, not in route handlers
4. **Timezone**: User.timezone → SystemConfig.timezone → UTC; use `useTimezone()` hook client-side
5. **Admin layout**: `/^\/dashboard\/admin\/system/` pattern catches sub-pages
6. **SystemConfig**: Single row, full object sent on PATCH — new fields need defaults
7. **Background jobs**: `ScheduledJob` model polled by cron endpoints
8. **Custom domains**: DNS verification + Let's Encrypt via ACME protocol
================================================
FILE: README.md
================================================
<p align="center">
<img src="public/logo.png" alt="logo" /><br/>
<strong>Ship, Change, Rawr</strong>
</p>
[](https://github.com/supernova3339/changerawr)
[](https://github.com/supernova3339/changerawr)
[](LICENSE)
# What is Changerawr?
Changerawr lets you write down what you changed, then share those changes with people. You write entries about updates you made, and Changerawr gives you ways to display them - like widgets for your website, public pages people can visit, or APIs to use however you want. \
You can think of it as a **Changelog Management System** [CMS]
If you don't know what a changelog is, check out [betterauth](https://www.better-auth.com/changelogs) for an example!
## ✨ Why Changerawr?
**Developer-focused.** Headless API, beautiful documentation, SDKs, integrations, and a CLI.
**Fully customizable.** Do things your way. No vendor lock-in, no forced workflows.
**For everyone.** Whether you're a solo developer, small business, or enterprise team - Changerawr scales with you. ( yes, this means you can use it for commercial usage! just please do reach out if you do, I would love to know how your using Changerawr! )
## 🚀 Features
- **📝 Beautiful Content Editor** - Write changelogs that look professional
- **🤖 AI-Powered** - Let AI help you write better changelog entries
- **📡 Headless API** - Beautifully documented REST API for full control
- **🧩 SDKs** - Pre-built libraries for popular languages
- **🎨 Embeddable Widget** - Drop a changelog widget anywhere on your site
- **📧 Email Notifications** - Keep users informed of updates
- **🏷️ Tags & Versioning** - Organize entries exactly how you want
- **🔗 Multiple Integrations** - Connect with your existing tools
- **🔐 Modern Authentication** - Custom-built auth with passkey support
- **🖥️ Desktop-First Design** - Built for desktop use (mobile works, but it's quirky)
- **🔍 Full-Text Search** - Search everything, instantly
- -**🌐 Custom Domains** - Link a custom domain to your changelog
## 🚀 Quick Start
### Prerequisites
- Node.js 22+
- PostgreSQL database
### Installation
```bash
# Clone the repository
git clone https://github.com/supernova3339/changerawr.git
cd changerawr
# Install dependencies
npm install
# Set up environment
cp .env.example .env.local
# Edit .env.local with your settings
# Set up database
npx prisma generate
npx prisma migrate deploy
# Build the widget
npm run build:widget
# Start development server
npm run dev
```
Visit [http://localhost:3000](http://localhost:3000) and you're ready to go!
### Docker Setup
```bash
docker-compose up --build
```
## ⚙️ Configuration
### Environment Variables
```bash
# Database
DATABASE_URL="postgresql://postgres@localhost:5432/changerawr?schema=public"
# Authentication
JWT_ACCESS_SECRET="your-jwt-secret-key"
NEXT_PUBLIC_APP_URL="http://localhost:3000"
# GitHub Integration (optional)
GITHUB_ENCRYPTION_KEY="your-github-encryption-key"
# Analytics
ANALYTICS_SALT="your-secure-random-salt-here"
```
## 📦 Widget Integration
The easiest way to add changelogs to your site - perfect for non-technical users:
```html
<!-- Basic widget -->
<script
src="https://your-changerawr.com/api/widget/your-project-id"
data-theme="light"
async
></script>
<!-- Popup widget -->
<button id="updates-btn">What's New?</button>
<script
src="https://your-changerawr.com/api/widget/your-project-id"
data-popup="true"
data-trigger="updates-btn"
async
></script>
```
### Widget Options
| Option | Type | Default | Description |
|--------|---------|:---------------:|-------------|
| `data-theme` | string | "light" | Theme: "light" or "dark" |
| `data-position` | string | "bottom-right" | Popup position |
| `data-max-height` | string | "400px" | Maximum height |
| `data-popup` | boolean | false | Enable popup mode |
| `data-trigger` | string | null | Button ID or "immediate" |
| `data-max-entries` | number | 3 | Amount of entries to display, min 3 max 10
## 🛠️ Tech Stack
**Built with modern, reliable technologies:**
- **Next.js 16** - React framework with App Router
- **Prisma ORM** - Type-safe database access
- **PostgreSQL** - Robust, scalable database
- **Shadcn/UI** - Beautiful, accessible UI components
- **TypeScript** - Full type safety throughout
## 🏗️ Development
### Available Scripts
```bash
npm run dev # Development server
npm run build # Production build
npm run start # Start built development serer
npm run start:prod # Start production server
npm run start:prod:win # Start production server ( Windows )
npm run build:widget # Build embeddable widget
npm run generate-swagger # Generate API docs
npm run lint # Code linting ( next 16 will depc this - note )
npm run maintenance # Run the maintenance page
npm run start:with-maintenance # Runs maintenance page and the main server
npm run prisma:studio # Database viewer and manager
```
### Project Structure
```
changerawr/
├── app/ # Next.js App Router
│ ├── (auth)/ # Auth pages
│ ├── (email)/ # Newsletter related pages
│ ├── api/ # API endpoints
│ ├── api-docs/ # API Documentation
| ├── changelog/ # Changelog pages (public/custom-domain)
│ ├── cli/ # Internal pages used to interface with the Changerawr CLI
│ └── dashboard/ # Main app
├── components/ # React components
├── lib/ # Core utilities
├── prisma/ # Database schema
├── widgets/ # Widget source
├── scripts/ # Build scripts
└── emails/ # Email templates
```
## 🚢 Deployment
### Docker (Recommended)
```bash
# Build
docker build -t changerawr .
# Run
docker run -p 3000:3000 \
-e DATABASE_URL="your-database-url" \
-e JWT_ACCESS_SECRET="your-secret" \
-e NEXT_PUBLIC_APP_URL="your-app-url" \
-e GITHUB_ENCRYPTION_KEY="your-encryption-key-32-chars" \
-e ANALYTICS_SALT="your-analytics-salt" \
changerawr
```
### Manual Deployment
```bash
npm run build
npx prisma migrate deploy
npm run build:widget
npm run generate-swagger
npm start:with-maintenance
```
## 🎯 Features in Detail
### AI-Powered Writing
Let AI help you craft professional changelog entries that your users will actually want to read.
### Custom Authentication
Built from scratch with modern features like passkeys. No third-party restrictions, full control.
### Developer-First API
Clean, well-documented REST API with SDKs for popular languages. Build exactly what you need.
### Email Notifications
Keep your users in the loop with beautiful email updates when you ship new features.
### Full Customization
Tags, versioning - organize your changelogs exactly how your team works.
## 🤝 Contributing
We welcome contributions! Whether it's:
- 🐛 Bug fixes
- ✨ New features
- 📖 Documentation improvements
- 🎨 UI/UX enhancements
1. Fork the repo
2. Create a feature branch
3. Make your changes
4. Submit a pull request
## 📄 License
Non-Commercial Open Source License - see [LICENSE](LICENSE) for details.
Changerawr is open source and free to use, modify, and fork (including closed-source versions). You can use it commercially in your business, but you cannot profit from selling the software itself or charge users for access to it. All features remain free forever.
## 🙋♂️ Support
- 🐛 **Issues**: [GitHub Issues](https://github.com/supernova3339/changerawr/issues)
- 💬 **Discussions**: [GitHub Discussions](https://github.com/supernova3339/changerawr/discussions)
---
**Built by developers, for developers.**
================================================
FILE: app/(auth)/forgot-password/layout.tsx
================================================
import React from 'react';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Forgot Password - Changerawr',
description: 'Reset your password for Changerawr',
};
export default function ForgotPasswordLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-100 to-slate-200 dark:from-slate-900 dark:to-slate-800 flex flex-col items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="mb-6 text-center">
<div className="inline-block">
<h1 className="text-2xl font-bold">Changerawr</h1>
</div>
</div>
{children}
<p className="text-center text-sm text-muted-foreground mt-8">
© {new Date().getFullYear()} Changerawr. All rights reserved.
</p>
</div>
</div>
);
}
================================================
FILE: app/(auth)/forgot-password/page.tsx
================================================
'use client';
import React, { useState, useEffect, useRef } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import Link from 'next/link';
import {
Card,
CardContent,
CardFooter,
} from "@/components/ui/card";
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { toast } from '@/hooks/use-toast';
import {
ArrowLeft,
Loader2,
CheckCircle2,
AlarmClock,
Mail,
RefreshCw,
Copy,
HelpCircle,
MailCheck,
ExternalLink
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import confetti from 'canvas-confetti';
const forgotPasswordSchema = z.object({
email: z.string().email('Please enter a valid email address'),
});
type ForgotPasswordFormValues = z.infer<typeof forgotPasswordSchema>;
// Email providers and their URLs
const emailProviders = {
'gmail.com': 'https://mail.google.com',
'outlook.com': 'https://outlook.live.com',
'hotmail.com': 'https://outlook.live.com',
'yahoo.com': 'https://mail.yahoo.com',
'icloud.com': 'https://www.icloud.com/mail',
'protonmail.com': 'https://mail.proton.me',
'naver.com': 'https://mail.naver.com/'
};
// Smart confetti function
const fireConfetti = (type: 'success' | 'resend' = 'success') => {
const isMobile = window.innerWidth < 768;
const defaults = {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: 0,
disableForReducedMotion: true
};
// Check if reduced motion is preferred
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Only show minimal confetti for users who prefer reduced motion
confetti({
...defaults,
particleCount: 20,
gravity: 1,
origin: { y: 0.6, x: 0.5 }
});
return;
}
if (type === 'success') {
// Initial burst from the center
confetti({
...defaults,
particleCount: isMobile ? 50 : 100,
origin: { y: 0.6, x: 0.5 }
});
// Create cannon effect
setTimeout(() => {
confetti({
...defaults,
particleCount: isMobile ? 25 : 50,
angle: 60,
spread: 50,
origin: { x: 0, y: 0.6 }
});
confetti({
...defaults,
particleCount: isMobile ? 25 : 50,
angle: 120,
spread: 50,
origin: { x: 1, y: 0.6 }
});
}, 250);
// Final smaller bursts
setTimeout(() => {
confetti({
...defaults,
particleCount: isMobile ? 15 : 30,
angle: 90,
gravity: 1.2,
origin: { x: 0.5, y: 0.7 }
});
}, 400);
} else {
// Simpler confetti for resend
confetti({
...defaults,
particleCount: isMobile ? 30 : 50,
origin: { y: 0.6, x: 0.5 },
gravity: 1.2
});
}
};
export default function ForgotPasswordPage() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isEmailSent, setIsEmailSent] = useState(false);
const [sentEmail, setSentEmail] = useState('');
const [error, setError] = useState('');
const [countdown, setCountdown] = useState(0);
const [emailProvider, setEmailProvider] = useState<string | null>(null);
const [lastTyped, setLastTyped] = useState(0);
const [isCopied, setIsCopied] = useState(false);
const wrapperRef = useRef<HTMLDivElement>(null);
const {
register,
handleSubmit,
watch,
formState: { errors, isValid },
reset,
} = useForm<ForgotPasswordFormValues>({
resolver: zodResolver(forgotPasswordSchema),
mode: 'onChange',
});
const email = watch('email', '');
// Handle countdown for resend functionality
useEffect(() => {
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
return () => clearTimeout(timer);
}
}, [countdown]);
// Detect email provider for quick link
useEffect(() => {
if (sentEmail) {
const domain = sentEmail.split('@')[1]?.toLowerCase();
const provider = domain && Object.keys(emailProviders).find(key =>
domain === key || domain?.endsWith(`.${key}`)
);
setEmailProvider(provider || null);
}
}, [sentEmail]);
// Handle email typing suggestions
useEffect(() => {
if (email && !isSubmitting && !isEmailSent) {
setLastTyped(Date.now());
}
}, [email, isSubmitting, isEmailSent]);
// Scroll to top when success view is shown
useEffect(() => {
if (isEmailSent && wrapperRef.current) {
wrapperRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [isEmailSent]);
const onSubmit = async (data: ForgotPasswordFormValues) => {
setError('');
setIsSubmitting(true);
try {
const response = await fetch('/api/auth/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to request password reset');
}
// Set 60-second countdown for resend button
setCountdown(60);
setSentEmail(data.email);
setIsEmailSent(true);
// Smart confetti - delayed to match animation
setTimeout(() => fireConfetti('success'), 200);
toast({
title: 'Reset Email Sent',
description: 'Check your inbox for the password reset link',
variant: 'success',
});
} catch (error) {
setError(error instanceof Error ? error.message : 'Something went wrong');
toast({
title: 'Error',
description: error instanceof Error ? error.message : 'Something went wrong',
variant: 'destructive',
});
} finally {
setIsSubmitting(false);
}
};
const handleResend = () => {
// Simpler confetti for resend
setTimeout(() => fireConfetti('resend'), 100);
onSubmit({ email: sentEmail });
};
const handleTryDifferentEmail = () => {
setIsEmailSent(false);
setError('');
reset();
setTimeout(() => {
document.getElementById('email')?.focus();
}, 300);
};
const copyToClipboard = () => {
if (sentEmail) {
navigator.clipboard.writeText(sentEmail);
setIsCopied(true);
toast({
title: 'Copied',
description: 'Email address copied to clipboard',
});
// Reset the copied state after 2 seconds
setTimeout(() => setIsCopied(false), 2000);
}
};
const getEmailDomainSuggestion = () => {
if (!email || email.includes('@') || Date.now() - lastTyped < 1000) return null;
const commonDomains = ['gmail.com', 'outlook.com', 'yahoo.com', 'icloud.com'];
return commonDomains[0]; // Suggest the first common domain
};
const suggestion = getEmailDomainSuggestion();
return (
<div ref={wrapperRef}>
<AnimatePresence mode="wait">
{isEmailSent ? (
<motion.div
key="success"
initial={{ opacity: 0, scale: 0.8, y: 20 }}
animate={{
opacity: 1,
scale: 1,
y: 0,
transition: {
type: "spring",
stiffness: 400,
damping: 30
}
}}
exit={{ opacity: 0, scale: 0.8, y: -20 }}
className="w-full max-w-sm mx-auto text-center"
>
<motion.div
className="mb-8"
initial={{ scale: 0 }}
animate={{
scale: 1,
transition: {
type: "spring",
stiffness: 300,
delay: 0.2
}
}}
>
<div className="w-24 h-24 bg-gradient-to-br from-green-100 to-green-200 dark:from-green-900/30 dark:to-green-800/30 rounded-full flex items-center justify-center mx-auto shadow-md">
<motion.div
animate={{
rotate: [0, 10, -10, 10, 0],
scale: [1, 1.1, 1]
}}
transition={{
duration: 0.5,
delay: 0.3
}}
>
<MailCheck className="h-12 w-12 text-green-600 dark:text-green-400" strokeWidth={1.5} />
</motion.div>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{
opacity: 1,
y: 0,
transition: { delay: 0.3 }
}}
>
<h2 className="text-2xl font-bold mb-3">Check your inbox</h2>
<p className="text-muted-foreground mb-1">
We've sent a password reset link to:
</p>
<div className="font-medium text-lg mb-1 break-all flex items-center justify-center gap-2">
<span>{sentEmail}</span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={copyToClipboard}
>
{isCopied ? (
<CheckCircle2 className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Copy email address</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
{emailProvider && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{
opacity: 1,
height: 'auto',
transition: { delay: 0.4 }
}}
className="mb-8"
>
<Button
variant="outline"
className="mt-2"
onClick={() => window.open(emailProviders[emailProvider as keyof typeof emailProviders], '_blank')}
>
<ExternalLink className="mr-2 h-4 w-4" />
Open {emailProvider.charAt(0).toUpperCase() + emailProvider.slice(1)}
</Button>
</motion.div>
)}
</motion.div>
<motion.div
className="space-y-3"
initial={{ opacity: 0 }}
animate={{
opacity: 1,
transition: { delay: 0.5 }
}}
>
<Button
variant="outline"
onClick={handleResend}
disabled={countdown > 0}
className="w-full h-11 relative overflow-hidden group"
>
{countdown > 0 ? (
<div className="flex items-center">
<AlarmClock className="mr-2 h-4 w-4 animate-pulse" />
<span>Resend in {countdown}s</span>
<div
className="absolute bottom-0 left-0 h-1 bg-primary transition-all duration-1000 ease-linear"
style={{ width: `${(countdown / 60) * 100}%` }}
/>
</div>
) : (
<div className="flex items-center">
<RefreshCw className="mr-2 h-4 w-4 group-hover:rotate-180 transition-transform duration-500" />
<span>Resend Email</span>
</div>
)}
</Button>
<Button
variant="secondary"
onClick={handleTryDifferentEmail}
className="w-full h-11"
>
<Mail className="mr-2 h-4 w-4" />
Try a different email
</Button>
<div className="pt-6">
<Button
variant="ghost"
asChild
className="text-sm text-muted-foreground hover:text-foreground"
>
<Link href="/login">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Login
</Link>
</Button>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 0.8 }}
transition={{ delay: 0.8 }}
className="mt-8 text-xs text-muted-foreground"
>
<p>Didn't receive the email? Check your spam folder or try another email address.</p>
</motion.div>
</motion.div>
) : (
<motion.div
key="form"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
className="w-full"
>
<Card className="w-full shadow-lg border-t-4 border-t-primary overflow-hidden">
<CardContent className="pt-6">
<div className="space-y-4">
<motion.div
className="text-center space-y-2"
initial={{ y: -10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.3 }}
>
<h1 className="text-2xl font-bold">Forgot Password</h1>
<p className="text-sm text-muted-foreground">
Enter your email address and we'll send you a link to reset your password
</p>
</motion.div>
<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
>
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
</motion.div>
)}
</AnimatePresence>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<motion.div
className="space-y-2"
initial={{ x: -10, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<div className="flex justify-between items-center">
<Label htmlFor="email">Email Address</Label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" className="h-6 w-6 p-0">
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p className="max-w-xs">Enter the email address you used to register</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="relative group">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-primary transition-colors duration-200" />
<Input
id="email"
type="email"
placeholder="your@email.com"
{...register('email')}
autoComplete="email"
autoFocus
className={`h-11 pl-10 pr-20 ${errors.email ? 'border-destructive focus-visible:ring-destructive/20' : 'focus-visible:ring-primary/20'} transition-all duration-200`}
/>
{suggestion && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<Button
type="button"
variant="ghost"
size="sm"
className="h-6 text-xs px-2 text-muted-foreground hover:text-foreground"
onClick={() => {
const value = `${email}@${suggestion}`;
reset({ email: value });
}}
>
@{suggestion}
</Button>
</div>
)}
</div>
<AnimatePresence>
{errors.email && (
<motion.p
className="text-sm text-destructive flex items-center gap-1 mt-1"
initial={{ opacity: 0, height: 0, y: -10 }}
animate={{ opacity: 1, height: 'auto', y: 0 }}
exit={{ opacity: 0, height: 0, y: -10 }}
>
<span className="inline-block">⚠️</span>
{errors.email.message}
</motion.p>
)}
</AnimatePresence>
</motion.div>
<motion.div
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<Button
type="submit"
className={`
w-full h-11 relative overflow-hidden
${isValid ? 'bg-primary hover:bg-primary/90' : 'bg-primary/70'}
transition-all duration-300
`}
disabled={isSubmitting || !isValid}
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Sending...
</>
) : (
<>
<Mail className="mr-2 h-4 w-4" />
Send Reset Link
</>
)}
{/* {isValid && !isSubmitting && (*/}
{/* <span className="absolute right-0 top-0 h-full w-12 -skew-x-12 overflow-hidden flex justify-center items-center">*/}
{/* <motion.div*/}
{/* className="bg-white/20 h-8 w-8 rounded-full"*/}
{/* initial={{ x: -100 }}*/}
{/* animate={{ x: 150 }}*/}
{/* transition={{*/}
{/* repeat: Infinity,*/}
{/* duration: 2,*/}
{/* ease: "easeInOut",*/}
{/* repeatDelay: 1*/}
{/* }}*/}
{/* />*/}
{/*</span>*/}
{/* )}*/}
</Button>
</motion.div>
</form>
</div>
</CardContent>
<CardFooter className="flex justify-center pb-6">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
>
<Button
variant="ghost"
asChild
size="sm"
className="text-sm text-muted-foreground hover:text-foreground"
>
<Link href="/login">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Login
</Link>
</Button>
</motion.div>
</CardFooter>
</Card>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
================================================
FILE: app/(auth)/layout.tsx
================================================
'use client'
import { useAuth } from '@/context/auth'
import { useRouter, usePathname } from 'next/navigation'
import React, { useEffect } from 'react'
export default function AuthLayout({
children,
}: {
children: React.ReactNode
}) {
const { user, isLoading } = useAuth()
const router = useRouter()
const pathname = usePathname()
useEffect(() => {
if (!isLoading && user) {
router.replace('/dashboard')
}
}, [user, isLoading, router, pathname])
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50/30 dark:bg-background">
<div className="animate-pulse">Loading...</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50/30">
{children}
</div>
)
}
================================================
FILE: app/(auth)/login/layout.tsx
================================================
import React from 'react';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Login - Changerawr',
description: 'Login to your Changerawr account',
};
export default function RegisterLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-100 to-slate-200 dark:from-slate-900 dark:to-slate-800 flex flex-col items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="mb-6 text-center">
<div className="inline-block">
<h1 className="text-2xl font-bold">Changerawr</h1>
</div>
</div>
{children}
<p className="text-center text-sm text-muted-foreground mt-8">
© {new Date().getFullYear()} Changerawr. All rights reserved.
</p>
</div>
</div>
);
}
================================================
FILE: app/(auth)/login/page.tsx
================================================
'use client'
import React, {useEffect, useState} from 'react'
import {useForm} from 'react-hook-form'
import {zodResolver} from '@hookform/resolvers/zod'
import {z} from 'zod'
import {useAuth} from '@/context/auth'
import Link from "next/link"
import {useQuery} from '@tanstack/react-query'
import {motion, AnimatePresence} from 'framer-motion'
import confetti from 'canvas-confetti'
import {
startAuthentication,
browserSupportsWebAuthn,
} from '@simplewebauthn/browser'
// UI Components
import {Input} from '@/components/ui/input'
import {Button} from '@/components/ui/button'
import {Label} from '@/components/ui/label'
import {Alert, AlertTitle, AlertDescription} from '@/components/ui/alert'
import {Avatar, AvatarImage, AvatarFallback} from "@/components/ui/avatar"
import {Card, CardContent, CardFooter} from '@/components/ui/card'
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip"
// Icons
import {
ArrowLeft,
User,
Fingerprint,
Eye,
EyeOff,
Loader2,
Lock,
Mail,
CheckCircle2,
AlertTriangle,
Shield,
RefreshCw,
ArrowRight,
Key
} from 'lucide-react'
import {ProviderLogo} from "@/components/sso/ProviderLogo";
const emailSchema = z.object({
email: z.string().email('Please enter a valid email')
})
const passwordSchema = z.object({
password: z.string().min(1, 'Please enter your password')
})
type EmailForm = z.infer<typeof emailSchema>
type PasswordForm = z.infer<typeof passwordSchema>
interface UserPreview {
name: string | null
email: string
avatarUrl: string
}
interface OAuthProvider {
id: string
name: string
enabled: boolean
isDefault: boolean
}
interface PasswordBreachData {
breachCount: number
resetUrl: string
}
// Smart confetti function from registration page
const fireConfetti = () => {
const isMobile = window.innerWidth < 768;
const defaults = {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: 0,
disableForReducedMotion: true
};
// Check if reduced motion is preferred
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Only show minimal confetti for users who prefer reduced motion
confetti({
...defaults,
particleCount: 20,
gravity: 1,
origin: {y: 0.6, x: 0.5}
});
return;
}
// Initial burst from the center
confetti({
...defaults,
particleCount: isMobile ? 50 : 100,
origin: {y: 0.6, x: 0.5}
});
// Create cannon effect
setTimeout(() => {
confetti({
...defaults,
particleCount: isMobile ? 25 : 50,
angle: 60,
spread: 50,
origin: {x: 0, y: 0.6}
});
confetti({
...defaults,
particleCount: isMobile ? 25 : 50,
angle: 120,
spread: 50,
origin: {x: 1, y: 0.6}
});
}, 250);
// Final smaller bursts
setTimeout(() => {
confetti({
...defaults,
particleCount: isMobile ? 15 : 30,
angle: 90,
gravity: 1.2,
origin: {x: 0.5, y: 0.7}
});
}, 400);
};
export default function LoginPage() {
const {user, isLoading: authLoading} = useAuth()
const [error, setError] = useState('')
const [step, setStep] = useState<'email' | 'password' | 'breach-warning'>('email')
const [userPreview, setUserPreview] = useState<UserPreview | null>(null)
const [passwordBreach, setPasswordBreach] = useState<PasswordBreachData | null>(null)
const [supportsWebAuthn, setSupportsWebAuthn] = useState(false)
const [isAuthenticating, setIsAuthenticating] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
const [redirectTo, setRedirectTo] = useState('/dashboard')
// Fetch OAuth providers
const {data: oauthProviders, isLoading: isLoadingProviders} = useQuery({
queryKey: ['oauthProviders'],
queryFn: async () => {
try {
const response = await fetch('/api/auth/oauth/providers')
if (!response.ok) return []
const data = await response.json()
return data.providers
} catch (error) {
console.error('Failed to fetch OAuth providers:', error)
return []
}
},
staleTime: 60000 // 1 minute
})
// Fetch SAML providers
const {data: samlProviders, isLoading: isLoadingSAMLProviders} = useQuery({
queryKey: ['samlProviders'],
queryFn: async () => {
try {
const response = await fetch('/api/auth/saml/providers')
if (!response.ok) return []
const data = await response.json()
return data.providers
} catch (error) {
console.error('Failed to fetch SAML providers:', error)
return []
}
},
staleTime: 60000
})
const emailForm = useForm<EmailForm>({
resolver: zodResolver(emailSchema),
defaultValues: {
email: ''
}
})
const passwordForm = useForm<PasswordForm>({
resolver: zodResolver(passwordSchema),
defaultValues: {
password: ''
}
})
useEffect(() => {
setSupportsWebAuthn(browserSupportsWebAuthn())
}, [])
useEffect(() => {
const searchParams = new URLSearchParams(window.location.search)
const redirectParam = searchParams.get('redirectTo') || searchParams.get('from')
if (redirectParam) {
setRedirectTo(redirectParam)
}
const handleOAuthRedirect = async () => {
const oauthComplete = searchParams.get('oauth_complete')
if (oauthComplete === 'true') {
try {
// Show success state and confetti, then redirect
setIsSuccess(true)
setTimeout(() => {
fireConfetti()
}, 300)
// Redirect with window.location after a short delay
setTimeout(() => {
window.location.href = redirectTo || '/dashboard'
}, 1500)
} catch (err) {
console.error('OAuth redirect error:', err)
setError('Failed to complete login')
}
}
}
if (user && !authLoading) {
// Show success state and confetti, then redirect
setIsSuccess(true)
setTimeout(() => {
fireConfetti()
}, 300)
setTimeout(() => {
window.location.href = redirectTo
}, 1500)
} else {
// Check for OAuth redirects only if not already logged in
handleOAuthRedirect()
}
// Check for error in URL (typically from OAuth callback)
const errorParam = searchParams.get('error')
if (errorParam) {
setError(decodeURIComponent(errorParam))
}
}, [user, authLoading, redirectTo])
const formatBreachCount = (count: number): string => {
if (count >= 1000000) {
return `${(count / 1000000).toFixed(1)}M`;
} else if (count >= 1000) {
return `${(count / 1000).toFixed(1)}K`;
}
return count.toLocaleString();
};
const onEmailSubmit = async (data: EmailForm) => {
try {
setError('')
const response = await fetch('/api/auth/preview', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: data.email.toLowerCase()})
})
if (!response.ok) {
throw new Error('Authentication failed')
}
const userData = await response.json()
setUserPreview(userData)
setStep('password')
} catch (err: unknown) {
setError('Unable to find your account')
console.log(err)
}
}
const onPasswordSubmit = async (data: PasswordForm) => {
try {
setError('')
if (!userPreview) return
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email: userPreview.email,
password: data.password,
bypassBreachWarning: false // Initial attempt without bypass
}),
credentials: 'include'
})
const responseData = await response.json()
// Handle password breach warning
if (response.status === 422 && responseData.error === 'password_breached') {
setPasswordBreach({
breachCount: responseData.breachCount,
resetUrl: responseData.resetUrl
});
setStep('breach-warning')
return;
}
// Handle 2FA requirement
if (response.status === 403 && responseData.requiresSecondFactor) {
sessionStorage.setItem('2faSessionToken', responseData.sessionToken)
sessionStorage.setItem('2faType', responseData.secondFactorType)
window.location.href = '/two-factor'
return
}
if (!response.ok) {
throw new Error(responseData.error || 'Authentication failed')
}
// Success state with confetti
setIsSuccess(true)
setTimeout(() => {
fireConfetti()
}, 300)
// Redirect with window.location
setTimeout(() => {
window.location.href = redirectTo
}, 1500)
} catch (error) {
setError(error instanceof Error ? error.message : 'Authentication failed')
passwordForm.reset()
}
}
// Handle continuing despite breach warning
const handleContinueWithBreachedPassword = async () => {
try {
setError('')
if (!userPreview) return
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email: userPreview.email,
password: passwordForm.getValues('password'),
bypassBreachWarning: true // Bypass the breach warning
}),
credentials: 'include'
})
const responseData = await response.json()
// Handle 2FA requirement
if (response.status === 403 && responseData.requiresSecondFactor) {
sessionStorage.setItem('2faSessionToken', responseData.sessionToken)
sessionStorage.setItem('2faType', responseData.secondFactorType)
window.location.href = '/two-factor'
return
}
if (!response.ok) {
throw new Error(responseData.error || 'Authentication failed')
}
// Success state with confetti
setIsSuccess(true)
setTimeout(() => {
fireConfetti()
}, 300)
// Redirect with window.location
setTimeout(() => {
window.location.href = redirectTo
}, 1500)
} catch (error) {
setError(error instanceof Error ? error.message : 'Authentication failed')
setStep('password') // Go back to password form
setPasswordBreach(null)
}
}
// Handle password reset
const handlePasswordReset = () => {
if (passwordBreach?.resetUrl && userPreview?.email) {
window.location.href = `${passwordBreach.resetUrl}?email=${encodeURIComponent(userPreview.email)}`;
}
}
const handleBack = () => {
if (step === 'breach-warning') {
setStep('password')
setPasswordBreach(null)
} else {
setStep('email')
setError('')
passwordForm.reset()
emailForm.reset()
setUserPreview(null)
}
}
const handleOAuthLogin = (provider: OAuthProvider) => {
// Create a URL-friendly version of the provider name
const providerNameForUrl = provider.name
.toLowerCase()
.replace(/\s+/g, '') // Remove all whitespace
.replace(/[^a-z0-9]/g, '') // Remove any non-alphanumeric characters
// Include redirectTo parameter in the OAuth URL
window.location.href = `/api/auth/oauth/authorize/${providerNameForUrl}?redirect=${encodeURIComponent(redirectTo)}`
}
const handleSAMLLogin = (provider: {id: string; name: string; isDefault: boolean}) => {
const providerNameForUrl = provider.name
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '')
window.location.href = `/api/auth/saml/authorize/${providerNameForUrl}?redirect=${encodeURIComponent(redirectTo)}`
}
const handlePasskeyLogin = async () => {
try {
setError('')
setIsAuthenticating(true)
// Get authentication options
const optionsResponse = await fetch('/api/auth/passkeys/authenticate/options', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email: userPreview?.email || emailForm.getValues('email') || undefined
}),
})
if (!optionsResponse.ok) {
throw new Error('Failed to get authentication options')
}
const {options, challenge} = await optionsResponse.json()
// Start WebAuthn authentication
const authenticationResponse = await startAuthentication(options)
// Verify with server
const verifyResponse = await fetch('/api/auth/passkeys/authenticate/verify', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
response: authenticationResponse,
challenge,
}),
})
if (!verifyResponse.ok) {
const errorData = await verifyResponse.json()
throw new Error(errorData.error || 'Authentication failed')
}
const verifyData = await verifyResponse.json()
// Check if 2FA is required
if (verifyData.requiresSecondFactor) {
sessionStorage.setItem('2faSessionToken', verifyData.sessionToken)
sessionStorage.setItem('2faType', verifyData.secondFactorType)
window.location.href = '/two-factor'
return
}
// Success state with confetti
setIsSuccess(true)
setTimeout(() => {
fireConfetti()
}, 300)
// Redirect with window.location
setTimeout(() => {
window.location.href = redirectTo
}, 1500)
} catch (err) {
console.error('Passkey login error:', err)
setError(err instanceof Error ? err.message : 'Failed to authenticate with passkey')
} finally {
setIsAuthenticating(false)
}
}
const togglePasswordVisibility = () => {
setShowPassword(!showPassword)
}
if (authLoading) {
return (
<div className="flex flex-col items-center justify-center h-full">
<div className="w-14 h-14 bg-muted/30 rounded-full flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-primary"/>
</div>
<p className="text-muted-foreground mt-4">Loading...</p>
</div>
)
}
return (
<div>
<AnimatePresence mode="wait">
{isSuccess ? (
<motion.div
key="success"
initial={{opacity: 0}}
animate={{opacity: 1}}
className="w-full max-w-sm mx-auto text-center"
>
<div className="mb-8">
<div
className="w-24 h-24 bg-gradient-to-br from-green-100 to-green-200 dark:from-green-900/30 dark:to-green-800/30 rounded-full flex items-center justify-center mx-auto shadow-md">
<CheckCircle2 className="h-12 w-12 text-green-600 dark:text-green-400"
strokeWidth={1.5}/>
</div>
</div>
<div>
<h2 className="text-2xl font-bold mb-2">Login Successful</h2>
<p className="text-muted-foreground mb-6">
You've signed in successfully. Redirecting you now...
</p>
</div>
</motion.div>
) : (
<div className="w-full max-w-sm mx-auto">
<Card className="w-full shadow-lg border-t-4 border-t-primary">
<CardContent className="pt-6">
<AnimatePresence mode="wait">
{step === 'email' ? (
<motion.div
key="email"
initial={{opacity: 0}}
animate={{opacity: 1}}
exit={{opacity: 0}}
className="space-y-6"
>
<div className="text-center space-y-2">
<h1 className="text-2xl font-bold">Sign in to Changerawr</h1>
<p className="text-sm text-muted-foreground">
Enter your email to get started
</p>
</div>
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<form onSubmit={emailForm.handleSubmit(onEmailSubmit)}
className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email address</Label>
<div className="relative">
<Input
id="email"
{...emailForm.register('email')}
type="email"
placeholder="you@example.com"
className="h-11 pl-10"
autoComplete="email"
autoFocus
startIcon={<Mail/>}
/>
</div>
{emailForm.formState.errors.email && (
<p className="text-sm text-destructive mt-1">
{emailForm.formState.errors.email.message}
</p>
)}
</div>
<Button
type="submit"
className="w-full h-11"
disabled={emailForm.formState.isSubmitting}
>
{emailForm.formState.isSubmitting ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin"/>
Checking...
</span>
) : (
'Continue'
)}
</Button>
</form>
{/* Auth Options */}
{(supportsWebAuthn || (!isLoadingProviders && oauthProviders && oauthProviders.length > 0) || (!isLoadingSAMLProviders && samlProviders && samlProviders.length > 0)) && (
<div>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t"/>
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="flex flex-col gap-3 mt-6">
{/* Passkey Button */}
{supportsWebAuthn && (
<Button
type="button"
variant="outline"
className="w-full h-11"
onClick={handlePasskeyLogin}
disabled={isAuthenticating}
>
{isAuthenticating ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin"/>
Authenticating...
</span>
) : (
<>
<Fingerprint className="mr-2 h-4 w-4"/>
Sign in with Passkey
</>
)}
</Button>
)}
{/* OAuth Provider Buttons */}
{!isLoadingProviders && oauthProviders && oauthProviders.map((provider: OAuthProvider) => (
<Button
key={provider.id}
variant="outline"
type="button"
className="w-full h-11 relative pl-10"
onClick={() => handleOAuthLogin(provider)}
>
<span
className="absolute left-3 top-1/2 transform -translate-y-1/2">
<ProviderLogo providerName={provider.name}
size="sm"/>
</span>
<span>Continue with {provider.name}</span>
</Button>
))}
{/* SAML Provider Buttons */}
{!isLoadingSAMLProviders && samlProviders && samlProviders.map((provider: {id: string; name: string; isDefault: boolean}) => (
<Button
key={provider.id}
variant="outline"
type="button"
className="w-full h-11 relative pl-10"
onClick={() => handleSAMLLogin(provider)}
>
<span
className="absolute left-3 top-1/2 transform -translate-y-1/2">
<ProviderLogo providerName={provider.name}
size="sm"/>
</span>
<span>Continue with {provider.name}</span>
</Button>
))}
</div>
</div>
)}
</motion.div>
) : step === 'password' ? (
<motion.div
key="password"
initial={{opacity: 0}}
animate={{opacity: 1}}
exit={{opacity: 0}}
className="space-y-6"
>
<Button
variant="ghost"
className="p-0 h-auto text-muted-foreground hover:text-foreground mb-2"
onClick={handleBack}
>
<ArrowLeft size={16} className="mr-2"/>
Back
</Button>
<div className="flex flex-col items-center space-y-4">
<Avatar
className="h-20 w-20 bg-gradient-to-br from-primary/20 to-primary/5 rounded-lg shadow-sm">
<AvatarImage
src={userPreview?.avatarUrl}
alt={userPreview?.name || "User avatar"}
/>
<AvatarFallback className="rounded-lg">
<User className="h-10 w-10 text-primary"/>
</AvatarFallback>
</Avatar>
<div className="space-y-1 text-center">
<h2 className="text-xl font-semibold">
Welcome back{userPreview?.name ? `, ${userPreview.name}` : ''}
</h2>
<p className="text-sm text-muted-foreground">{userPreview?.email}</p>
</div>
</div>
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<form onSubmit={passwordForm.handleSubmit(onPasswordSubmit)}
className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="password">Password</Label>
<Link
href="/forgot-password"
className="text-xs font-medium text-primary hover:underline"
>
Forgot password?
</Link>
</div>
<div className="relative">
<Lock
className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground"/>
<Input
id="password"
{...passwordForm.register('password')}
type={showPassword ? 'text' : 'password'}
placeholder="••••••••"
className="h-11 pl-10 pr-10"
autoComplete="current-password"
autoFocus
startIcon={<Key/>}
/>
<Button
type="button" variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={togglePasswordVisibility}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground"/>
) : (
<Eye className="h-4 w-4 text-muted-foreground"/>
)}
</Button>
</div>
{passwordForm.formState.errors.password && (
<p className="text-sm text-destructive mt-1">
{passwordForm.formState.errors.password.message}
</p>
)}
</div>
<Button
type="submit"
className="w-full h-11"
disabled={passwordForm.formState.isSubmitting}
>
{passwordForm.formState.isSubmitting ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin"/>
Signing in...
</span>
) : (
'Sign in'
)}
</Button>
</form>
{/* Show passkey option in password step too */}
{supportsWebAuthn && (
<div>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t"/>
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or
</span>
</div>
</div>
<Button
type="button"
variant="outline"
className="w-full h-11"
onClick={handlePasskeyLogin}
disabled={isAuthenticating}
>
{isAuthenticating ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin"/>
Authenticating...
</span>
) : (
<>
<Fingerprint className="mr-2 h-4 w-4"/>
Use Passkey Instead
</>
)}
</Button>
</div>
)}
</motion.div>
) : step === 'breach-warning' ? (
<motion.div
key="breach-warning"
initial={{opacity: 0, scale: 0.95}}
animate={{opacity: 1, scale: 1}}
exit={{opacity: 0, scale: 0.95}}
className="space-y-6"
>
<Button
variant="ghost"
className="p-0 h-auto text-muted-foreground hover:text-foreground mb-2"
onClick={handleBack}
>
<ArrowLeft size={16} className="mr-2"/>
Back
</Button>
<div className="text-center space-y-4">
<div
className="w-16 h-16 bg-gradient-to-br from-amber-100 to-red-100 dark:from-amber-900/30 dark:to-red-900/30 rounded-full flex items-center justify-center mx-auto">
<AlertTriangle
className="h-8 w-8 text-amber-600 dark:text-amber-400"/>
</div>
<div>
<h2 className="text-xl font-bold text-amber-900 dark:text-amber-100">
Password Security Alert
</h2>
<p className="text-sm text-muted-foreground mt-2">
Your password was found in a data breach
</p>
</div>
</div>
<Alert
variant="warning"
icon={<Shield className="h-4 w-4"/>}
className="border-amber-200 dark:border-amber-800"
>
<AlertTitle>Security Notice</AlertTitle>
<AlertDescription>
This password has appeared in{' '}
<span className="font-semibold text-red-600 dark:text-red-400">
{passwordBreach ? formatBreachCount(passwordBreach.breachCount) : '0'} known data breach{passwordBreach && passwordBreach.breachCount === 1 ? '' : 'es'}
</span>
. Using it puts your account at risk.
</AlertDescription>
</Alert>
<div className="space-y-3">
<h4 className="font-medium text-sm">What does this mean?</h4>
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
<li>Your password has been compromised in past security incidents
</li>
<li>Attackers may have access to this password</li>
<li>Your account security could be at risk</li>
</ul>
</div>
<div className="grid gap-3">
<Button
onClick={handlePasswordReset}
className="w-full h-11 bg-green-600 hover:bg-green-700 text-white"
disabled={passwordForm.formState.isSubmitting}
>
<RefreshCw className="mr-2 h-4 w-4"/>
Reset Password (Recommended)
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-muted-foreground/20"/>
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or
</span>
</div>
</div>
<Button
onClick={handleContinueWithBreachedPassword}
variant="outline"
className="w-full h-11 border-amber-200 text-amber-700 hover:bg-amber-50 dark:border-amber-800 dark:text-amber-300 dark:hover:bg-amber-900/20"
disabled={passwordForm.formState.isSubmitting}
>
<ArrowRight className="mr-2 h-4 w-4"/>
Continue Anyway (Not Recommended)
</Button>
</div>
<div className="text-xs text-muted-foreground text-center space-y-1">
<p>
Password checking powered by{' '}
<a
href="https://haveibeenpwned.com/Passwords"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
HaveIBeenPwned
</a>
</p>
<p>Your password is never transmitted - only a secure hash is
checked.</p>
</div>
</motion.div>
) : null}
</AnimatePresence>
</CardContent>
<CardFooter className="pb-6 flex justify-center">
{step === 'email' && (
<div className="text-center pt-2">
<p className="text-sm text-muted-foreground">
Don't have an account?{' '}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="link" className="p-0 h-auto text-primary"
onClick={() => setError("Contact your administrator for an invitation to join.")}>
Request access
</Button>
</TooltipTrigger>
<TooltipContent>
<p className="max-w-xs">You need an invitation to create an
account</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</p>
</div>
)}
</CardFooter>
</Card>
</div>
)}
</AnimatePresence>
</div>
)
}
================================================
FILE: app/(auth)/oauth-callback/layout.tsx
================================================
import { cookies, headers } from 'next/headers'
import React from "react";
export default async function OAuthCallbackLayout({
children,
}: {
children: React.ReactNode
}) {
const headersList = await headers()
const redirectUrl = headersList.get('X-OAuth-Redirect') || '/dashboard'
// Get user data from auth context
const cookieStore = await cookies()
const accessToken = cookieStore.get('accessToken')?.value
// This will be serialized and passed to the client component
const oauthData = {
redirectTo: redirectUrl,
hasToken: !!accessToken
}
return (
<div>
{/* Hidden div with the OAuth data for the client component */}
<script
id="oauth-data"
type="application/json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(oauthData)
}}
/>
{children}
</div>
)
}
================================================
FILE: app/(auth)/oauth-callback/page.tsx
================================================
'use client'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { Loader2 } from 'lucide-react'
export default function OAuthCallback() {
const router = useRouter()
const [error, setError] = useState<string | null>(null)
useEffect(() => {
// Extract the redirect URL from the page content
const handleOAuthRedirect = () => {
try {
// Get the JSON content from the pre-rendered response
const jsonContent = document.getElementById('oauth-data')?.textContent
if (jsonContent) {
const data = JSON.parse(jsonContent)
if (data.redirectTo) {
// Navigate to the redirect URL
router.push(data.redirectTo)
return
}
}
// Fallback to dashboard if no redirect found
router.push('/dashboard')
} catch (err) {
console.error('OAuth redirect error:', err)
setError('Failed to complete login')
// Redirect to login with error after a brief delay
setTimeout(() => {
router.push(`/login?error=${encodeURIComponent('Failed to complete OAuth login')}`)
}, 2000)
}
}
handleOAuthRedirect()
}, [router])
return (
<div className="min-h-screen flex flex-col bg-transparent items-center justify-center">
{error ? (
<div className="text-center">
<p className="text-destructive">{error}</p>
<p className="text-sm text-muted-foreground mt-2">Redirecting to login...</p>
</div>
) : (
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto" />
<p className="mt-4">Completing login...</p>
</div>
)}
</div>
)
}
================================================
FILE: app/(auth)/register/[token]/layout.tsx
================================================
import React from 'react';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Register - Changerawr',
description: 'Create your Changerawr account',
};
export default function RegisterLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-100 to-slate-200 dark:from-slate-900 dark:to-slate-800 flex flex-col items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="mb-6 text-center">
<div className="inline-block">
<h1 className="text-2xl font-bold">Changerawr</h1>
</div>
</div>
{children}
<p className="text-center text-sm text-muted-foreground mt-8">
© {new Date().getFullYear()} Changerawr. All rights reserved.
</p>
</div>
</div>
);
}
================================================
FILE: app/(auth)/register/[token]/page.tsx
================================================
'use client'
import React, {useEffect, useState, use} from 'react'
import {useForm} from 'react-hook-form'
import {zodResolver} from '@hookform/resolvers/zod'
import {z} from 'zod'
import {useRouter} from 'next/navigation'
import {motion, AnimatePresence} from 'framer-motion'
import {Input} from '@/components/ui/input'
import {Button} from '@/components/ui/button'
import {Label} from '@/components/ui/label'
import {AlertCircle, ArrowLeft, CheckCircle2, Eye, EyeOff, Loader2, User, Lock, RefreshCw, Mail, Key} from 'lucide-react'
import {Alert, AlertDescription} from '@/components/ui/alert'
import {Card, CardContent, CardFooter} from '@/components/ui/card'
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip"
import Link from 'next/link'
import confetti from 'canvas-confetti'
const registerSchema = z.object({
name: z.string().min(2, 'Name is too short'),
password: z.string().min(8, 'Password must be at least 8 characters'),
confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"]
})
type RegisterForm = z.infer<typeof registerSchema>
interface InvitationInfo {
email: string
role: string
expiresAt: string
}
// Smart confetti function
const fireConfetti = () => {
const isMobile = window.innerWidth < 768;
const defaults = {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: 0,
disableForReducedMotion: true
};
// Check if reduced motion is preferred
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Only show minimal confetti for users who prefer reduced motion
confetti({
...defaults,
particleCount: 20,
gravity: 1,
origin: {y: 0.6, x: 0.5}
});
return;
}
// Initial burst from the center
confetti({
...defaults,
particleCount: isMobile ? 50 : 100,
origin: {y: 0.6, x: 0.5}
});
// Create cannon effect
setTimeout(() => {
confetti({
...defaults,
particleCount: isMobile ? 25 : 50,
angle: 60,
spread: 50,
origin: {x: 0, y: 0.6}
});
confetti({
...defaults,
particleCount: isMobile ? 25 : 50,
angle: 120,
spread: 50,
origin: {x: 1, y: 0.6}
});
}, 250);
// Final smaller bursts
setTimeout(() => {
confetti({
...defaults,
particleCount: isMobile ? 15 : 30,
angle: 90,
gravity: 1.2,
origin: {x: 0.5, y: 0.7}
});
}, 400);
};
export default function RegisterPage({params}: { params: Promise<{ token: string }> }) {
const {token} = use(params)
const [error, setError] = useState('')
const [invitation, setInvitation] = useState<InvitationInfo | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [isSuccess, setIsSuccess] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [passwordStrength, setPasswordStrength] = useState(0)
const router = useRouter()
const {
register,
handleSubmit,
watch,
formState: {errors, isSubmitting, isValid}
} = useForm<RegisterForm>({
resolver: zodResolver(registerSchema),
mode: "onChange",
defaultValues: {
name: '',
password: '',
confirmPassword: ''
}
})
const password = watch('password', '')
// Calculate password strength
useEffect(() => {
if (!password) {
setPasswordStrength(0);
return;
}
let strength = 0;
// Length check
if (password.length >= 8) strength += 1;
if (password.length >= 12) strength += 1;
// Character variety
if (/[A-Z]/.test(password)) strength += 1;
if (/[a-z]/.test(password)) strength += 1;
if (/[0-9]/.test(password)) strength += 1;
if (/[^A-Za-z0-9]/.test(password)) strength += 1;
// Normalize to a scale of 0-3
setPasswordStrength(Math.min(3, Math.floor(strength / 2)));
}, [password]);
// Celebration effect
useEffect(() => {
if (isSuccess) {
// Trigger confetti after a short delay
setTimeout(() => fireConfetti(), 300);
}
}, [isSuccess]);
useEffect(() => {
async function validateInvitation() {
try {
const response = await fetch(`/api/auth/invitation/${token}`)
const data = await response.json()
if (!response.ok) {
console.error('Invitation validation failed:', data)
throw new Error(data.message || 'Invalid invitation')
}
setInvitation(data)
} catch (err) {
if (err instanceof Error) {
setError(err.message)
} else {
setError('This invitation link is invalid or has expired')
}
} finally {
setIsLoading(false)
}
}
validateInvitation()
}, [token])
const onSubmit = async (data: RegisterForm) => {
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
token,
name: data.name,
password: data.password
})
})
if (!response.ok) {
throw new Error('Registration failed')
}
setIsSuccess(true)
// Redirect to login after 3 seconds
setTimeout(() => {
router.push('/login')
}, 3000)
} catch (err: unknown) {
setError('Unable to complete registration')
console.log(err)
}
}
const togglePasswordVisibility = () => {
setShowPassword(!showPassword);
};
// Get strength label and color
const getStrengthLabel = () => {
if (!password) return '';
const labels = ['Weak', 'Fair', 'Good', 'Strong'];
return labels[passwordStrength];
};
const getStrengthColor = () => {
if (!password) return 'bg-muted';
const colors = ['bg-destructive', 'bg-orange-500', 'bg-yellow-500', 'bg-green-500'];
return colors[passwordStrength];
};
if (isLoading) {
return (
<div className="flex flex-col items-center justify-center h-full">
<div className="w-14 h-14 bg-muted/30 rounded-full flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-primary"/>
</div>
<p className="text-muted-foreground mt-4">Validating invitation...</p>
</div>
)
}
return (
<div>
<AnimatePresence mode="wait">
{isSuccess ? (
<motion.div
key="success"
initial={{opacity: 0, scale: 0.8, y: 20}}
animate={{
opacity: 1,
scale: 1,
y: 0,
transition: {
type: "spring",
stiffness: 400,
damping: 30
}
}}
exit={{opacity: 0, scale: 0.8, y: -20}}
className="w-full max-w-sm mx-auto text-center"
>
<motion.div
className="mb-8"
initial={{scale: 0}}
animate={{
scale: 1,
transition: {
type: "spring",
stiffness: 300,
delay: 0.2
}
}}
>
<div
className="w-24 h-24 bg-gradient-to-br from-green-100 to-green-200 dark:from-green-900/30 dark:to-green-800/30 rounded-full flex items-center justify-center mx-auto shadow-md">
<motion.div
animate={{
rotate: [0, 10, -10, 10, 0],
scale: [1, 1.1, 1]
}}
transition={{
duration: 0.5,
delay: 0.3
}}
>
<CheckCircle2 className="h-12 w-12 text-green-600 dark:text-green-400"
strokeWidth={1.5}/>
</motion.div>
</div>
</motion.div>
<motion.div
initial={{opacity: 0, y: 20}}
animate={{
opacity: 1,
y: 0,
transition: {delay: 0.3}
}}
>
<h2 className="text-2xl font-bold mb-2">Account Created Successfully</h2>
{invitation && (
<p className="text-muted-foreground mb-6">
Your account for {invitation.email} has been created successfully.
</p>
)}
</motion.div>
<motion.div
className="space-y-3"
initial={{opacity: 0}}
animate={{
opacity: 1,
transition: {delay: 0.4}
}}
>
<Button
asChild
className="w-full h-11"
>
<Link href="/login">
Continue to Login
</Link>
</Button>
<div className="pt-4">
<p className="text-sm text-muted-foreground">
Redirecting to login page in 3 seconds...
</p>
</div>
</motion.div>
</motion.div>
) : !invitation ? (
<motion.div
key="error"
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -10}}
className="w-full"
>
<Card className="w-full shadow-lg border-t-4 border-t-destructive">
<CardContent className="pt-6">
<div className="flex flex-col items-center text-center space-y-4">
<div
className="w-20 h-20 rounded-full bg-destructive/10 flex items-center justify-center">
<AlertCircle className="h-10 w-10 text-destructive"/>
</div>
<div className="space-y-2">
<h2 className="text-2xl font-bold">Invalid Invitation</h2>
<p className="text-muted-foreground">
{error || 'This invitation link is invalid or has expired.'}
</p>
</div>
<Alert variant="destructive" className="mt-4">
<AlertDescription>
Please contact your administrator for a valid invitation link.
</AlertDescription>
</Alert>
</div>
</CardContent>
<CardFooter className="flex justify-center mt-2 pb-6">
<Button
variant="default"
asChild
>
<Link href="/login">
Return to Login
</Link>
</Button>
</CardFooter>
</Card>
</motion.div>
) : (
<motion.div
key="form"
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -10}}
transition={{duration: 0.2}}
className="w-full"
>
<Card className="w-full shadow-lg border-t-4 border-t-primary overflow-hidden">
<CardContent className="pt-6">
<div className="space-y-4">
<motion.div
className="text-center space-y-2"
initial={{y: -10, opacity: 0}}
animate={{y: 0, opacity: 1}}
transition={{duration: 0.3}}
>
<h1 className="text-2xl font-bold">Complete your registration</h1>
<p className="text-sm text-muted-foreground">
Set up your account for {invitation.email}
</p>
</motion.div>
<AnimatePresence>
{error && (
<motion.div
initial={{opacity: 0, height: 0}}
animate={{opacity: 1, height: 'auto'}}
exit={{opacity: 0, height: 0}}
>
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
</motion.div>
)}
</AnimatePresence>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<motion.div
className="space-y-2"
initial={{x: -10, opacity: 0}}
animate={{x: 0, opacity: 1}}
transition={{duration: 0.3, delay: 0.1}}
>
<Label htmlFor="name">Full name</Label>
<div className="relative group">
<User
className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-primary transition-colors duration-200"/>
<Input
id="name"
{...register('name')}
type="text"
placeholder="Your name"
className={`h-11 pl-10 ${errors.name ? 'border-destructive focus-visible:ring-destructive/20' : 'focus-visible:ring-primary/20'} transition-all duration-200`}
autoFocus
startIcon={<User/>}
/>
</div>
<AnimatePresence>
{errors.name && (
<motion.p
className="text-sm text-destructive flex items-center gap-1 mt-1"
initial={{opacity: 0, height: 0, y: -10}}
animate={{opacity: 1, height: 'auto', y: 0}}
exit={{opacity: 0, height: 0, y: -10}}
>
<span className="inline-block">⚠️</span>
{errors.name.message}
</motion.p>
)}
</AnimatePresence>
</motion.div>
<motion.div
className="space-y-2"
initial={{x: -10, opacity: 0}}
animate={{x: 0, opacity: 1}}
transition={{duration: 0.3, delay: 0.2}}
>
<div className="flex justify-between items-center">
<Label htmlFor="password">Password</Label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" className="h-6 w-6 p-0">
<AlertCircle className="h-4 w-4 text-muted-foreground"/>
</Button>
</TooltipTrigger>
<TooltipContent>
<p className="max-w-xs">Password should be at least 8
characters. Strong passwords include uppercase letters,
numbers, and symbols.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="relative group">
<Lock
className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-primary transition-colors duration-200"/>
<Input
id="password"
{...register('password')}
type={showPassword ? 'text' : 'password'}
placeholder="••••••••"
className={`h-11 pl-10 pr-10 ${errors.password ? 'border-destructive focus-visible:ring-destructive/20' : 'focus-visible:ring-primary/20'} transition-all duration-200`}
startIcon={<Key/>}
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={togglePasswordVisibility}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground"/>
) : (
<Eye className="h-4 w-4 text-muted-foreground"/>
)}
</Button>
</div>
{/* Password strength indicator */}
{password && (
<div className="pt-1">
<div className="flex justify-between items-center text-xs mb-1">
<span>Password strength:</span>
<span className={
passwordStrength === 0 ? "text-destructive" :
passwordStrength === 1 ? "text-orange-500" :
passwordStrength === 2 ? "text-yellow-500" :
"text-green-500"
}>
{getStrengthLabel()}
</span>
</div>
<div
className="h-1.5 w-full bg-muted rounded-full overflow-hidden flex">
<div
className={`h-full ${getStrengthColor()} transition-all duration-300 ease-out`}
style={{width: `${(passwordStrength + 1) * 25}%`}}
></div>
</div>
</div>
)}
<AnimatePresence>
{errors.password && (
<motion.p
className="text-sm text-destructive flex items-center gap-1 mt-1"
initial={{opacity: 0, height: 0, y: -10}}
animate={{opacity: 1, height: 'auto', y: 0}}
exit={{opacity: 0, height: 0, y: -10}}
>
<span className="inline-block">⚠️</span>
{errors.password.message}
</motion.p>
)}
</AnimatePresence>
</motion.div>
<motion.div
className="space-y-2"
initial={{x: -10, opacity: 0}}
animate={{x: 0, opacity: 1}}
transition={{duration: 0.3, delay: 0.3}}
>
<Label htmlFor="confirmPassword">Confirm password</Label>
<div className="relative">
<Lock
className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground"/>
<Input
id="confirmPassword"
{...register('confirmPassword')}
type={showPassword ? 'text' : 'password'}
placeholder="••••••••"
className={`h-11 pl-10 ${errors.confirmPassword ? 'border-destructive focus-visible:ring-destructive/20' : 'focus-visible:ring-primary/20'} transition-all duration-200`}
startIcon={<Key/>}
/>
</div>
<AnimatePresence>
{errors.confirmPassword && (
<motion.p
className="text-sm text-destructive flex items-center gap-1 mt-1"
initial={{opacity: 0, height: 0, y: -10}}
animate={{opacity: 1, height: 'auto', y: 0}}
exit={{opacity: 0, height: 0, y: -10}}
>
<span className="inline-block">⚠️</span>
{errors.confirmPassword.message}
</motion.p>
)}
</AnimatePresence>
</motion.div>
<motion.div
initial={{y: 10, opacity: 0}}
animate={{y: 0, opacity: 1}}
transition={{duration: 0.3, delay: 0.4}}
>
<Button
type="submit"
className={`
w-full h-11 relative overflow-hidden
${isValid ? 'bg-primary hover:bg-primary/90' : 'bg-primary/70'}
transition-all duration-300
`}
disabled={isSubmitting || !isValid}
>
{isSubmitting ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin"/>
Creating account...
</span>
) : (
<>
<RefreshCw className="mr-2 h-4 w-4"/>
Create account
</>
)}
{isValid && !isSubmitting && (
<span
className="absolute right-0 top-0 h-full w-12 -skew-x-12 overflow-hidden flex justify-center items-center">
<motion.div
className="bg-white/20 h-8 w-8 rounded-full"
initial={{x: -100}}
animate={{x: 150}}
transition={{
repeat: Infinity,
duration: 2,
ease: "easeInOut",
repeatDelay: 1
}}
/>
</span>
)}
</Button>
</motion.div>
</form>
</div>
</CardContent>
<CardFooter className="flex justify-center pb-6">
<motion.div
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{delay: 0.5}}
>
<Button
variant="ghost"
asChild
size="sm"
className="text-sm text-muted-foreground hover:text-foreground"
>
<Link href="/login">
<ArrowLeft className="mr-2 h-4 w-4"/>
Back to Login
</Link>
</Button>
</motion.div>
</CardFooter>
</Card>
</motion.div>
)}
</AnimatePresence>
</div>
)
}
================================================
FILE: app/(auth)/reset-password/[token]/layout.tsx
================================================
import React from 'react';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Reset Password - Changerawr',
description: 'Create a new password for your Changerawr account',
};
export default function ResetPasswordLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-100 to-slate-200 dark:from-slate-900 dark:to-slate-800 flex flex-col items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="mb-6 text-center">
<div className="inline-block">
<h1 className="text-2xl font-bold">Changerawr</h1>
</div>
</div>
{children}
<p className="text-center text-sm text-muted-foreground mt-8">
© {new Date().getFullYear()} Changerawr. All rights reserved.
</p>
</div>
</div>
);
}
================================================
FILE: app/(auth)/reset-password/[token]/page.tsx
================================================
import { Suspense } from 'react';
import ResetPasswordForm from './reset-password-form';
import { LoadingSpinner } from '@/components/loading-spinner';
export default async function ResetPasswordPage({
params,
}: {
params: Promise<{ token: string }>;
}) {
const { token } = await params;
return (
<Suspense fallback={<LoadingSpinner />}>
<ResetPasswordForm token={token} />
</Suspense>
);
}
================================================
FILE: app/(auth)/reset-password/[token]/reset-password-form.tsx
================================================
'use client'
import React, { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useRouter } from 'next/navigation'
import { motion, AnimatePresence } from 'framer-motion'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import { AlertCircle, ArrowLeft, CheckCircle2, Eye, EyeOff, Loader2, Lock, RefreshCw } from 'lucide-react'
import Link from 'next/link'
import confetti from 'canvas-confetti'
import {
Card,
CardContent,
CardFooter,
} from '@/components/ui/card'
import { useToast } from '@/hooks/use-toast'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
// Password schema with requirements
const passwordSchema = z
.object({
password: z.string().min(8, 'Password must be at least 8 characters'),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type ResetPasswordForm = z.infer<typeof passwordSchema>;
// Smart confetti function
const fireConfetti = () => {
const isMobile = window.innerWidth < 768;
const defaults = {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: 0,
disableForReducedMotion: true
};
// Check if reduced motion is preferred
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Only show minimal confetti for users who prefer reduced motion
confetti({
...defaults,
particleCount: 20,
gravity: 1,
origin: { y: 0.6, x: 0.5 }
});
return;
}
// Initial burst from the center
confetti({
...defaults,
particleCount: isMobile ? 50 : 100,
origin: { y: 0.6, x: 0.5 }
});
// Create cannon effect
setTimeout(() => {
confetti({
...defaults,
particleCount: isMobile ? 25 : 50,
angle: 60,
spread: 50,
origin: { x: 0, y: 0.6 }
gitextract_pand8s4o/
├── .dockerignore
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yml
│ │ └── feature-request.yml
│ └── workflows/
│ ├── build-platform.yml
│ └── docker-build.yml
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── changerawr.iml
│ ├── dataSources.xml
│ ├── data_source_mapping.xml
│ ├── db-forest-config.xml
│ ├── discord.xml
│ ├── inspectionProfiles/
│ │ └── Project_Default.xml
│ ├── jsLibraryMappings.xml
│ ├── modules.xml
│ ├── sqldialects.xml
│ └── vcs.xml
├── APIDOCGUIDE.md
├── CHANGELOG.md
├── Caddyfile
├── Dockerfile
├── Dockerfile.compose
├── LICENSE
├── PROJECT_INDEX.md
├── README.md
├── app/
│ ├── (auth)/
│ │ ├── forgot-password/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── login/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── oauth-callback/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── register/
│ │ │ └── [token]/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── reset-password/
│ │ │ └── [token]/
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ └── reset-password-form.tsx
│ │ ├── setup/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── two-factor/
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── (email)/
│ │ └── unsubscribed/
│ │ └── page.tsx
│ ├── .well-known/
│ │ └── acme-challenge/
│ │ ├── [token]/
│ │ │ ├── route.ts
│ │ │ └── route.ts.disabled
│ │ └── route.ts
│ ├── api/
│ │ ├── acme/
│ │ │ ├── cancel/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ ├── issue/
│ │ │ │ └── route.ts
│ │ │ ├── renew/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ ├── revoke/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ ├── status/
│ │ │ │ └── [certId]/
│ │ │ │ └── route.ts
│ │ │ └── verify-dns/
│ │ │ └── route.ts
│ │ ├── admin/
│ │ │ ├── ai-settings/
│ │ │ │ ├── route.ts
│ │ │ │ └── test-key/
│ │ │ │ └── route.ts
│ │ │ ├── analytics/
│ │ │ │ └── route.ts
│ │ │ ├── api-keys/
│ │ │ │ ├── [keyId]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── audit-logs/
│ │ │ │ ├── actions/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── config/
│ │ │ │ ├── route.ts
│ │ │ │ └── system-email/
│ │ │ │ └── route.ts
│ │ │ ├── dashboard/
│ │ │ │ └── route.ts
│ │ │ ├── oauth/
│ │ │ │ └── providers/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── saml/
│ │ │ │ └── providers/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── sponsor/
│ │ │ │ └── route.ts
│ │ │ ├── system/
│ │ │ │ └── slack/
│ │ │ │ └── route.ts
│ │ │ └── users/
│ │ │ ├── [userId]/
│ │ │ │ ├── role/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── invitations/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── ai/
│ │ │ ├── decrypt/
│ │ │ │ └── route.ts
│ │ │ └── settings/
│ │ │ └── route.ts
│ │ ├── analytics/
│ │ │ └── track/
│ │ │ └── route.ts
│ │ ├── auth/
│ │ │ ├── change-password/
│ │ │ │ └── route.ts
│ │ │ ├── cli/
│ │ │ │ ├── generate/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── refresh/
│ │ │ │ │ └── route.ts
│ │ │ │ └── token/
│ │ │ │ └── route.ts
│ │ │ ├── connections/
│ │ │ │ └── route.ts
│ │ │ ├── forgot-password/
│ │ │ │ └── route.ts
│ │ │ ├── invitation/
│ │ │ │ └── [token]/
│ │ │ │ └── route.ts
│ │ │ ├── login/
│ │ │ │ ├── route.ts
│ │ │ │ └── second-factor/
│ │ │ │ └── route.ts
│ │ │ ├── logout/
│ │ │ │ └── route.ts
│ │ │ ├── me/
│ │ │ │ └── route.ts
│ │ │ ├── oauth/
│ │ │ │ ├── authorize/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── callback/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── providers/
│ │ │ │ └── route.ts
│ │ │ ├── passkeys/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── authenticate/
│ │ │ │ │ ├── options/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── verify/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── register/
│ │ │ │ │ ├── options/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── verify/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── preview/
│ │ │ │ └── route.ts
│ │ │ ├── refresh/
│ │ │ │ └── route.ts
│ │ │ ├── register/
│ │ │ │ └── route.ts
│ │ │ ├── reset-password/
│ │ │ │ ├── [token]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── request/
│ │ │ │ └── route.ts
│ │ │ ├── saml/
│ │ │ │ ├── authorize/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── callback/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── metadata/
│ │ │ │ │ └── [providerName]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── providers/
│ │ │ │ └── route.ts
│ │ │ ├── security-settings/
│ │ │ │ └── route.ts
│ │ │ ├── settings/
│ │ │ │ └── route.ts
│ │ │ └── validate/
│ │ │ └── route.ts
│ │ ├── avatar/
│ │ │ └── [hash]/
│ │ │ └── route.ts
│ │ ├── changelog/
│ │ │ ├── [projectId]/
│ │ │ │ └── entries/
│ │ │ │ ├── all/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── entries/
│ │ │ │ └── [entryId]/
│ │ │ │ └── route.ts
│ │ │ ├── requests/
│ │ │ │ ├── [requestId]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── subscribe/
│ │ │ │ └── route.ts
│ │ │ ├── unsubscribe/
│ │ │ │ └── [token]/
│ │ │ │ └── route.ts
│ │ │ └── verify-domain/
│ │ │ └── route.ts
│ │ ├── check-setup/
│ │ │ └── route.ts
│ │ ├── config/
│ │ │ ├── runtime/
│ │ │ │ └── route.ts
│ │ │ └── timezone/
│ │ │ └── route.ts
│ │ ├── cron/
│ │ │ └── ssl-renewal/
│ │ │ └── route.ts
│ │ ├── custom-domains/
│ │ │ ├── [domain]/
│ │ │ │ ├── browser-rules/
│ │ │ │ │ ├── [id]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── dns-instructions/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ ├── ssl/
│ │ │ │ │ ├── mode/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── revoke/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── toggle-https/
│ │ │ │ │ └── route.ts
│ │ │ │ └── throttle/
│ │ │ │ └── route.ts
│ │ │ ├── add/
│ │ │ │ └── route.ts
│ │ │ ├── list/
│ │ │ │ └── route.ts
│ │ │ └── verify/
│ │ │ └── route.ts
│ │ ├── dashboard/
│ │ │ └── stats/
│ │ │ └── route.ts
│ │ ├── domain-check/
│ │ │ └── route.ts
│ │ ├── health/
│ │ │ └── route.ts
│ │ ├── integrations/
│ │ │ ├── slack/
│ │ │ │ ├── callback/
│ │ │ │ │ └── route.ts
│ │ │ │ └── manifest/
│ │ │ │ └── route.ts
│ │ │ └── widget/
│ │ │ └── [projectId]/
│ │ │ ├── [widgetId]/
│ │ │ │ └── route.ts
│ │ │ ├── list/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── internal/
│ │ │ ├── agent/
│ │ │ │ └── version/
│ │ │ │ └── route.ts
│ │ │ ├── cert/
│ │ │ │ └── [domain]/
│ │ │ │ └── route.ts
│ │ │ └── ip-config/
│ │ │ └── route.ts
│ │ ├── projects/
│ │ │ ├── [projectId]/
│ │ │ │ ├── analytics/
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── api-keys/
│ │ │ │ │ ├── [keyId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── catch-up/
│ │ │ │ │ ├── ai-summary/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── changelog/
│ │ │ │ │ ├── [entryId]/
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ └── schedule/
│ │ │ │ │ │ ├── approval/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── route.ts
│ │ │ │ │ └── tags/
│ │ │ │ │ ├── [tagId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── cli/
│ │ │ │ │ ├── link/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── sync/
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ └── status/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── unlink/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── integrations/
│ │ │ │ │ ├── email/
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ ├── send/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── test/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── github/
│ │ │ │ │ │ ├── generate/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ ├── route.ts
│ │ │ │ │ │ ├── tags/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── test/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── slack/
│ │ │ │ │ ├── channels/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ ├── settings/
│ │ │ │ │ └── route.ts
│ │ │ │ └── versions/
│ │ │ │ └── route.ts
│ │ │ ├── import/
│ │ │ │ ├── canny/
│ │ │ │ │ ├── fetch/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── validate/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── parse/
│ │ │ │ │ └── route.ts
│ │ │ │ └── process/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── requests/
│ │ │ └── route.ts
│ │ ├── search/
│ │ │ └── route.ts
│ │ ├── setup/
│ │ │ ├── admin/
│ │ │ │ └── route.ts
│ │ │ ├── invitations/
│ │ │ │ └── route.ts
│ │ │ ├── oauth/
│ │ │ │ ├── auto/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── debug/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ ├── settings/
│ │ │ │ └── route.ts
│ │ │ ├── setupProgressSchema.ts
│ │ │ ├── status/
│ │ │ │ └── route.ts
│ │ │ └── types.ts
│ │ ├── subscribers/
│ │ │ ├── [subscriberId]/
│ │ │ │ └── route.ts
│ │ │ ├── generate-mock/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── system/
│ │ │ ├── agent-version/
│ │ │ │ └── route.ts
│ │ │ ├── easypanel/
│ │ │ │ └── status/
│ │ │ │ └── route.ts
│ │ │ ├── perform-update/
│ │ │ │ └── route.ts
│ │ │ ├── update-status/
│ │ │ │ └── route.ts
│ │ │ └── version/
│ │ │ └── route.ts
│ │ └── telemetry/
│ │ ├── config/
│ │ │ └── route.ts
│ │ └── debug/
│ │ └── route.ts
│ ├── api-docs/
│ │ └── route.ts
│ ├── changelog/
│ │ ├── [projectId]/
│ │ │ ├── [entryId]/
│ │ │ │ └── page.tsx
│ │ │ ├── changelog-view.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── loading.tsx
│ │ │ ├── not-found.tsx
│ │ │ ├── page.tsx
│ │ │ └── rss.xml/
│ │ │ └── route.ts
│ │ └── custom-domain/
│ │ └── [domain]/
│ │ ├── [entryId]/
│ │ │ ├── EntryContent.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── rss.xml/
│ │ │ └── route.ts
│ │ └── unsubscribed/
│ │ └── page.tsx
│ ├── cli/
│ │ └── auth/
│ │ └── page.tsx
│ ├── dashboard/
│ │ ├── admin/
│ │ │ ├── about/
│ │ │ │ └── page.tsx
│ │ │ ├── ai-settings/
│ │ │ │ └── page.tsx
│ │ │ ├── analytics/
│ │ │ │ └── page.tsx
│ │ │ ├── api-keys/
│ │ │ │ └── page.tsx
│ │ │ ├── audit-logs/
│ │ │ │ └── page.tsx
│ │ │ ├── domains/
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── oauth/
│ │ │ │ └── page.tsx
│ │ │ ├── page.tsx
│ │ │ ├── requests/
│ │ │ │ └── page.tsx
│ │ │ ├── system/
│ │ │ │ ├── email/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── slack/
│ │ │ │ │ └── page.tsx
│ │ │ │ └── templates/
│ │ │ │ └── page.tsx
│ │ │ └── users/
│ │ │ └── page.tsx
│ │ ├── bookmarks/
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── projects/
│ │ │ ├── [projectId]/
│ │ │ │ ├── analytics/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── api-keys/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── catch-up/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── changelog/
│ │ │ │ │ ├── [entryId]/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── new/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── domains/
│ │ │ │ │ ├── [domain]/
│ │ │ │ │ │ ├── client.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── components/
│ │ │ │ │ │ ├── DomainCard.tsx
│ │ │ │ │ │ ├── InlineSSLSetup.tsx
│ │ │ │ │ │ ├── SSLCertificateCard.tsx
│ │ │ │ │ │ ├── SSLCertificateWizard.tsx
│ │ │ │ │ │ ├── SSLManagement.tsx
│ │ │ │ │ │ └── ssl/
│ │ │ │ │ │ ├── ExternalSSLManagement.tsx
│ │ │ │ │ │ ├── SSLCertificateActions.tsx
│ │ │ │ │ │ ├── SSLCertificateStatus.tsx
│ │ │ │ │ │ ├── SSLDNSInstructions.tsx
│ │ │ │ │ │ ├── SSLModeSelector.tsx
│ │ │ │ │ │ ├── SSLVerificationMethod.tsx
│ │ │ │ │ │ ├── SSLVerificationProgress.tsx
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── page.tsx.backup
│ │ │ │ ├── import/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── integrations/
│ │ │ │ │ ├── email/
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ └── subscribers/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── github/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── slack/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── widget/
│ │ │ │ │ ├── [widgetId]/
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ └── widget-editor.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── widget-config.tsx
│ │ │ │ │ └── widget-list.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── settings/
│ │ │ │ └── page.tsx
│ │ │ ├── new/
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── providers.tsx
│ │ ├── requests/
│ │ │ └── page.tsx
│ │ └── settings/
│ │ └── page.tsx
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ └── startup.ts
├── components/
│ ├── CommandPalette.tsx
│ ├── DinoGame.tsx
│ ├── Logo.tsx
│ ├── MarkdownEditor.tsx
│ ├── UpdateIndicatorBadge.tsx
│ ├── UpdateStatus.tsx
│ ├── admin/
│ │ ├── api/
│ │ │ ├── PermissionsModal.tsx
│ │ │ ├── Rename.tsx
│ │ │ ├── sdk-showcase-compact.tsx
│ │ │ └── sdk-showcase.tsx
│ │ ├── audit-logs/
│ │ │ └── VirtualizedList.tsx
│ │ └── requests/
│ │ └── Management.tsx
│ ├── analytics/
│ │ ├── analytics-chart.tsx
│ │ ├── analytics-metric-card.tsx
│ │ ├── country-analytics-table.tsx
│ │ ├── entry-analytics-table.tsx
│ │ ├── project-analytics-table.tsx
│ │ └── referrer-analytics-table.tsx
│ ├── changelog/
│ │ ├── ButtonGroup.tsx
│ │ ├── ChangelogActionRequest.tsx
│ │ ├── ChangelogEditor.tsx
│ │ ├── ChangelogEntries.tsx
│ │ ├── RequestHandler.tsx
│ │ ├── ScrollToTopButton.tsx
│ │ ├── ShareButton.tsx
│ │ ├── ThemeToggle.tsx
│ │ ├── WidgetPreview.tsx
│ │ └── editor/
│ │ ├── AITitleGenerator.tsx
│ │ ├── EditorHeader.tsx
│ │ ├── TagColorPicker.tsx
│ │ ├── TagSelector.tsx
│ │ ├── TagSuggester.tsx
│ │ ├── VersionSelector.tsx
│ │ └── scheduler/
│ │ └── ScheduleEntryDialog.tsx
│ ├── dashboard/
│ │ └── WhatsNewModal.tsx
│ ├── github/
│ │ ├── GitHubGenerateDialog.tsx
│ │ └── GitHubIntegrationSettings.tsx
│ ├── loading-spinner.tsx
│ ├── markdown-editor/
│ │ ├── MarkdownEditor.tsx
│ │ ├── MarkdownEditorArea.tsx
│ │ ├── MarkdownPreview.tsx
│ │ ├── MarkdownToolbar.tsx
│ │ ├── RenderMarkdown.tsx
│ │ ├── StatusBar.tsx
│ │ ├── ai/
│ │ │ └── AIAssistantPanel.tsx
│ │ ├── hooks/
│ │ │ └── useCUMModals.ts
│ │ ├── index.ts
│ │ ├── modals/
│ │ │ ├── CUMAlertModal.tsx
│ │ │ ├── CUMButtonModal.tsx
│ │ │ ├── CUMEmbedModal.tsx
│ │ │ ├── CUMTableModal.tsx
│ │ │ └── index.ts
│ │ ├── types/
│ │ │ └── cum-extensions.ts
│ │ └── utils/
│ │ ├── formatting.ts
│ │ ├── keyboard-shortcuts.ts
│ │ ├── renderer.ts
│ │ └── safe-keyboard-shortcuts.tsx
│ ├── project/
│ │ ├── ProjectNavItem.tsx
│ │ ├── ProjectSettingsPage.tsx
│ │ ├── ProjectSidebar.tsx
│ │ ├── RecentChangelogItem.tsx
│ │ ├── catch-up/
│ │ │ ├── CatchUpEntry.tsx
│ │ │ ├── CatchUpView.tsx
│ │ │ └── SinceSelector.tsx
│ │ └── settings/
│ │ └── TagManagement.tsx
│ ├── projects/
│ │ └── importing/
│ │ ├── ChangelogImportModal.tsx
│ │ ├── ImportDataPrompt.tsx
│ │ └── integrations/
│ │ └── CannyImportStep.tsx
│ ├── providers/
│ │ ├── CommandPaletteProvider.tsx
│ │ └── setup-context.tsx
│ ├── settings/
│ │ ├── connected-sso-section.tsx
│ │ ├── passkeys-section.tsx
│ │ └── security-settings.tsx
│ ├── setup/
│ │ ├── TeamImportModal.tsx
│ │ ├── setup-context.tsx
│ │ ├── setup-step.tsx
│ │ └── steps/
│ │ ├── admin-step.tsx
│ │ ├── completion-step.tsx
│ │ ├── oauth-step.tsx
│ │ ├── settings-step.tsx
│ │ ├── team-step.tsx
│ │ └── welcome-step.tsx
│ ├── sso/
│ │ └── ProviderLogo.tsx
│ ├── subscription-form.tsx
│ ├── telemetry/
│ │ └── PromptModal.tsx
│ ├── theme-provider.tsx
│ └── ui/
│ ├── accordion.tsx
│ ├── alert-dialog.tsx
│ ├── alert.tsx
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── breadcrumb.tsx
│ ├── breadcrumbs.tsx
│ ├── button.tsx
│ ├── calendar.tsx
│ ├── card.tsx
│ ├── checkbox.tsx
│ ├── collapsible.tsx
│ ├── command.tsx
│ ├── confetti.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── error-alert.tsx
│ ├── form.tsx
│ ├── hover-card.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── popover.tsx
│ ├── progress.tsx
│ ├── radio-group.tsx
│ ├── scroll-area.tsx
│ ├── searchable-select.tsx
│ ├── select.tsx
│ ├── separator.tsx
│ ├── sheet.tsx
│ ├── sidebar.tsx
│ ├── skeleton.tsx
│ ├── slider.tsx
│ ├── switch.tsx
│ ├── table.tsx
│ ├── tabs.tsx
│ ├── textarea.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ └── tooltip.tsx
├── components.json
├── context/
│ └── auth.tsx
├── docker-compose-online.yml
├── docker-compose.yml
├── docker-entrypoint-compose.sh
├── docker-entrypoint.sh
├── emails/
│ ├── approval-notification.tsx
│ ├── changelog.tsx
│ ├── password-reset.tsx
│ ├── rejection-notification.tsx
│ └── schedule-published.tsx
├── eslint.config.mjs
├── hooks/
│ ├── use-local-storage.ts
│ ├── use-media-query.ts
│ ├── use-timezone.ts
│ ├── use-toast.ts
│ ├── useAIAssistant.ts
│ ├── useBookmarks.ts
│ ├── useChunkedData.ts
│ ├── useCommandPalette.ts
│ ├── useEditorHistory.ts
│ ├── useMarkdownState.ts
│ ├── useSlashCommands.ts
│ ├── useTelemetry.ts
│ └── useWhatsNew.ts
├── ideas.md
├── instrumentation.ts
├── issues.md
├── jsdoc.json
├── lib/
│ ├── api/
│ │ ├── README.md
│ │ ├── middleware.ts
│ │ ├── permissions.ts
│ │ └── route-permissions.ts
│ ├── app-info.ts
│ ├── auth/
│ │ ├── api-key.ts
│ │ ├── authorization.ts
│ │ ├── claim-validator.ts
│ │ ├── cli-auth.ts
│ │ ├── email-domain-validator.ts
│ │ ├── jwt-utils.ts
│ │ ├── oauth.ts
│ │ ├── password.ts
│ │ ├── providers/
│ │ │ ├── easypanel/
│ │ │ │ ├── auto-setup.ts
│ │ │ │ └── client.ts
│ │ │ ├── easypanel.ts
│ │ │ └── pocketid.ts
│ │ ├── saml.ts
│ │ ├── tokens.ts
│ │ └── webauthn.ts
│ ├── constants/
│ │ └── timezones.ts
│ ├── custom-domains/
│ │ ├── constants.ts
│ │ ├── dns.ts
│ │ ├── service.ts
│ │ ├── ssl/
│ │ │ ├── acme-account.ts
│ │ │ ├── auto-renewal.ts
│ │ │ ├── cron-setup.md
│ │ │ ├── encryption.ts
│ │ │ ├── is-supported.ts
│ │ │ ├── service.ts
│ │ │ ├── setup-renewal-job.ts
│ │ │ ├── ssrf-guard.ts
│ │ │ └── webhook.ts
│ │ ├── utils.ts
│ │ └── validation.ts
│ ├── db.ts
│ ├── middleware/
│ │ └── analytics.ts
│ ├── services/
│ │ ├── analytics/
│ │ │ └── geolocation.ts
│ │ ├── auth/
│ │ │ ├── password-breach.ts
│ │ │ └── password-reset.ts
│ │ ├── bookmarks/
│ │ │ └── bookmark.service.ts
│ │ ├── changelog/
│ │ │ └── rss.ts
│ │ ├── core/
│ │ │ ├── markdown/
│ │ │ │ ├── EXTENSIONS.md
│ │ │ │ ├── EXTENSIONS_SETUP.md
│ │ │ │ ├── extensions/
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── subtext/
│ │ │ │ │ │ └── subtext.ts
│ │ │ │ │ └── table/
│ │ │ │ │ └── table.ts
│ │ │ │ └── useCustomExtensions.ts
│ │ │ └── system-user/
│ │ │ ├── README.md
│ │ │ └── service.ts
│ │ ├── easypanel/
│ │ │ └── index.ts
│ │ ├── email/
│ │ │ ├── notification.ts
│ │ │ └── schedule-notification.ts
│ │ ├── github/
│ │ │ ├── changelog-generator.ts
│ │ │ └── client.ts
│ │ ├── jobs/
│ │ │ ├── executors/
│ │ │ │ ├── changelog-publish.executor.ts
│ │ │ │ ├── ssl-renewal.executor.ts
│ │ │ │ └── telemetry-send.executor.ts
│ │ │ ├── job-runner.service.ts
│ │ │ └── scheduled-job.service.ts
│ │ ├── projects/
│ │ │ ├── catch-up/
│ │ │ │ └── catch-up.service.ts
│ │ │ └── importing/
│ │ │ ├── index.ts
│ │ │ ├── integrations/
│ │ │ │ └── canny.service.ts
│ │ │ ├── markdown-parser.service.ts
│ │ │ ├── processor.service.ts
│ │ │ └── validation.service.ts
│ │ ├── request/
│ │ │ └── changelog-request.ts
│ │ ├── search/
│ │ │ └── service.ts
│ │ ├── slack/
│ │ │ ├── index.ts
│ │ │ ├── logo.tsx
│ │ │ └── post-message.ts
│ │ ├── sponsor/
│ │ │ └── service.ts
│ │ └── telemetry/
│ │ └── service.ts
│ ├── types/
│ │ ├── analytics.ts
│ │ ├── auth.ts
│ │ ├── changelog.ts
│ │ ├── cli/
│ │ │ └── project-api.ts
│ │ ├── custom-domains.ts
│ │ ├── dashboard.ts
│ │ ├── easypanel.ts
│ │ ├── oauth.ts
│ │ ├── projects/
│ │ │ ├── catch-up/
│ │ │ │ └── types.ts
│ │ │ ├── importing/
│ │ │ │ └── canny.ts
│ │ │ └── importing.ts
│ │ ├── saml.ts
│ │ ├── settings.ts
│ │ ├── sso.ts
│ │ └── telemetry.ts
│ ├── utils/
│ │ ├── ai/
│ │ │ ├── prompts.ts
│ │ │ ├── secton.ts
│ │ │ └── types.ts
│ │ ├── analytics.ts
│ │ ├── api.ts
│ │ ├── auditLog.ts
│ │ ├── changelog.ts
│ │ ├── cookies.ts
│ │ ├── docker.ts
│ │ ├── encryption.ts
│ │ ├── format-date.ts
│ │ ├── gravatar.ts
│ │ ├── rate-limit.ts
│ │ └── text.ts
│ └── utils.ts
├── next.config.ts
├── next.openapi.json
├── nginx.conf
├── package-lock.json.backup
├── package.json
├── package.json.backup
├── postcss.config.mjs
├── prisma/
│ ├── migrations/
│ │ ├── 20250215042251_init_authentication/
│ │ │ └── migration.sql
│ │ ├── 20250215051621_add_invitation_links/
│ │ │ └── migration.sql
│ │ ├── 20250215063030_add_last_logged_in_at_field/
│ │ │ └── migration.sql
│ │ ├── 20250215075317_add_changelog_models/
│ │ │ └── migration.sql
│ │ ├── 20250215080528_add_viewer_role/
│ │ │ └── migration.sql
│ │ ├── 20250215180005_add_project_settings/
│ │ │ └── migration.sql
│ │ ├── 20250215232640_add_api_keys/
│ │ │ └── migration.sql
│ │ ├── 20250216005356_add_audit_logs/
│ │ │ └── migration.sql
│ │ ├── 20250216050812_add_system_configuration/
│ │ │ └── migration.sql
│ │ ├── 20250216053424_update_changelog_requests_schema/
│ │ │ └── migration.sql
│ │ ├── 20250221000121_add_new_request_types/
│ │ │ └── migration.sql
│ │ ├── 20250305225249_add_oauth/
│ │ │ └── migration.sql
│ │ ├── 20250418044737_add_email_integration/
│ │ │ └── migration.sql
│ │ ├── 20250418045834_add_email_logs/
│ │ │ └── migration.sql
│ │ ├── 20250418161805_add_email_log_enums/
│ │ │ └── migration.sql
│ │ ├── 20250418163601_add_email_subscription/
│ │ │ └── migration.sql
│ │ ├── 20250419000001_add_password_reset/
│ │ │ └── migration.sql
│ │ ├── 20250421000001_add_notification_preferences/
│ │ │ └── migration.sql
│ │ ├── 20250504203021_passkey_support/
│ │ │ └── migration.sql
│ │ ├── 20250504203707_add_last_challenge/
│ │ │ └── migration.sql
│ │ ├── 20250504211358_add_two_factor_mode/
│ │ │ └── migration.sql
│ │ ├── 20250504212400_add_two_factor_session/
│ │ │ └── migration.sql
│ │ ├── 20250511204818_add_ai_assistant_config/
│ │ │ └── migration.sql
│ │ ├── 20250512000000_add_github_integration/
│ │ │ └── migration.sql
│ │ ├── 20250610120000_fix_user_deletion_constraints/
│ │ │ └── migration.sql
│ │ ├── 20250613040144_changelog_analytics/
│ │ │ └── migration.sql
│ │ ├── 20250624024525_add_custom_domains/
│ │ │ └── migration.sql
│ │ ├── 20250625030954_add_custom_domains_to_email_notifications/
│ │ │ └── migration.sql
│ │ ├── 20250625120000_add_changelog_scheduling/
│ │ │ └── migration.sql
│ │ ├── 20250627020135_add_color_field_to_changelog_tag/
│ │ │ └── migration.sql
│ │ ├── 20250628171029_add_telemetry/
│ │ │ └── migration.sql
│ │ ├── 20250628175000_preemptive_telemetry_setup/
│ │ │ └── migration.sql
│ │ ├── 20250628180800_add_telemetry_support/
│ │ │ └── migration.sql
│ │ ├── 20250628215000_complete_telemetry_fix/
│ │ │ └── migration.sql
│ │ ├── 20250628215100_fix_telemetry_casting/
│ │ │ └── migration.sql
│ │ ├── 20250628230000_add_changelogentryid_back/
│ │ │ └── migration.sql
│ │ ├── 20250702092443_add_cli_auth_codes/
│ │ │ └── migration.sql
│ │ ├── 20250702121802_add_synced_cli_migrations_and_metadata/
│ │ │ └── migration.sql
│ │ ├── 20250703092000_fix_missing_changelogentryid_column/
│ │ │ └── migration.sql
│ │ ├── 20251031_add_metadata_to_changelog_request/
│ │ │ └── migration.sql
│ │ ├── 20251111210147_add_api_key_and_widgets/
│ │ │ └── migration.sql
│ │ ├── 20251127011835_add_slack_integration/
│ │ │ └── migration.sql
│ │ ├── 20251127015242_add_slack_oauth_to_system_config/
│ │ │ └── migration.sql
│ │ ├── 20251127_add_slack_signing_secret/
│ │ │ └── migration.sql
│ │ ├── 20260219214321_add_license_fields/
│ │ │ └── migration.sql
│ │ ├── 20260219215954_fix_changelog_request_cascade_delete/
│ │ │ └── migration.sql
│ │ ├── 20260220120000_add_timezone_config/
│ │ │ └── migration.sql
│ │ ├── 20260220140000_add_custom_date_templates/
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema/
│ │ ├── README.md
│ │ ├── base.prisma
│ │ ├── enums.prisma
│ │ ├── integrations.prisma
│ │ ├── projects.prisma
│ │ ├── system.prisma
│ │ └── users.prisma
│ ├── schema.prisma.backup
│ └── seed.ts
├── proxy.ts
├── public/
│ └── swagger.json
├── scripts/
│ ├── api/
│ │ ├── generateSwagger.js
│ │ └── generateSwagger.ts
│ ├── ftb/
│ │ ├── index.html
│ │ └── server.js
│ ├── maintenance/
│ │ ├── index.html
│ │ └── server.js
│ ├── nginx-reload.sh
│ ├── utils/
│ │ └── scan.ts
│ └── widget/
│ └── build.ts
├── tailwind.config.ts
├── temp_migration.sql
├── tsconfig.json
├── useful-information-for-development/
│ └── slack_scopes.md
└── widgets/
├── changelog/
│ └── index.js
├── core/
│ └── styles/
│ ├── announcement.css
│ ├── common.css
│ ├── floating.css
│ ├── modal.css
│ ├── reset.css
│ └── variables.css
└── variants/
├── announcement.js
├── classic.js
├── floating.js
└── modal.js
SYMBOL INDEX (1951 symbols across 494 files)
FILE: app/(auth)/forgot-password/layout.tsx
function ForgotPasswordLayout (line 9) | function ForgotPasswordLayout({
FILE: app/(auth)/forgot-password/page.tsx
type ForgotPasswordFormValues (line 38) | type ForgotPasswordFormValues = z.infer<typeof forgotPasswordSchema>;
function ForgotPasswordPage (line 123) | function ForgotPasswordPage() {
FILE: app/(auth)/layout.tsx
function AuthLayout (line 7) | function AuthLayout({
FILE: app/(auth)/login/layout.tsx
function RegisterLayout (line 9) | function RegisterLayout({
FILE: app/(auth)/login/page.tsx
type EmailForm (line 53) | type EmailForm = z.infer<typeof emailSchema>
type PasswordForm (line 54) | type PasswordForm = z.infer<typeof passwordSchema>
type UserPreview (line 56) | interface UserPreview {
type OAuthProvider (line 62) | interface OAuthProvider {
type PasswordBreachData (line 69) | interface PasswordBreachData {
function LoginPage (line 136) | function LoginPage() {
FILE: app/(auth)/oauth-callback/layout.tsx
function OAuthCallbackLayout (line 4) | async function OAuthCallbackLayout({
FILE: app/(auth)/oauth-callback/page.tsx
function OAuthCallback (line 7) | function OAuthCallback() {
FILE: app/(auth)/register/[token]/layout.tsx
function RegisterLayout (line 9) | function RegisterLayout({
FILE: app/(auth)/register/[token]/page.tsx
type RegisterForm (line 28) | type RegisterForm = z.infer<typeof registerSchema>
type InvitationInfo (line 30) | interface InvitationInfo {
function RegisterPage (line 98) | function RegisterPage({params}: { params: Promise<{ token: string }> }) {
FILE: app/(auth)/reset-password/[token]/layout.tsx
function ResetPasswordLayout (line 9) | function ResetPasswordLayout({
FILE: app/(auth)/reset-password/[token]/page.tsx
function ResetPasswordPage (line 5) | async function ResetPasswordPage({
FILE: app/(auth)/reset-password/[token]/reset-password-form.tsx
type ResetPasswordForm (line 36) | type ResetPasswordForm = z.infer<typeof passwordSchema>;
function ResetPasswordForm (line 100) | function ResetPasswordForm({ token }: { token: string }) {
FILE: app/(auth)/setup/layout.tsx
function SetupLayout (line 9) | function SetupLayout({
FILE: app/(auth)/setup/page.tsx
function StepIndicator (line 17) | function StepIndicator() {
function SetupContent (line 40) | function SetupContent() {
function SetupPage (line 89) | function SetupPage() {
FILE: app/(auth)/two-factor/layout.tsx
function TwoFactorLayout (line 9) | function TwoFactorLayout({
FILE: app/(auth)/two-factor/page.tsx
type PasswordForm (line 25) | type PasswordForm = z.infer<typeof passwordSchema>
function TwoFactorPage (line 27) | function TwoFactorPage() {
FILE: app/(email)/unsubscribed/page.tsx
function UnsubscribedContent (line 7) | function UnsubscribedContent(): JSX.Element {
function UnsubscribedPage (line 49) | function UnsubscribedPage(): JSX.Element {
FILE: app/.well-known/acme-challenge/[token]/route.ts
function GET (line 7) | async function GET(
FILE: app/.well-known/acme-challenge/route.ts
function GET (line 7) | async function GET(request: NextRequest) {
FILE: app/api-docs/route.ts
constant GET (line 10) | const GET = ApiReference(config)
FILE: app/api/acme/cancel/[certId]/route.ts
function POST (line 6) | async function POST(
FILE: app/api/acme/issue/route.ts
type IssueRequest (line 11) | interface IssueRequest {
function POST (line 16) | async function POST(request: NextRequest) {
FILE: app/api/acme/renew/[certId]/route.ts
function POST (line 7) | async function POST(
FILE: app/api/acme/revoke/[certId]/route.ts
function POST (line 8) | async function POST(
FILE: app/api/acme/status/[certId]/route.ts
function GET (line 7) | async function GET(
FILE: app/api/acme/verify-dns/route.ts
type VerifyDnsRequest (line 8) | interface VerifyDnsRequest {
function POST (line 12) | async function POST(request: NextRequest) {
FILE: app/api/admin/ai-settings/route.ts
type AISettingsResponse (line 8) | interface AISettingsResponse {
type AISettingsUpdateRequest (line 14) | interface AISettingsUpdateRequest {
type SystemConfigUpdate (line 20) | interface SystemConfigUpdate {
function GET (line 42) | async function GET() {
function POST (line 116) | async function POST(request: Request) {
FILE: app/api/admin/ai-settings/test-key/route.ts
type APIKeyRequest (line 6) | interface APIKeyRequest {
type APIKeyResponse (line 10) | interface APIKeyResponse {
function POST (line 37) | async function POST(request: Request) {
FILE: app/api/admin/analytics/route.ts
function GET (line 89) | async function GET(request: Request) {
FILE: app/api/admin/api-keys/[keyId]/route.ts
function GET (line 51) | async function GET(
function PATCH (line 165) | async function PATCH(
function DELETE (line 305) | async function DELETE(
FILE: app/api/admin/api-keys/route.ts
function GET (line 49) | async function GET() {
function POST (line 154) | async function POST(request: Request) {
FILE: app/api/admin/audit-logs/actions/route.ts
function GET (line 26) | async function GET() {
FILE: app/api/admin/audit-logs/route.ts
type PreservedUserData (line 6) | interface PreservedUserData {
type AuditLogDetails (line 15) | interface AuditLogDetails {
type DatabaseAuditLog (line 49) | interface DatabaseAuditLog {
type ProcessedUserInfo (line 66) | interface ProcessedUserInfo {
type ProcessedAuditLog (line 72) | interface ProcessedAuditLog extends Omit<DatabaseAuditLog, 'user' | 'tar...
type CsvLogEntry (line 80) | interface CsvLogEntry {
type AuditLogsResponse (line 92) | interface AuditLogsResponse {
type WhereClause (line 101) | interface WhereClause {
function getLogUserInfo (line 133) | function getLogUserInfo(log: DatabaseAuditLog, isTarget = false): Proces...
function processLogs (line 162) | function processLogs(logs: DatabaseAuditLog[]): ProcessedAuditLog[] {
function convertLogsToCSV (line 189) | function convertLogsToCSV(logs: DatabaseAuditLog[]): string {
function GET (line 279) | async function GET(request: Request): Promise<NextResponse<AuditLogsResp...
FILE: app/api/admin/config/route.ts
function buildConfigSchema (line 11) | function buildConfigSchema(sponsored: boolean) {
function mapTelemetryStateToString (line 32) | function mapTelemetryStateToString(state: TelemetryState): 'prompt' | 'e...
function mapStringToTelemetryState (line 45) | function mapStringToTelemetryState(state: 'prompt' | 'enabled' | 'disabl...
function GET (line 62) | async function GET() {
function PATCH (line 128) | async function PATCH(request: NextRequest) {
FILE: app/api/admin/config/system-email/route.ts
function GET (line 38) | async function GET() {
function POST (line 125) | async function POST(request: Request) {
function PATCH (line 317) | async function PATCH(request: Request) {
FILE: app/api/admin/dashboard/route.ts
function GET (line 46) | async function GET() {
FILE: app/api/admin/oauth/providers/[id]/route.ts
function PATCH (line 56) | async function PATCH(
function DELETE (line 255) | async function DELETE(
FILE: app/api/admin/oauth/providers/route.ts
function GET (line 59) | async function GET(request: Request) {
function POST (line 141) | async function POST(request: Request) {
FILE: app/api/admin/saml/providers/[id]/route.ts
function PATCH (line 23) | async function PATCH(
function DELETE (line 94) | async function DELETE(
FILE: app/api/admin/saml/providers/route.ts
function GET (line 25) | async function GET(request: Request) {
function POST (line 56) | async function POST(request: Request) {
FILE: app/api/admin/sponsor/route.ts
function GET (line 6) | async function GET() {
function POST (line 49) | async function POST(request: Request) {
function DELETE (line 157) | async function DELETE() {
FILE: app/api/admin/system/slack/route.ts
function GET (line 9) | async function GET() {
function PUT (line 60) | async function PUT(req: Request) {
FILE: app/api/admin/users/[userId]/role/route.ts
function PATCH (line 68) | async function PATCH(
FILE: app/api/admin/users/[userId]/route.ts
type PreservedUserData (line 9) | interface PreservedUserData {
type AuditLogDetails (line 19) | interface AuditLogDetails {
function PATCH (line 64) | async function PATCH(
function DELETE (line 311) | async function DELETE(
FILE: app/api/admin/users/invitations/[id]/route.ts
function DELETE (line 46) | async function DELETE(
FILE: app/api/admin/users/invitations/route.ts
function GET (line 33) | async function GET() {
FILE: app/api/admin/users/route.ts
function GET (line 37) | async function GET() {
function POST (line 124) | async function POST(request: Request) {
FILE: app/api/ai/decrypt/route.ts
type DecryptRequest (line 5) | interface DecryptRequest {
type DecryptResponse (line 9) | interface DecryptResponse {
type DecryptErrorResponse (line 13) | interface DecryptErrorResponse {
function POST (line 34) | async function POST(request: NextRequest): Promise<NextResponse<DecryptR...
FILE: app/api/ai/settings/route.ts
type SystemConfig (line 8) | interface SystemConfig {
type AISettingsResponse (line 14) | interface AISettingsResponse {
type AISettingsErrorResponse (line 20) | interface AISettingsErrorResponse {
function GET (line 38) | async function GET(): Promise<NextResponse<AISettingsResponse | AISettin...
FILE: app/api/analytics/track/route.ts
function POST (line 37) | async function POST(request: Request) {
FILE: app/api/auth/change-password/route.ts
function POST (line 41) | async function POST(request: Request) {
FILE: app/api/auth/cli/generate/route.ts
function POST (line 47) | async function POST(request: NextRequest) {
FILE: app/api/auth/cli/refresh/route.ts
function POST (line 56) | async function POST(request: NextRequest) {
FILE: app/api/auth/cli/token/route.ts
function POST (line 61) | async function POST(request: NextRequest) {
FILE: app/api/auth/connections/route.ts
type ConnectionResponse (line 5) | interface ConnectionResponse {
type SAMLConnectionResponse (line 20) | interface SAMLConnectionResponse {
type UserConnectionsResponse (line 34) | interface UserConnectionsResponse {
function GET (line 98) | async function GET() {
FILE: app/api/auth/forgot-password/route.ts
function POST (line 55) | async function POST(request: NextRequest) {
FILE: app/api/auth/invitation/[token]/route.ts
function GET (line 36) | async function GET(
FILE: app/api/auth/login/route.ts
type RequestBody (line 17) | type RequestBody = z.infer<typeof loginSchema>
function POST (line 72) | async function POST(request: NextRequest) {
function GET (line 388) | async function GET(request: Request) {
FILE: app/api/auth/login/second-factor/route.ts
function POST (line 18) | async function POST(request: Request) {
FILE: app/api/auth/logout/route.ts
function POST (line 17) | async function POST() {
FILE: app/api/auth/me/route.ts
function GET (line 23) | async function GET() {
FILE: app/api/auth/oauth/authorize/[providerName]/route.ts
function GET (line 18) | async function GET(
FILE: app/api/auth/oauth/callback/[providerName]/route.ts
function GET (line 20) | async function GET(
FILE: app/api/auth/oauth/providers/route.ts
function GET (line 26) | async function GET() {
FILE: app/api/auth/passkeys/[id]/route.ts
function DELETE (line 5) | async function DELETE(
FILE: app/api/auth/passkeys/authenticate/options/route.ts
function POST (line 5) | async function POST(request: Request) {
FILE: app/api/auth/passkeys/authenticate/verify/route.ts
function POST (line 9) | async function POST(request: Request) {
FILE: app/api/auth/passkeys/register/options/route.ts
function POST (line 6) | async function POST() {
FILE: app/api/auth/passkeys/register/verify/route.ts
function POST (line 7) | async function POST(request: Request) {
FILE: app/api/auth/passkeys/route.ts
function GET (line 5) | async function GET() {
FILE: app/api/auth/preview/route.ts
function POST (line 15) | async function POST(request: Request) {
FILE: app/api/auth/refresh/route.ts
function POST (line 32) | async function POST(request: Request) {
FILE: app/api/auth/register/route.ts
function POST (line 29) | async function POST(request: NextRequest) {
FILE: app/api/auth/reset-password/[token]/route.ts
function GET (line 47) | async function GET(
function POST (line 119) | async function POST(
FILE: app/api/auth/reset-password/request/route.ts
function POST (line 18) | async function POST() {
FILE: app/api/auth/saml/authorize/[providerName]/route.ts
function GET (line 8) | async function GET(
FILE: app/api/auth/saml/callback/[providerName]/route.ts
function POST (line 9) | async function POST(
FILE: app/api/auth/saml/metadata/[providerName]/route.ts
function GET (line 8) | async function GET(
FILE: app/api/auth/saml/providers/route.ts
function GET (line 8) | async function GET() {
FILE: app/api/auth/security-settings/route.ts
function GET (line 10) | async function GET() {
function PATCH (line 34) | async function PATCH(request: Request) {
FILE: app/api/auth/settings/route.ts
function GET (line 23) | async function GET() {
function PATCH (line 72) | async function PATCH(request: Request) {
FILE: app/api/auth/validate/route.ts
function GET (line 35) | async function GET(request: NextRequest) {
FILE: app/api/avatar/[hash]/route.ts
function GET (line 7) | async function GET(
FILE: app/api/changelog/[projectId]/entries/all/route.ts
constant ITEMS_PER_PAGE (line 4) | const ITEMS_PER_PAGE = 10
type SortOrder (line 7) | type SortOrder = 'asc' | 'desc'
type BaseWhereClause (line 10) | interface BaseWhereClause {
type SearchCondition (line 21) | interface SearchCondition {
type CursorCondition (line 26) | interface CursorCondition {
function GET (line 42) | async function GET(
FILE: app/api/changelog/[projectId]/entries/route.ts
constant ITEMS_PER_PAGE (line 4) | const ITEMS_PER_PAGE = 10
type SortOrder (line 7) | type SortOrder = 'asc' | 'desc'
type BaseWhereClause (line 10) | interface BaseWhereClause {
type SearchCondition (line 21) | interface SearchCondition {
type CursorCondition (line 26) | interface CursorCondition {
function GET (line 83) | async function GET(
FILE: app/api/changelog/entries/[entryId]/route.ts
function getProjectIdFromEntry (line 15) | async function getProjectIdFromEntry(entryId: string) {
function GET (line 63) | async function GET(
function PUT (line 238) | async function PUT(
function DELETE (line 345) | async function DELETE(
FILE: app/api/changelog/requests/[requestId]/route.ts
function PATCH (line 48) | async function PATCH(
FILE: app/api/changelog/requests/route.ts
function GET (line 70) | async function GET(request: Request) {
function POST (line 177) | async function POST(request: Request) {
FILE: app/api/changelog/subscribe/route.ts
type SubscribeRequest (line 6) | interface SubscribeRequest {
type SubscribeResponse (line 14) | interface SubscribeResponse {
type ErrorResponse (line 19) | interface ErrorResponse {
function POST (line 28) | async function POST(request: Request): Promise<NextResponse<SubscribeRes...
FILE: app/api/changelog/unsubscribe/[token]/route.ts
type RouteParams (line 5) | interface RouteParams {
function GET (line 11) | async function GET(
FILE: app/api/changelog/verify-domain/route.ts
function GET (line 4) | async function GET(request: NextRequest) {
FILE: app/api/check-setup/route.ts
function GET (line 8) | async function GET(request: Request) {
FILE: app/api/config/runtime/route.ts
function GET (line 8) | async function GET() {
FILE: app/api/config/timezone/route.ts
function GET (line 6) | async function GET() {
FILE: app/api/cron/ssl-renewal/route.ts
function GET (line 9) | async function GET(request: NextRequest) {
function POST (line 13) | async function POST(request: NextRequest) {
function handleRenewal (line 17) | async function handleRenewal(request: NextRequest) {
FILE: app/api/custom-domains/[domain]/browser-rules/[id]/route.ts
function PATCH (line 8) | async function PATCH(
function DELETE (line 74) | async function DELETE(
FILE: app/api/custom-domains/[domain]/browser-rules/route.ts
function POST (line 8) | async function POST(
FILE: app/api/custom-domains/[domain]/dns-instructions/route.ts
function GET (line 9) | async function GET(
FILE: app/api/custom-domains/[domain]/route.ts
type RouteParams (line 6) | interface RouteParams {
function DELETE (line 12) | async function DELETE(
FILE: app/api/custom-domains/[domain]/ssl/mode/route.ts
function POST (line 9) | async function POST(
FILE: app/api/custom-domains/[domain]/ssl/revoke/route.ts
function DELETE (line 15) | async function DELETE(
FILE: app/api/custom-domains/[domain]/ssl/toggle-https/route.ts
function POST (line 8) | async function POST(
FILE: app/api/custom-domains/[domain]/throttle/route.ts
function POST (line 8) | async function POST(
FILE: app/api/custom-domains/add/route.ts
function POST (line 8) | async function POST(request: NextRequest): Promise<NextResponse<AddDomai...
FILE: app/api/custom-domains/list/route.ts
function GET (line 10) | async function GET(request: NextRequest): Promise<NextResponse<ListDomai...
FILE: app/api/custom-domains/verify/route.ts
function POST (line 8) | async function POST(request: NextRequest): Promise<NextResponse<VerifyDo...
FILE: app/api/dashboard/stats/route.ts
type ProjectPreview (line 5) | interface ProjectPreview {
type DashboardActivity (line 12) | interface DashboardActivity {
type DashboardStats (line 22) | interface DashboardStats {
function GET (line 79) | async function GET(): Promise<NextResponse<DashboardStats | { error: str...
FILE: app/api/domain-check/route.ts
function GET (line 9) | async function GET(request: NextRequest) {
FILE: app/api/health/route.ts
function GET (line 30) | async function GET() {
FILE: app/api/integrations/slack/callback/route.ts
function getAppUrl (line 14) | function getAppUrl(req: NextRequest): string {
function createAbsoluteUrl (line 59) | function createAbsoluteUrl(path: string, baseUrl: string): string {
function GET (line 68) | async function GET(req: NextRequest) {
FILE: app/api/integrations/slack/manifest/route.ts
function getAppUrl (line 6) | function getAppUrl(req: NextRequest): string {
function GET (line 45) | async function GET(req: NextRequest) {
FILE: app/api/integrations/widget/[projectId]/[widgetId]/route.ts
function GET (line 10) | async function GET(
function PUT (line 162) | async function PUT(
function DELETE (line 205) | async function DELETE(
function POST (line 231) | async function POST(
FILE: app/api/integrations/widget/[projectId]/list/route.ts
function GET (line 9) | async function GET(
FILE: app/api/integrations/widget/[projectId]/route.ts
function GET (line 15) | async function GET(
function OPTIONS (line 180) | async function OPTIONS() {
FILE: app/api/internal/agent/version/route.ts
function GET (line 11) | async function GET(request: NextRequest) {
FILE: app/api/internal/cert/[domain]/route.ts
function GET (line 8) | async function GET(
FILE: app/api/internal/ip-config/route.ts
function GET (line 8) | async function GET(request: NextRequest) {
FILE: app/api/projects/[projectId]/analytics/export/route.ts
function GET (line 21) | async function GET(
FILE: app/api/projects/[projectId]/analytics/route.ts
function GET (line 78) | async function GET(
FILE: app/api/projects/[projectId]/api-keys/[keyId]/route.ts
function GET (line 13) | async function GET(
function PATCH (line 69) | async function PATCH(
function DELETE (line 146) | async function DELETE(
FILE: app/api/projects/[projectId]/api-keys/route.ts
function GET (line 16) | async function GET(
function POST (line 115) | async function POST(
FILE: app/api/projects/[projectId]/catch-up/ai-summary/route.ts
type AISummaryRequest (line 30) | type AISummaryRequest = z.infer<typeof aiSummarySchema>;
type CatchUpEntry (line 32) | interface CatchUpEntry {
function POST (line 93) | async function POST(
function createPrompt (line 236) | function createPrompt(projectName: string, entries: CatchUpEntry[], from...
FILE: app/api/projects/[projectId]/catch-up/route.ts
function GET (line 85) | async function GET(
FILE: app/api/projects/[projectId]/changelog/[entryId]/route.ts
function GET (line 56) | async function GET(
function PUT (line 277) | async function PUT(
function PATCH (line 547) | async function PATCH(
function DELETE (line 1013) | async function DELETE(
FILE: app/api/projects/[projectId]/changelog/[entryId]/schedule/approval/route.ts
type ScheduleApprovalBody (line 16) | interface ScheduleApprovalBody {
function POST (line 68) | async function POST(
FILE: app/api/projects/[projectId]/changelog/[entryId]/schedule/route.ts
type ScheduleRequestBody (line 14) | interface ScheduleRequestBody {
function POST (line 77) | async function POST(
function GET (line 349) | async function GET(
FILE: app/api/projects/[projectId]/changelog/route.ts
function GET (line 58) | async function GET(
function POST (line 304) | async function POST(
FILE: app/api/projects/[projectId]/changelog/tags/[tagId]/route.ts
function GET (line 30) | async function GET(
function PATCH (line 112) | async function PATCH(
function DELETE (line 241) | async function DELETE(
FILE: app/api/projects/[projectId]/changelog/tags/route.ts
constant DEFAULT_PAGE_SIZE (line 8) | const DEFAULT_PAGE_SIZE = 20;
constant MAX_PAGE_SIZE (line 9) | const MAX_PAGE_SIZE = 100;
function GET (line 58) | async function GET(
function POST (line 217) | async function POST(
function PATCH (line 382) | async function PATCH(
function DELETE (line 548) | async function DELETE(
FILE: app/api/projects/[projectId]/cli/link/route.ts
function POST (line 60) | async function POST(
FILE: app/api/projects/[projectId]/cli/sync/route.ts
type SyncMetadata (line 18) | interface SyncMetadata {
function POST (line 118) | async function POST(
function processSyncRequest (line 226) | async function processSyncRequest(
FILE: app/api/projects/[projectId]/cli/sync/status/route.ts
function GET (line 57) | async function GET(
FILE: app/api/projects/[projectId]/cli/unlink/route.ts
function POST (line 54) | async function POST(
FILE: app/api/projects/[projectId]/integrations/email/route.ts
function GET (line 24) | async function GET(
function POST (line 79) | async function POST(
FILE: app/api/projects/[projectId]/integrations/email/send/route.ts
function POST (line 31) | async function POST(
FILE: app/api/projects/[projectId]/integrations/email/test/route.ts
function POST (line 24) | async function POST(
FILE: app/api/projects/[projectId]/integrations/github/generate/route.ts
function POST (line 47) | async function POST(
FILE: app/api/projects/[projectId]/integrations/github/route.ts
function GET (line 27) | async function GET(
function POST (line 68) | async function POST(
function DELETE (line 209) | async function DELETE(
FILE: app/api/projects/[projectId]/integrations/github/tags/route.ts
function GET (line 13) | async function GET(
FILE: app/api/projects/[projectId]/integrations/github/test/route.ts
function POST (line 15) | async function POST(
FILE: app/api/projects/[projectId]/integrations/slack/channels/route.ts
function GET (line 10) | async function GET(
FILE: app/api/projects/[projectId]/integrations/slack/route.ts
function GET (line 9) | async function GET(
function PUT (line 60) | async function PUT(
function DELETE (line 120) | async function DELETE(
FILE: app/api/projects/[projectId]/route.ts
function GET (line 46) | async function GET(
function PATCH (line 116) | async function PATCH(
function DELETE (line 157) | async function DELETE(
FILE: app/api/projects/[projectId]/settings/route.ts
function GET (line 39) | async function GET(
function PATCH (line 176) | async function PATCH(
FILE: app/api/projects/[projectId]/versions/route.ts
function GET (line 21) | async function GET(request: Request, context: { params: Promise<{ projec...
FILE: app/api/projects/import/canny/fetch/route.ts
function POST (line 6) | async function POST(request: NextRequest) {
FILE: app/api/projects/import/canny/validate/route.ts
function POST (line 4) | async function POST(request: NextRequest) {
FILE: app/api/projects/import/parse/route.ts
function POST (line 6) | async function POST(request: NextRequest) {
FILE: app/api/projects/import/process/route.ts
type AuthenticatedUser (line 12) | interface AuthenticatedUser {
function POST (line 19) | async function POST(request: NextRequest) {
function GET (line 168) | async function GET(request: NextRequest) {
FILE: app/api/projects/route.ts
function GET (line 37) | async function GET() {
function POST (line 113) | async function POST(request: Request) {
FILE: app/api/requests/route.ts
function GET (line 51) | async function GET() {
FILE: app/api/search/route.ts
constant ENABLE_TAGS (line 9) | const ENABLE_TAGS = false;
function getUserFromRequest (line 29) | async function getUserFromRequest(request: NextRequest) {
function POST (line 69) | async function POST(request: NextRequest) {
function GET (line 156) | async function GET(request: NextRequest) {
FILE: app/api/setup/admin/route.ts
function POST (line 61) | async function POST(request: Request) {
function GET (line 139) | async function GET() {
FILE: app/api/setup/invitations/route.ts
function POST (line 64) | async function POST(request: Request) {
function GET (line 186) | async function GET() {
FILE: app/api/setup/oauth/auto/route.ts
function POST (line 73) | async function POST(request: Request) {
function GET (line 227) | async function GET() {
FILE: app/api/setup/oauth/debug/route.ts
function GET (line 14) | async function GET() {
function POST (line 62) | async function POST(request: Request) {
FILE: app/api/setup/oauth/route.ts
function POST (line 21) | async function POST(request: Request) {
FILE: app/api/setup/route.ts
function GET (line 11) | async function GET() {
FILE: app/api/setup/settings/route.ts
function POST (line 137) | async function POST(request: Request) {
function GET (line 226) | async function GET() {
FILE: app/api/setup/status/route.ts
function GET (line 26) | async function GET() {
FILE: app/api/setup/types.ts
type SetupProgress (line 13) | type SetupProgress = z.infer<typeof setupProgressSchema>;
FILE: app/api/subscribers/[subscriberId]/route.ts
function PATCH (line 19) | async function PATCH(
function DELETE (line 118) | async function DELETE(
FILE: app/api/subscribers/generate-mock/route.ts
function POST (line 12) | async function POST(request: Request) {
FILE: app/api/subscribers/route.ts
function POST (line 11) | async function POST(request: Request) {
function GET (line 116) | async function GET(request: Request) {
FILE: app/api/system/agent-version/route.ts
function GET (line 9) | async function GET() {
FILE: app/api/system/easypanel/status/route.ts
type EasypanelStatusResponse (line 5) | interface EasypanelStatusResponse {
function GET (line 42) | async function GET(request: NextRequest) {
FILE: app/api/system/perform-update/route.ts
type PerformUpdateRequest (line 8) | interface PerformUpdateRequest {
function POST (line 49) | async function POST(request: NextRequest) {
FILE: app/api/system/update-status/route.ts
function GET (line 30) | async function GET(request: NextRequest) {
FILE: app/api/system/version/route.ts
function GET (line 4) | async function GET(): Promise<NextResponse> {
FILE: app/api/telemetry/config/route.ts
function GET (line 5) | async function GET() {
function POST (line 18) | async function POST(request: NextRequest) {
FILE: app/api/telemetry/debug/route.ts
function GET (line 5) | async function GET() {
function POST (line 39) | async function POST(request: NextRequest) {
FILE: app/changelog/[projectId]/[entryId]/page.tsx
type ChangelogEntry (line 15) | interface ChangelogEntry {
type EntryResponse (line 32) | interface EntryResponse {
type EntryPageProps (line 41) | type EntryPageProps = {
function EntryPage (line 45) | function EntryPage({params}: EntryPageProps) {
FILE: app/changelog/[projectId]/changelog-view.tsx
type ChangelogEntry (line 12) | interface ChangelogEntry {
type ChangelogData (line 21) | interface ChangelogData {
type ChangelogViewProps (line 29) | interface ChangelogViewProps {
function useEntryViewTracking (line 34) | function useEntryViewTracking(entryId: string, projectId: string) {
function ChangelogEntry (line 80) | function ChangelogEntry({ entry, projectId, timezone }: { entry: Changel...
function ChangelogView (line 150) | function ChangelogView({ data }: ChangelogViewProps) {
FILE: app/changelog/[projectId]/layout.tsx
type ChangelogLayoutProps (line 7) | interface ChangelogLayoutProps {
function generateMetadata (line 12) | async function generateMetadata(
function ChangelogLayout (line 27) | async function ChangelogLayout({ children, params }: ChangelogLayoutProp...
FILE: app/changelog/[projectId]/loading.tsx
function Loading (line 5) | function Loading() {
FILE: app/changelog/[projectId]/not-found.tsx
function NotFound (line 5) | function NotFound() {
FILE: app/changelog/[projectId]/page.tsx
type ChangelogResponse (line 20) | interface ChangelogResponse {
type ChangelogPageProps (line 35) | type ChangelogPageProps = {
function getInitialData (line 39) | async function getInitialData(projectId: string): Promise<ChangelogRespo...
function generateMetadata (line 53) | async function generateMetadata(
function ChangelogSkeleton (line 89) | function ChangelogSkeleton() {
function ChangelogPage (line 110) | async function ChangelogPage({ params }: ChangelogPageProps) {
FILE: app/changelog/[projectId]/rss.xml/route.ts
function GET (line 5) | async function GET(
FILE: app/changelog/custom-domain/[domain]/[entryId]/EntryContent.tsx
type ChangelogEntry (line 13) | interface ChangelogEntry {
type EntryContentProps (line 29) | interface EntryContentProps {
function EntryContent (line 36) | function EntryContent({ domain, projectId, projectName, entry }: EntryCo...
FILE: app/changelog/custom-domain/[domain]/[entryId]/page.tsx
type ChangelogEntry (line 6) | interface ChangelogEntry {
type EntryResponse (line 22) | interface EntryResponse {
type EntryPageProps (line 31) | type EntryPageProps = {
function getProjectFromDomain (line 35) | async function getProjectFromDomain(domain: string) {
function getEntry (line 71) | async function getEntry(changelogId: string, entryId: string): Promise<C...
function generateMetadata (line 112) | async function generateMetadata({params}: EntryPageProps): Promise<Metad...
function CustomDomainEntryPage (line 143) | async function CustomDomainEntryPage({params}: EntryPageProps) {
FILE: app/changelog/custom-domain/[domain]/layout.tsx
type CustomDomainLayoutProps (line 6) | interface CustomDomainLayoutProps {
function generateMetadata (line 14) | async function generateMetadata(
function CustomDomainLayout (line 38) | async function CustomDomainLayout({children, params}: CustomDomainLayout...
FILE: app/changelog/custom-domain/[domain]/page.tsx
type ChangelogResponse (line 23) | interface ChangelogResponse {
type CustomDomainPageProps (line 38) | type CustomDomainPageProps = {
function getInitialData (line 45) | async function getInitialData(projectId: string): Promise<ChangelogRespo...
function generateMetadata (line 59) | async function generateMetadata(
function ChangelogSkeleton (line 120) | function ChangelogSkeleton() {
function DomainVerificationPage (line 142) | function DomainVerificationPage({domain}: { domain: string }) {
function CustomDomainPage (line 213) | async function CustomDomainPage({params}: CustomDomainPageProps) {
FILE: app/changelog/custom-domain/[domain]/rss.xml/route.ts
function GET (line 6) | async function GET(
FILE: app/changelog/custom-domain/[domain]/unsubscribed/page.tsx
type CustomDomainUnsubscribedProps (line 5) | interface CustomDomainUnsubscribedProps {
function CustomDomainUnsubscribedPage (line 14) | async function CustomDomainUnsubscribedPage({
function generateMetadata (line 69) | async function generateMetadata({ params }: CustomDomainUnsubscribedProp...
FILE: app/cli/auth/page.tsx
type AuthState (line 10) | interface AuthState {
function CLIAuthContent (line 24) | function CLIAuthContent() {
function CLIAuthLoading (line 257) | function CLIAuthLoading() {
function CLIAuthPage (line 283) | function CLIAuthPage() {
FILE: app/dashboard/admin/about/page.tsx
function AboutPage (line 16) | function AboutPage() {
FILE: app/dashboard/admin/ai-settings/page.tsx
type AISettings (line 45) | interface AISettings {
function AISettingsPage (line 51) | function AISettingsPage() {
FILE: app/dashboard/admin/analytics/page.tsx
function AdminAnalyticsPage (line 56) | function AdminAnalyticsPage() {
FILE: app/dashboard/admin/api-keys/page.tsx
type ApiKey (line 55) | interface ApiKey {
type RenameDialogProps (line 71) | interface RenameDialogProps {
function RenameDialog (line 78) | function RenameDialog({
function NewKeyAlert (line 145) | function NewKeyAlert({ keyData, onClose, onCopy }: { keyData: { key: str...
function ApiKeysPage (line 192) | function ApiKeysPage() {
FILE: app/dashboard/admin/audit-logs/page.tsx
type PreservedUserData (line 56) | interface PreservedUserData {
type AuditLogDetails (line 65) | interface AuditLogDetails {
type UserInfo (line 71) | interface UserInfo {
type AuditLog (line 77) | interface AuditLog {
type AuditLogsResponse (line 92) | interface AuditLogsResponse {
type AuditAction (line 98) | interface AuditAction {
type FilterState (line 103) | interface FilterState {
type UserDisplayProps (line 115) | interface UserDisplayProps {
function UserDisplay (line 122) | function UserDisplay({ user, showEmail = true, className, size = 'sm' }:...
function useDebounce (line 215) | function useDebounce<T>(value: T, delay: number = 500): T {
function AuditLogsPage (line 231) | function AuditLogsPage() {
FILE: app/dashboard/admin/domains/page.tsx
type DomainStats (line 55) | interface DomainStats {
function AdminDomainsPage (line 63) | function AdminDomainsPage() {
FILE: app/dashboard/admin/layout.tsx
function AdminLayout (line 108) | function AdminLayout({ children }: { children: React.ReactNode }) {
FILE: app/dashboard/admin/oauth/page.tsx
type ProviderFormValues (line 113) | type ProviderFormValues = z.infer<typeof providerFormSchema>;
constant PROVIDER_PRESETS (line 116) | const PROVIDER_PRESETS = {
type OAuthProvider (line 169) | interface OAuthProvider {
type ProviderApiData (line 186) | interface ProviderApiData {
type SAMLProvider (line 315) | interface SAMLProvider {
type SAMLProviderFormValues (line 347) | type SAMLProviderFormValues = z.infer<typeof samlProviderSchema>;
function OAuthProvidersPage (line 349) | function OAuthProvidersPage() {
type ProvidersListProps (line 2505) | interface ProvidersListProps {
type SAMLProvidersListProps (line 2713) | interface SAMLProvidersListProps {
FILE: app/dashboard/admin/page.tsx
type AdminDashboardData (line 24) | interface AdminDashboardData {
function AdminOverviewPage (line 44) | function AdminOverviewPage() {
FILE: app/dashboard/admin/requests/page.tsx
function AdminRequestsPage (line 3) | function AdminRequestsPage() {
FILE: app/dashboard/admin/system/email/page.tsx
type SystemEmailConfig (line 67) | type SystemEmailConfig = z.infer<typeof systemEmailSchema>;
function SystemEmailConfigPage (line 69) | function SystemEmailConfigPage() {
FILE: app/dashboard/admin/system/page.tsx
function buildConfigSchema (line 77) | function buildConfigSchema(sponsored: boolean) {
type SystemConfig (line 93) | type SystemConfig = {
function SystemConfigPage (line 111) | function SystemConfigPage() {
FILE: app/dashboard/admin/system/slack/page.tsx
type SlackOAuthConfig (line 57) | type SlackOAuthConfig = z.infer<typeof slackOAuthSchema>;
function SystemSlackConfigPage (line 59) | function SystemSlackConfigPage() {
FILE: app/dashboard/admin/system/templates/page.tsx
type DateTemplate (line 31) | interface DateTemplate {
type SystemConfig (line 36) | interface SystemConfig {
constant TEMPLATE_VARIABLES (line 43) | const TEMPLATE_VARIABLES = [
constant DEFAULT_TEMPLATES (line 64) | const DEFAULT_TEMPLATES: DateTemplate[] = [
function resolvePreview (line 70) | function resolvePreview(format: string, timezone: string): string {
function VersionTemplatesPage (line 111) | function VersionTemplatesPage() {
FILE: app/dashboard/admin/users/page.tsx
type UserData (line 66) | interface UserData {
type InvitationLink (line 75) | interface InvitationLink {
function UsersPage (line 85) | function UsersPage() {
FILE: app/dashboard/bookmarks/page.tsx
type Project (line 41) | interface Project {
type EnhancedBookmark (line 47) | interface EnhancedBookmark extends BookmarkedItem {
type SortOption (line 57) | type SortOption = 'recent' | 'alphabetical' | 'project'
function BookmarksPage (line 59) | function BookmarksPage() {
type BookmarkRowProps (line 442) | interface BookmarkRowProps {
function BookmarkRow (line 447) | function BookmarkRow({bookmark, onRemove}: BookmarkRowProps) {
FILE: app/dashboard/layout.tsx
constant NAV_SECTIONS (line 37) | const NAV_SECTIONS: NavSection[] = [
function LoadingScreen (line 152) | function LoadingScreen() {
function DashboardLayout (line 164) | function DashboardLayout({
FILE: app/dashboard/page.tsx
type StatsCardProps (line 170) | interface StatsCardProps {
type QuickActionProps (line 199) | interface QuickActionProps {
function DashboardPage (line 233) | function DashboardPage() {
FILE: app/dashboard/projects/[projectId]/analytics/page.tsx
function ProjectAnalyticsPage (line 54) | function ProjectAnalyticsPage() {
FILE: app/dashboard/projects/[projectId]/api-keys/page.tsx
type ApiKey (line 68) | interface ApiKey {
type RenameDialogProps (line 87) | interface RenameDialogProps {
function RenameDialog (line 94) | function RenameDialog({
function NewKeyAlert (line 161) | function NewKeyAlert({ keyData, onClose, onCopy }: { keyData: { key: str...
function ProjectApiKeysPage (line 208) | function ProjectApiKeysPage() {
FILE: app/dashboard/projects/[projectId]/catch-up/page.tsx
type CatchUpPageProps (line 18) | interface CatchUpPageProps {
type ProjectSummaryResponse (line 22) | interface ProjectSummaryResponse {
function CatchUpPage (line 41) | function CatchUpPage({params}: CatchUpPageProps) {
FILE: app/dashboard/projects/[projectId]/changelog/[entryId]/page.tsx
type ChangelogPageProps (line 6) | interface ChangelogPageProps {
function ChangelogEntryPage (line 13) | function ChangelogEntryPage({ params }: ChangelogPageProps) {
FILE: app/dashboard/projects/[projectId]/changelog/new/page.tsx
type NewChangelogPageProps (line 8) | interface NewChangelogPageProps {
function NewChangelogEntryPage (line 14) | function NewChangelogEntryPage({ params }: NewChangelogPageProps) {
FILE: app/dashboard/projects/[projectId]/changelog/page.tsx
type ChangelogPageProps (line 18) | interface ChangelogPageProps {
type ViewMode (line 22) | type ViewMode = 'grid' | 'list'
type SortOrder (line 23) | type SortOrder = 'newest' | 'oldest' | 'version'
function ChangelogPage (line 25) | function ChangelogPage({params}: ChangelogPageProps) {
FILE: app/dashboard/projects/[projectId]/domains/[domain]/client.tsx
type DomainSettingsClientProps (line 32) | interface DomainSettingsClientProps {
function DomainSettingsClient (line 37) | function DomainSettingsClient({ projectId, domain: domainName }: DomainS...
FILE: app/dashboard/projects/[projectId]/domains/[domain]/page.tsx
type DomainSettingsPageProps (line 9) | interface DomainSettingsPageProps {
function DomainSettingsPage (line 16) | async function DomainSettingsPage({ params }: DomainSettingsPageProps) {
FILE: app/dashboard/projects/[projectId]/domains/components/DomainCard.tsx
type DomainCardProps (line 31) | interface DomainCardProps {
function DomainCard (line 41) | function DomainCard({
FILE: app/dashboard/projects/[projectId]/domains/components/InlineSSLSetup.tsx
type InlineSSLSetupProps (line 22) | interface InlineSSLSetupProps {
type SSLStep (line 29) | type SSLStep = 'choose' | 'http01-progress' | 'dns01-instructions' | 'dn...
function InlineSSLSetup (line 31) | function InlineSSLSetup({
FILE: app/dashboard/projects/[projectId]/domains/components/SSLCertificateCard.tsx
type SSLCertificateCardProps (line 33) | interface SSLCertificateCardProps {
function SSLCertificateCard (line 40) | function SSLCertificateCard({ domain, onUpdate, onError, onSuccess }: SS...
FILE: app/dashboard/projects/[projectId]/domains/components/SSLCertificateWizard.tsx
type SSLCertificateWizardProps (line 30) | interface SSLCertificateWizardProps {
type Step (line 40) | type Step = 'select-method' | 'http01-progress' | 'dns01-instructions' |...
function SSLCertificateWizard (line 42) | function SSLCertificateWizard({
FILE: app/dashboard/projects/[projectId]/domains/components/SSLManagement.tsx
type SSLManagementProps (line 34) | interface SSLManagementProps {
type SSLMode (line 41) | type SSLMode = 'LETS_ENCRYPT' | 'EXTERNAL' | 'NONE'
type SetupStep (line 42) | type SetupStep = 'choose-mode' | 'choose-method' | 'http01-progress' | '...
function SSLManagement (line 44) | function SSLManagement({
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/ExternalSSLManagement.tsx
type BrowserRule (line 32) | interface BrowserRule {
type ThrottleConfig (line 39) | interface ThrottleConfig {
type ExternalSSLManagementProps (line 45) | interface ExternalSSLManagementProps {
function ExternalSSLManagement (line 55) | function ExternalSSLManagement({
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/SSLCertificateActions.tsx
type SSLCertificateActionsProps (line 10) | interface SSLCertificateActionsProps {
function SSLCertificateActions (line 20) | function SSLCertificateActions({
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/SSLCertificateStatus.tsx
type SSLCertificateStatusProps (line 10) | interface SSLCertificateStatusProps {
function SSLCertificateStatus (line 17) | function SSLCertificateStatus({
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/SSLDNSInstructions.tsx
type SSLDNSInstructionsProps (line 9) | interface SSLDNSInstructionsProps {
function SSLDNSInstructions (line 19) | function SSLDNSInstructions({
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/SSLModeSelector.tsx
type SSLModeSelectorProps (line 6) | interface SSLModeSelectorProps {
function SSLModeSelector (line 10) | function SSLModeSelector({ onSelectMode }: SSLModeSelectorProps) {
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/SSLVerificationMethod.tsx
type SSLVerificationMethodProps (line 7) | interface SSLVerificationMethodProps {
function SSLVerificationMethod (line 13) | function SSLVerificationMethod({
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/SSLVerificationProgress.tsx
type SSLVerificationProgressProps (line 7) | interface SSLVerificationProgressProps {
function SSLVerificationProgress (line 12) | function SSLVerificationProgress({ type, onCancel }: SSLVerificationProg...
FILE: app/dashboard/projects/[projectId]/domains/components/ssl/index.tsx
type SSLManagementProps (line 20) | interface SSLManagementProps {
type FlowStep (line 27) | type FlowStep = 'mode-select' | 'method-select' | 'dns-instructions' | '...
function SSLManagement (line 29) | function SSLManagement({ domain, onUpdate, onError, onSuccess }: SSLMana...
FILE: app/dashboard/projects/[projectId]/domains/page.tsx
type ProjectDomainSettingsProps (line 27) | interface ProjectDomainSettingsProps {
function ProjectDomainSettings (line 68) | function ProjectDomainSettings({ params }: ProjectDomainSettingsProps) {
FILE: app/dashboard/projects/[projectId]/import/page.tsx
type Project (line 35) | interface Project {
type ImportPageProps (line 53) | interface ImportPageProps {
function ImportPage (line 63) | function ImportPage({params}: ImportPageProps) {
FILE: app/dashboard/projects/[projectId]/integrations/email/page.tsx
type EmailConfig (line 66) | interface EmailConfig {
type ChangelogEntry (line 82) | interface ChangelogEntry {
type Project (line 93) | interface Project {
type FormValues (line 127) | type FormValues = z.infer<typeof formSchema>;
type TestEmailFormValues (line 134) | type TestEmailFormValues = z.infer<typeof testEmailSchema>;
type SendEmailFormValues (line 146) | type SendEmailFormValues = z.infer<typeof sendEmailSchema>;
function EmailIntegrationPage (line 148) | function EmailIntegrationPage() {
FILE: app/dashboard/projects/[projectId]/integrations/email/subscribers/page.tsx
type SubscriberFormValues (line 91) | type SubscriberFormValues = z.infer<typeof subscriberSchema>;
type UpdateSubscriberFormValues (line 99) | type UpdateSubscriberFormValues = z.infer<typeof updateSubscriberSchema>;
type Subscriber (line 101) | type Subscriber = {
function SubscribersPage (line 110) | function SubscribersPage() {
FILE: app/dashboard/projects/[projectId]/integrations/github/page.tsx
type Project (line 41) | interface Project {
type GitHubIntegration (line 48) | interface GitHubIntegration {
type RepositoryStats (line 62) | interface RepositoryStats {
function GitHubIntegrationPage (line 75) | function GitHubIntegrationPage() {
FILE: app/dashboard/projects/[projectId]/integrations/slack/page.tsx
type SlackIntegration (line 62) | interface SlackIntegration {
type Project (line 81) | interface Project {
type FormValues (line 94) | type FormValues = z.infer<typeof formSchema>;
function SlackIntegrationPage (line 96) | function SlackIntegrationPage() {
FILE: app/dashboard/projects/[projectId]/integrations/widget/[widgetId]/page.tsx
type WidgetEditPageProps (line 5) | interface WidgetEditPageProps {
function WidgetEditPage (line 9) | async function WidgetEditPage({params}: WidgetEditPageProps) {
FILE: app/dashboard/projects/[projectId]/integrations/widget/[widgetId]/widget-editor.tsx
type WidgetEditorProps (line 97) | interface WidgetEditorProps {
function WidgetEditor (line 102) | function WidgetEditor({widget: initialWidget, projectId}: WidgetEditorPr...
FILE: app/dashboard/projects/[projectId]/integrations/widget/page.tsx
type WidgetPageProps (line 4) | interface WidgetPageProps {
function WidgetPage (line 8) | async function WidgetPage({params}: WidgetPageProps) {
FILE: app/dashboard/projects/[projectId]/integrations/widget/widget-config.tsx
constant MIN_ENTRIES (line 99) | const MIN_ENTRIES = 1;
constant MAX_ENTRIES (line 100) | const MAX_ENTRIES = 10;
constant DEFAULT_ENTRIES (line 101) | const DEFAULT_ENTRIES = 3;
type WidgetConfig (line 104) | interface WidgetConfig {
type CodeExample (line 114) | interface CodeExample {
constant CODE_EXAMPLES (line 120) | const CODE_EXAMPLES: CodeExample[] = [
type CodeInstallationProps (line 349) | interface CodeInstallationProps {
function CodeInstallation (line 355) | function CodeInstallation({ config, projectId, codeExamples }: CodeInsta...
function WidgetConfigContent (line 459) | function WidgetConfigContent({projectId}: { projectId: string }) {
FILE: app/dashboard/projects/[projectId]/integrations/widget/widget-list.tsx
type WidgetListProps (line 29) | interface WidgetListProps {
function WidgetList (line 35) | function WidgetList({ projectId, initialWidgets, project }: WidgetListPr...
FILE: app/dashboard/projects/[projectId]/layout.tsx
function ProjectLayout (line 9) | function ProjectLayout({
FILE: app/dashboard/projects/[projectId]/page.tsx
type Project (line 45) | interface Project {
type User (line 67) | interface User {
type ProjectPageProps (line 72) | interface ProjectPageProps {
type QuickStatsCardProps (line 90) | interface QuickStatsCardProps {
function QuickStatsCard (line 102) | function QuickStatsCard({
type ActionCardProps (line 142) | interface ActionCardProps {
function ActionCard (line 154) | function ActionCard({
type RecentEntryCardProps (line 229) | interface RecentEntryCardProps {
function RecentEntryCard (line 241) | function RecentEntryCard({entry, projectId}: RecentEntryCardProps) {
function ProjectPage (line 295) | function ProjectPage({params}: ProjectPageProps) {
FILE: app/dashboard/projects/[projectId]/settings/page.tsx
type ProjectSettingsPageProps (line 48) | interface ProjectSettingsPageProps {
type ProjectSettings (line 52) | interface ProjectSettings {
function ProjectSettingsPage (line 62) | function ProjectSettingsPage({params}: ProjectSettingsPageProps) {
FILE: app/dashboard/projects/new/page.tsx
function NewProjectPage (line 21) | function NewProjectPage() {
FILE: app/dashboard/projects/page.tsx
type PageProps (line 8) | interface PageProps {
function SettingsPageWrapper (line 12) | function SettingsPageWrapper({ }: PageProps) {
FILE: app/dashboard/providers.tsx
function Providers (line 6) | function Providers({ children }: { children: React.ReactNode }) {
FILE: app/dashboard/requests/page.tsx
type RequestData (line 35) | interface RequestData {
function RequestsPage (line 60) | function RequestsPage() {
FILE: app/dashboard/settings/page.tsx
type FormState (line 36) | interface FormState {
type TimezoneConfig (line 42) | interface TimezoneConfig {
type OAuthProvider (line 48) | interface OAuthProvider {
type OAuthConnection (line 55) | interface OAuthConnection {
type SAMLProvider (line 65) | interface SAMLProvider {
type SAMLConnection (line 72) | interface SAMLConnection {
type SsoData (line 81) | interface SsoData {
function SettingsPage (line 88) | function SettingsPage() {
function ThemeButton (line 600) | function ThemeButton({
FILE: app/layout.tsx
function RootLayout (line 18) | function RootLayout({
FILE: app/page.tsx
type ChangelogType (line 37) | type ChangelogType = "feature" | "bugfix" | "improvement" | "breaking" |...
type ChangelogEntry (line 39) | type ChangelogEntry = {
constant CHANGE_TYPES (line 49) | const CHANGE_TYPES = [
constant CHANGELOG_ENTRIES (line 61) | const CHANGELOG_ENTRIES: ChangelogEntry[] = [
constant DAILY_CHALLENGE_ENTRIES (line 116) | const DAILY_CHALLENGE_ENTRIES: ChangelogEntry[] = [
function Home (line 125) | function Home() {
function getTypeDescription (line 1221) | function getTypeDescription(type: string): string {
FILE: app/startup.ts
type EnvironmentValidationError (line 13) | interface EnvironmentValidationError extends Error {
function createEnvironmentError (line 18) | function createEnvironmentError(missingVars: string[]): EnvironmentValid...
function checkRequirements (line 27) | function checkRequirements(): void {
function cleanup (line 50) | async function cleanup(): Promise<void> {
function launchGuide (line 63) | function launchGuide(missing: string[]): void {
function startBackgroundServices (line 87) | async function startBackgroundServices(): Promise<void> {
FILE: components/CommandPalette.tsx
type SearchResult (line 18) | interface SearchResult {
type CommandPaletteProps (line 35) | interface CommandPaletteProps {
constant ENABLE_TAGS (line 40) | const ENABLE_TAGS = false;
function ChangelogCommandPalette (line 42) | function ChangelogCommandPalette({isOpen, onClose}: CommandPaletteProps) {
FILE: components/DinoGame.tsx
type DinoGameProps (line 10) | interface DinoGameProps {
type Dino (line 15) | interface Dino {
type Obstacle (line 27) | interface Obstacle {
type Cloud (line 35) | interface Cloud {
type GameState (line 43) | interface GameState {
FILE: components/MarkdownEditor.tsx
type DOMPurifyInterface (line 48) | interface DOMPurifyInterface {
type MarkdownFeatures (line 68) | interface MarkdownFeatures {
type RenderMarkdownProps (line 87) | interface RenderMarkdownProps {
constant ALLOWED_TAGS (line 112) | const ALLOWED_TAGS = [
constant ALLOWED_ATTR (line 119) | const ALLOWED_ATTR = [
type SelectionAction (line 516) | interface SelectionAction {
type MarkdownAction (line 524) | interface MarkdownAction {
type HeadingAction (line 533) | interface HeadingAction extends SelectionAction {
type EditorProps (line 537) | interface EditorProps {
constant HEADING_ACTIONS (line 550) | const HEADING_ACTIONS: HeadingAction[] = [
constant SELECTION_ACTIONS (line 593) | const SELECTION_ACTIONS: SelectionAction[] = [
constant DEFAULT_ACTIONS (line 642) | const DEFAULT_ACTIONS: MarkdownAction[] = [
FILE: components/UpdateStatus.tsx
type UpdateStatusProps (line 13) | interface UpdateStatusProps {
type UpdateState (line 22) | type UpdateState = 'idle' | 'checking' | 'available' | 'no-update' | 'up...
FILE: components/admin/api/PermissionsModal.tsx
type PermissionsModalProps (line 20) | interface PermissionsModalProps {
function PermissionsModal (line 27) | function PermissionsModal({
FILE: components/admin/api/Rename.tsx
type RenameDialogProps (line 14) | interface RenameDialogProps {
function RenameDialog (line 21) | function RenameDialog({
FILE: components/admin/audit-logs/VirtualizedList.tsx
type AuditLog (line 12) | interface AuditLog {
type VirtualizedListProps (line 29) | interface VirtualizedListProps {
FILE: components/admin/requests/Management.tsx
type RequestType (line 58) | type RequestType = 'DELETE_PROJECT' | 'DELETE_TAG' | 'DELETE_ENTRY' | 'A...
constant REQUEST_TYPES (line 60) | const REQUEST_TYPES: Record<RequestType, {
type ChangelogRequest (line 281) | interface ChangelogRequest {
type RequestStatus (line 302) | type RequestStatus = 'APPROVED' | 'REJECTED';
type ProcessingRequest (line 304) | type ProcessingRequest = {
function RequestManagement (line 309) | function RequestManagement() {
FILE: components/analytics/analytics-chart.tsx
type AnalyticsChartProps (line 19) | interface AnalyticsChartProps {
type TooltipPayload (line 25) | interface TooltipPayload {
type CustomTooltipProps (line 37) | interface CustomTooltipProps {
function AnalyticsChart (line 42) | function AnalyticsChart({
FILE: components/analytics/analytics-metric-card.tsx
type AnalyticsMetricCardProps (line 9) | interface AnalyticsMetricCardProps {
function AnalyticsMetricCard (line 20) | function AnalyticsMetricCard({
FILE: components/analytics/country-analytics-table.tsx
type CountryAnalyticsTableProps (line 24) | interface CountryAnalyticsTableProps {
function CountryAnalyticsTable (line 74) | function CountryAnalyticsTable({countries}: CountryAnalyticsTableProps) {
FILE: components/analytics/entry-analytics-table.tsx
type EntryAnalyticsTableProps (line 26) | interface EntryAnalyticsTableProps {
function EntryAnalyticsTable (line 31) | function EntryAnalyticsTable({entries, projectId}: EntryAnalyticsTablePr...
FILE: components/analytics/project-analytics-table.tsx
type ProjectAnalyticsTableProps (line 27) | interface ProjectAnalyticsTableProps {
function ProjectAnalyticsTable (line 31) | function ProjectAnalyticsTable({ projects }: ProjectAnalyticsTableProps) {
FILE: components/analytics/referrer-analytics-table.tsx
type ReferrerAnalyticsTableProps (line 26) | interface ReferrerAnalyticsTableProps {
function ReferrerAnalyticsTable (line 87) | function ReferrerAnalyticsTable({referrers}: ReferrerAnalyticsTableProps) {
FILE: components/changelog/ButtonGroup.tsx
type ButtonGroupProps (line 8) | interface ButtonGroupProps {
function ButtonGroup (line 12) | function ButtonGroup({ projectId }: ButtonGroupProps) {
FILE: components/changelog/ChangelogActionRequest.tsx
type ActionType (line 39) | type ActionType = 'PUBLISH' | 'UNPUBLISH' | 'DELETE' | 'ALLOW_SCHEDULE';
type RequestType (line 40) | type RequestType = 'ALLOW_PUBLISH' | 'DELETE_ENTRY' | 'ALLOW_SCHEDULE';
type ButtonVariant (line 41) | type ButtonVariant = 'default' | 'destructive' | 'outline' | 'ghost' | '...
type ButtonSize (line 42) | type ButtonSize = 'default' | 'sm' | 'lg' | 'icon';
type RecipientType (line 43) | type RecipientType = 'SUBSCRIBERS' | 'MANUAL' | 'BOTH';
type SubscriptionType (line 44) | type SubscriptionType = 'ALL_UPDATES' | 'MAJOR_ONLY' | 'DIGEST_ONLY';
type PendingRequest (line 46) | interface PendingRequest {
type ChangelogActionRequestProps (line 57) | interface ChangelogActionRequestProps {
function ChangelogActionRequest (line 70) | function ChangelogActionRequest({
FILE: components/changelog/ChangelogEditor.tsx
type Tag (line 18) | interface Tag {
type ProjectResponse (line 23) | interface ProjectResponse {
type EntryResponse (line 31) | interface EntryResponse {
type TagsResponse (line 42) | interface TagsResponse {
type AISystemSettings (line 51) | interface AISystemSettings {
type SaveErrorDetails (line 57) | interface SaveErrorDetails {
type EditorState (line 65) | interface EditorState {
type EditorStatus (line 75) | interface EditorStatus {
type SaveError (line 84) | interface SaveError extends Error {
type WWCProtocolData (line 90) | interface WWCProtocolData {
type WWCProtocolState (line 102) | interface WWCProtocolState {
constant ITEMS_PER_PAGE (line 111) | const ITEMS_PER_PAGE = 20;
constant CACHE_TIME (line 112) | const CACHE_TIME = 1000 * 60 * 5;
constant DEBOUNCE_TIME (line 113) | const DEBOUNCE_TIME = 1000;
constant DEBOUNCE_TIME_MEDIUM (line 114) | const DEBOUNCE_TIME_MEDIUM = 2000;
constant DEBOUNCE_TIME_LARGE (line 115) | const DEBOUNCE_TIME_LARGE = 5000;
constant MAX_RETRY_ATTEMPTS (line 116) | const MAX_RETRY_ATTEMPTS = 3;
constant RETRY_DELAY (line 117) | const RETRY_DELAY = 2000;
type EnhancedEditorHeaderProps (line 129) | interface EnhancedEditorHeaderProps extends React.ComponentProps<typeof ...
type ChangelogEditorProps (line 191) | interface ChangelogEditorProps {
function ChangelogEditor (line 201) | function ChangelogEditor({
FILE: components/changelog/ChangelogEntries.tsx
type ChangelogEntriesProps (line 53) | interface ChangelogEntriesProps {
type ChangelogApiResponse (line 58) | interface ChangelogApiResponse {
type TagWithColor (line 85) | interface TagWithColor {
type SortOption (line 129) | type SortOption = 'newest' | 'oldest';
type FilterState (line 130) | type FilterState = {
function ChangelogEntries (line 136) | function ChangelogEntries({projectId}: ChangelogEntriesProps) {
FILE: components/changelog/RequestHandler.tsx
type ActionType (line 21) | type ActionType = 'DELETE_PROJECT' | 'DELETE_TAG' | 'DELETE_ENTRY'
type ChangelogRequest (line 23) | interface ChangelogRequest {
type DestructiveActionRequestProps (line 32) | interface DestructiveActionRequestProps {
function DestructiveActionRequest (line 40) | function DestructiveActionRequest({
FILE: components/changelog/ScrollToTopButton.tsx
function useScrollVisibility (line 7) | function useScrollVisibility() {
function ScrollToTopButton (line 28) | function ScrollToTopButton() {
FILE: components/changelog/ShareButton.tsx
type ShareButtonProps (line 14) | interface ShareButtonProps {
function ShareButton (line 20) | function ShareButton({
FILE: components/changelog/ThemeToggle.tsx
type ThemeToggleProps (line 7) | interface ThemeToggleProps {
function ThemeToggle (line 16) | function ThemeToggle({ projectId }: ThemeToggleProps) {
FILE: components/changelog/WidgetPreview.tsx
type WidgetPreviewProps (line 7) | interface WidgetPreviewProps {
constant FAKE_ENTRIES (line 13) | const FAKE_ENTRIES = [
function WidgetPreview (line 210) | function WidgetPreview({ config }: { config: WidgetConfig }) {
FILE: components/changelog/editor/AITitleGenerator.tsx
type AITitleGeneratorProps (line 11) | interface AITitleGeneratorProps {
type TitleSuggestion (line 18) | interface TitleSuggestion {
function AITitleGenerator (line 81) | function AITitleGenerator({ content, onSelectTitle, apiKey, initialTitle...
FILE: components/changelog/editor/EditorHeader.tsx
type Tag (line 23) | interface Tag {
type EntryData (line 28) | interface EntryData {
type ProjectData (line 39) | interface ProjectData {
type UserData (line 49) | interface UserData {
type EditorHeaderProps (line 55) | interface EditorHeaderProps {
type BookmarkButtonProps (line 79) | interface BookmarkButtonProps {
function BookmarkButton (line 85) | function BookmarkButton({entryId, projectId, title}: BookmarkButtonProps) {
type WWCOpenButtonProps (line 140) | interface WWCOpenButtonProps {
function WWCOpenButton (line 149) | function WWCOpenButton({title, content, version, tags, projectId, entryI...
FILE: components/changelog/editor/TagColorPicker.tsx
type ColorPickerProps (line 25) | interface ColorPickerProps {
function ColorPicker (line 33) | function ColorPicker({
type ColoredTagProps (line 190) | interface ColoredTagProps {
function ColoredTag (line 201) | function ColoredTag({
FILE: components/changelog/editor/TagSelector.tsx
type Tag (line 37) | interface Tag {
type TagSelectorProps (line 43) | interface TagSelectorProps {
constant MAX_CHARS_PER_SECTION (line 53) | const MAX_CHARS_PER_SECTION = 150;
constant SECTIONS_TO_EXTRACT (line 54) | const SECTIONS_TO_EXTRACT = 3;
constant SECTIONS_TO_ANALYZE (line 55) | const SECTIONS_TO_ANALYZE = 3;
type TagSuggestion (line 58) | interface TagSuggestion {
constant TAG_SUGGESTIONS (line 63) | const TAG_SUGGESTIONS: TagSuggestion[] = [
function TagSelector (line 71) | function TagSelector({
FILE: components/changelog/editor/TagSuggester.tsx
type Tag (line 13) | interface Tag {
type TagSuggesterProps (line 18) | interface TagSuggesterProps {
constant MAX_CHARS_PER_SECTION (line 27) | const MAX_CHARS_PER_SECTION = 500;
constant SECTIONS_TO_EXTRACT (line 28) | const SECTIONS_TO_EXTRACT = 3;
constant SECTIONS_TO_ANALYZE (line 29) | const SECTIONS_TO_ANALYZE = 3;
function TagSuggester (line 31) | function TagSuggester({
FILE: components/changelog/editor/VersionSelector.tsx
type VersionType (line 26) | type VersionType = 'major' | 'minor' | 'patch' | 'custom';
type TabMode (line 27) | type TabMode = 'semver' | 'custom';
type ProcessedVersion (line 29) | interface ProcessedVersion {
type ResolvedTemplate (line 37) | interface ResolvedTemplate {
type TimezoneResponse (line 44) | interface TimezoneResponse {
type VersionSelectorProps (line 49) | interface VersionSelectorProps {
type State (line 58) | interface State {
type Action (line 67) | type Action =
constant TYPE_CONFIG (line 74) | const TYPE_CONFIG: Record<VersionType, { dot: string; label: string; bad...
constant BUILT_IN_TEMPLATES (line 81) | const BUILT_IN_TEMPLATES: { format: string; label: string }[] = [
function reducer (line 89) | function reducer(state: State, action: Action): State {
function isSemVer (line 98) | function isSemVer(v: string): boolean {
function stripV (line 105) | function stripV(v: string): string {
function addV (line 109) | function addV(v: string): string {
function getType (line 115) | function getType(v: string): VersionType {
function parseSemVer (line 123) | function parseSemVer(v: string): [number, number, number] | null {
function resolveTemplate (line 129) | function resolveTemplate(
FILE: components/changelog/editor/scheduler/ScheduleEntryDialog.tsx
type ScheduleFormData (line 47) | type ScheduleFormData = z.infer<typeof scheduleSchema>;
type ScheduleEntryDialogProps (line 49) | interface ScheduleEntryDialogProps {
FILE: components/dashboard/WhatsNewModal.tsx
type WhatsNewItem (line 20) | interface WhatsNewItem {
type WhatsNewContent (line 26) | interface WhatsNewContent {
type WhatsNewModalProps (line 34) | interface WhatsNewModalProps {
FILE: components/github/GitHubGenerateDialog.tsx
type GitHubTag (line 66) | interface GitHubTag {
type GitHubRelease (line 71) | interface GitHubRelease {
type AISettings (line 76) | interface AISettings {
type GenerationOptions (line 82) | interface GenerationOptions {
type GeneratedChangelog (line 94) | interface GeneratedChangelog {
type GenerateResult (line 109) | interface GenerateResult {
type Props (line 130) | interface Props {
constant DEFAULT_OPTIONS (line 136) | const DEFAULT_OPTIONS: GenerationOptions = {
function GitHubGenerateDialog (line 148) | function GitHubGenerateDialog({
FILE: components/github/GitHubIntegrationSettings.tsx
type GitHubIntegration (line 36) | interface GitHubIntegration {
type RepositoryInfo (line 51) | interface RepositoryInfo {
type TestResult (line 62) | interface TestResult {
constant DEFAULT_SETTINGS (line 74) | const DEFAULT_SETTINGS: GitHubIntegration = {
function GitHubIntegrationSettings (line 89) | function GitHubIntegrationSettings({ projectId, projectName }: { project...
FILE: components/loading-spinner.tsx
function LoadingSpinner (line 5) | function LoadingSpinner() {
FILE: components/markdown-editor/MarkdownEditor.tsx
type MarkdownEditorProps (line 38) | interface MarkdownEditorProps {
FILE: components/markdown-editor/MarkdownEditorArea.tsx
type MarkdownEditorAreaProps (line 8) | interface MarkdownEditorAreaProps {
FILE: components/markdown-editor/MarkdownPreview.tsx
type MarkdownPreviewProps (line 8) | interface MarkdownPreviewProps {
FILE: components/markdown-editor/MarkdownToolbar.tsx
type ToolbarAction (line 46) | interface ToolbarAction {
type ToolbarGroup (line 55) | interface ToolbarGroup {
type ToolbarDropdown (line 60) | interface ToolbarDropdown {
type MarkdownToolbarProps (line 66) | interface MarkdownToolbarProps {
FILE: components/markdown-editor/RenderMarkdown.tsx
type RenderMarkdownProps (line 7) | interface RenderMarkdownProps {
FILE: components/markdown-editor/StatusBar.tsx
type StatusBarProps (line 13) | interface StatusBarProps {
function formatSaveTime (line 66) | function formatSaveTime(date: Date, timezone = 'UTC'): string {
function StatusBar (line 107) | function StatusBar({
FILE: components/markdown-editor/ai/AIAssistantPanel.tsx
type AIAssistantPanelProps (line 43) | interface AIAssistantPanelProps {
function AIAssistantPanel (line 138) | function AIAssistantPanel({
FILE: components/markdown-editor/hooks/useCUMModals.ts
type CUMModalState (line 6) | interface CUMModalState {
type UseCUMModalsReturn (line 13) | interface UseCUMModalsReturn {
function useCUMModals (line 20) | function useCUMModals(): UseCUMModalsReturn {
FILE: components/markdown-editor/modals/CUMEmbedModal.tsx
type EmbedProvider (line 38) | type EmbedProvider = typeof embedProviders[number]['value'];
type EmbedOptions (line 40) | interface EmbedOptions {
type CUMEmbedConfig (line 55) | interface CUMEmbedConfig {
FILE: components/markdown-editor/types/cum-extensions.ts
type CUMButtonConfig (line 1) | interface CUMButtonConfig {
type CUMAlertConfig (line 11) | interface CUMAlertConfig {
type CUMEmbedConfig (line 19) | interface CUMEmbedConfig {
type CUMTableConfig (line 31) | interface CUMTableConfig {
type CUMModalProps (line 39) | interface CUMModalProps {
type CUMExtensionType (line 45) | type CUMExtensionType = 'button' | 'alert' | 'embed' | 'table';
FILE: components/markdown-editor/utils/formatting.ts
type TextFormatOptions (line 8) | interface TextFormatOptions {
constant FORMAT_OPTIONS (line 20) | const FORMAT_OPTIONS = {
function formatText (line 49) | function formatText(
function isEmptyLine (line 125) | function isEmptyLine(line: string): boolean {
function lineStartsWith (line 132) | function lineStartsWith(line: string, prefix: string): boolean {
function getIndentationLevel (line 139) | function getIndentationLevel(line: string): number {
function addIndentation (line 147) | function addIndentation(line: string, spaces: number): string {
function removeIndentation (line 154) | function removeIndentation(line: string, spaces: number): string {
function toggleFormatting (line 163) | function toggleFormatting(
function getTextMetrics (line 195) | function getTextMetrics(text: string): { words: number; chars: number; l...
function positionToLineColumn (line 206) | function positionToLineColumn(text: string, position: number): { line: n...
function getCurrentParagraph (line 217) | function getCurrentParagraph(text: string, position: number): { text: st...
FILE: components/markdown-editor/utils/keyboard-shortcuts.ts
type KeyboardShortcut (line 8) | interface KeyboardShortcut {
type KeyboardShortcutsConfig (line 22) | interface KeyboardShortcutsConfig {
constant DEFAULT_KEYBOARD_SHORTCUTS (line 31) | const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [
function matchesShortcut (line 113) | function matchesShortcut(event: KeyboardEvent, shortcut: KeyboardShortcu...
function formatShortcut (line 132) | function formatShortcut(shortcut: KeyboardShortcut): string {
function createShortcutHandler (line 162) | function createShortcutHandler(config: KeyboardShortcutsConfig) {
function bindShortcutsToElement (line 201) | function bindShortcutsToElement(
function bindGlobalShortcuts (line 236) | function bindGlobalShortcuts(
function getShortcutMap (line 251) | function getShortcutMap(shortcuts: KeyboardShortcut[]): Record<string, s...
FILE: components/markdown-editor/utils/renderer.ts
type MarkdownRenderOptions (line 8) | interface MarkdownRenderOptions {
constant DEFAULT_RENDER_OPTIONS (line 60) | const DEFAULT_RENDER_OPTIONS: MarkdownRenderOptions = {
function escapeHtml (line 86) | function escapeHtml(unsafe: string): string {
function processCodeBlocks (line 98) | function processCodeBlocks(input: string, features?: MarkdownRenderOptio...
function processInlineCode (line 110) | function processInlineCode(input: string, features?: MarkdownRenderOptio...
function processLists (line 121) | function processLists(input: string, features?: MarkdownRenderOptions['f...
function processLineBreaks (line 317) | function processLineBreaks(input: string, features?: MarkdownRenderOptio...
function processHeadings (line 332) | function processHeadings(input: string, features?: MarkdownRenderOptions...
function processBlockquotes (line 380) | function processBlockquotes(input: string, features?: MarkdownRenderOpti...
function processTables (line 393) | function processTables(input: string, features?: MarkdownRenderOptions['...
function processInlineFormatting (line 446) | function processInlineFormatting(input: string, features?: MarkdownRende...
function processLinks (line 470) | function processLinks(input: string, options: MarkdownRenderOptions): st...
function processImages (line 502) | function processImages(input: string, options: MarkdownRenderOptions): s...
function processFootnotes (line 523) | function processFootnotes(input: string, features?: MarkdownRenderOption...
function processHorizontalRules (line 545) | function processHorizontalRules(input: string, features?: MarkdownRender...
function wrapParagraphs (line 554) | function wrapParagraphs(input: string): string {
function applyCustomRenderers (line 569) | function applyCustomRenderers(
function renderMarkdown (line 593) | function renderMarkdown(markdown: string, options: MarkdownRenderOptions...
function MarkdownRenderer (line 660) | function MarkdownRenderer(props: {
function extractHeadings (line 685) | function extractHeadings(markdown: string): Array<{
function getWordCount (line 708) | function getWordCount(markdown: string): number {
function getReadingTime (line 726) | function getReadingTime(markdown: string, wordsPerMinute: number = 200):...
function generateTableOfContents (line 735) | function generateTableOfContents(markdown: string): string {
function containsElementType (line 783) | function containsElementType(markdown: string, type: 'table' | 'code' | ...
FILE: components/markdown-editor/utils/safe-keyboard-shortcuts.tsx
type KeyboardShortcut (line 8) | interface KeyboardShortcut {
function useSafeKeyboardShortcuts (line 21) | function useSafeKeyboardShortcuts(
function KeyboardShortcutsProvider (line 151) | function KeyboardShortcutsProvider({
FILE: components/project/ProjectNavItem.tsx
type ProjectNavItemProps (line 6) | interface ProjectNavItemProps {
function ProjectNavItem (line 13) | function ProjectNavItem({
FILE: components/project/ProjectSettingsPage.tsx
type Project (line 20) | interface Project extends PrismaProject {
function ProjectsPage (line 47) | function ProjectsPage() {
FILE: components/project/ProjectSidebar.tsx
type NavItemProps (line 43) | interface NavItemProps {
type ChangelogEntry (line 53) | interface ChangelogEntry {
type ChangelogData (line 62) | interface ChangelogData {
type Project (line 67) | interface Project {
function NavItem (line 73) | function NavItem({href, icon: Icon, label, active, external, badge, disa...
type RecentChangelogProps (line 127) | interface RecentChangelogProps {
function RecentChangelog (line 136) | function RecentChangelog({
type BookmarkedChangelogProps (line 224) | interface BookmarkedChangelogProps {
function BookmarkedChangelog (line 230) | function BookmarkedChangelog({id, projectId, title}: BookmarkedChangelog...
function ProjectSidebar (line 273) | function ProjectSidebar({projectId}: { projectId: string }) {
FILE: components/project/RecentChangelogItem.tsx
type RecentChangelogItemProps (line 5) | interface RecentChangelogItemProps {
function RecentChangelogItem (line 12) | function RecentChangelogItem({
FILE: components/project/catch-up/CatchUpEntry.tsx
type CatchUpEntryProps (line 9) | interface CatchUpEntryProps {
function CatchUpEntry (line 13) | function CatchUpEntry({ entry }: CatchUpEntryProps) {
FILE: components/project/catch-up/CatchUpView.tsx
type CatchUpViewProps (line 16) | interface CatchUpViewProps {
type TimelineEntryProps (line 20) | interface TimelineEntryProps {
function TimelineEntry (line 25) | function TimelineEntry({entry, isLast}: TimelineEntryProps) {
function CatchUpView (line 135) | function CatchUpView({projectId}: CatchUpViewProps) {
FILE: components/project/catch-up/SinceSelector.tsx
type SinceSelectorProps (line 15) | interface SinceSelectorProps {
function SinceSelector (line 21) | function SinceSelector({value, onChange, projectId}: SinceSelectorProps) {
FILE: components/project/settings/TagManagement.tsx
type Tag (line 47) | interface Tag {
type TagManagementProps (line 56) | interface TagManagementProps {
type CreateTagData (line 60) | interface CreateTagData {
type UpdateTagData (line 65) | interface UpdateTagData {
function TagManagement (line 91) | function TagManagement({projectId}: TagManagementProps) {
FILE: components/projects/importing/ChangelogImportModal.tsx
type ChangelogImportModalProps (line 42) | interface ChangelogImportModalProps {
type ImportStep (line 49) | type ImportStep = 'source' | 'preview' | 'configure' | 'importing';
type ImportSource (line 50) | type ImportSource = 'markdown' | 'canny';
function ChangelogImportModal (line 52) | function ChangelogImportModal({
function SourceSelectionStep (line 352) | function SourceSelectionStep({
function PreviewStep (line 497) | function PreviewStep({
function ConfigureStep (line 668) | function ConfigureStep({
function ImportingStep (line 915) | function ImportingStep({
FILE: components/projects/importing/ImportDataPrompt.tsx
type ImportDataPromptProps (line 22) | interface ImportDataPromptProps {
function ImportDataPrompt (line 29) | function ImportDataPrompt({
function EmptyStateWithImport (line 182) | function EmptyStateWithImport({
FILE: components/projects/importing/integrations/CannyImportStep.tsx
type CannyImportStepProps (line 22) | interface CannyImportStepProps {
function CannyImportStep (line 27) | function CannyImportStep({ onImport, isProcessing }: CannyImportStepProp...
FILE: components/providers/CommandPaletteProvider.tsx
type CommandPaletteProviderProps (line 7) | interface CommandPaletteProviderProps {
function CommandPaletteProvider (line 11) | function CommandPaletteProvider({ children }: CommandPaletteProviderProp...
FILE: components/providers/setup-context.tsx
type SetupStep (line 7) | type SetupStep = 'welcome' | 'admin' | 'settings' | 'oauth' | 'complete'
type AdminData (line 9) | interface AdminData {
type SystemSettings (line 15) | interface SystemSettings {
type OAuthSettings (line 32) | interface OAuthSettings {
type SetupState (line 40) | interface SetupState {
type SetupContextType (line 50) | interface SetupContextType extends SetupState {
function SetupProvider (line 77) | function SetupProvider({ children }: { children: ReactNode }) {
function useSetup (line 197) | function useSetup() {
FILE: components/settings/connected-sso-section.tsx
type OAuthProvider (line 11) | interface OAuthProvider {
type OAuthConnection (line 18) | interface OAuthConnection {
type SAMLProvider (line 28) | interface SAMLProvider {
type SAMLConnection (line 35) | interface SAMLConnection {
type ConnectedSsoProvidersProps (line 44) | interface ConnectedSsoProvidersProps {
type ConnectionStatus (line 52) | type ConnectionStatus = 'connected' | 'expired' | 'disabled';
FILE: components/settings/passkeys-section.tsx
type Passkey (line 61) | interface Passkey {
function PasskeysSection (line 103) | function PasskeysSection() {
FILE: components/settings/security-settings.tsx
type TwoFactorMode (line 40) | type TwoFactorMode = 'NONE' | 'PASSKEY_PLUS_PASSWORD' | 'PASSWORD_PLUS_P...
type SecuritySettings (line 42) | interface SecuritySettings {
function SecuritySettings (line 82) | function SecuritySettings() {
FILE: components/setup/TeamImportModal.tsx
type TeamImportModalProps (line 20) | interface TeamImportModalProps {
type ParsedEmail (line 27) | interface ParsedEmail {
constant TEMPLATE_EXAMPLES (line 35) | const TEMPLATE_EXAMPLES = {
function TeamImportModal (line 48) | function TeamImportModal({
FILE: components/setup/setup-context.tsx
type SetupStep (line 7) | type SetupStep = 'welcome' | 'admin' | 'settings' | 'oauth' | 'team' | '...
type SetupContextType (line 9) | interface SetupContextType {
FILE: components/setup/setup-step.tsx
type SetupStepProps (line 16) | interface SetupStepProps {
function SetupStep (line 33) | function SetupStep({
FILE: components/setup/steps/admin-step.tsx
type AdminStepProps (line 27) | interface AdminStepProps {
type AdminFormValues (line 42) | type AdminFormValues = z.infer<typeof adminSchema>;
function AdminStep (line 44) | function AdminStep({onNext, onBack}: AdminStepProps) {
FILE: components/setup/steps/completion-step.tsx
type CompletionStepProps (line 12) | type CompletionStepProps = Record<string, never>;
function CompletionStep (line 14) | function CompletionStep({}: CompletionStepProps) {
FILE: components/setup/steps/oauth-step.tsx
type OAuthStepProps (line 34) | interface OAuthStepProps {
type AutoSetupStatus (line 39) | interface AutoSetupStatus {
type AutoSetupResult (line 50) | interface AutoSetupResult {
function OAuthStep (line 62) | function OAuthStep({onNext, onBack}: OAuthStepProps) {
FILE: components/setup/steps/settings-step.tsx
type SettingsStepProps (line 19) | interface SettingsStepProps {
type SettingsFormValues (line 33) | type SettingsFormValues = z.infer<typeof settingsSchema>;
function SettingsStep (line 35) | function SettingsStep({ onNext, onBack }: SettingsStepProps) {
FILE: components/setup/steps/team-step.tsx
type TeamInviteStepProps (line 16) | interface TeamInviteStepProps {
type Invitation (line 22) | interface Invitation {
type TeamInviteState (line 32) | interface TeamInviteState {
function TeamStep (line 41) | function TeamStep({onNext, onSkip}: TeamInviteStepProps) {
FILE: components/setup/steps/welcome-step.tsx
type WelcomeStepProps (line 7) | interface WelcomeStepProps {
function WelcomeStep (line 11) | function WelcomeStep({ onNext }: WelcomeStepProps) {
FILE: components/sso/ProviderLogo.tsx
type ProviderLogoProps (line 3) | interface ProviderLogoProps {
FILE: components/subscription-form.tsx
type SubscriptionFormValues (line 22) | type SubscriptionFormValues = z.infer<typeof formSchema>;
type Update (line 24) | interface Update {
type SubscriptionFormProps (line 29) | interface SubscriptionFormProps {
type Step (line 35) | type Step = 'email' | 'name' | 'preferences' | 'success';
function SubscriptionForm (line 37) | function SubscriptionForm({
FILE: components/telemetry/PromptModal.tsx
type TelemetryPromptModalProps (line 11) | interface TelemetryPromptModalProps {
type ModalStep (line 17) | type ModalStep = 'initial' | 'confirmation' | 'final-plea';
FILE: components/theme-provider.tsx
function ThemeSync (line 9) | function ThemeSync() {
function ThemeProvider (line 68) | function ThemeProvider({
function useThemeWithLoading (line 88) | function useThemeWithLoading() {
FILE: components/ui/alert.tsx
type AlertProps (line 115) | interface AlertProps
FILE: components/ui/badge.tsx
function getContrastColor (line 95) | function getContrastColor(hexColor: string): string {
type BadgeProps (line 103) | interface BadgeProps
function Badge (line 111) | function Badge({
FILE: components/ui/breadcrumb.tsx
function Breadcrumb (line 7) | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
function BreadcrumbList (line 11) | function BreadcrumbList({ className, ...props }: React.ComponentProps<"o...
function BreadcrumbItem (line 24) | function BreadcrumbItem({ className, ...props }: React.ComponentProps<"l...
function BreadcrumbLink (line 34) | function BreadcrumbLink({
function BreadcrumbPage (line 52) | function BreadcrumbPage({ className, ...props }: React.ComponentProps<"s...
function BreadcrumbSeparator (line 65) | function BreadcrumbSeparator({
function BreadcrumbEllipsis (line 83) | function BreadcrumbEllipsis({
FILE: components/ui/breadcrumbs.tsx
function fetchProjectName (line 11) | async function fetchProjectName(projectId: string) {
function humanizeSegment (line 24) | function humanizeSegment(segment: string) {
function Breadcrumbs (line 32) | function Breadcrumbs() {
FILE: components/ui/button.tsx
type ButtonProps (line 88) | interface ButtonProps
FILE: components/ui/calendar.tsx
type CalendarProps (line 16) | type CalendarProps = DayPickerProps & {
type NavView (line 53) | type NavView = "days" | "years"
function Calendar (line 61) | function Calendar({
function Nav (line 243) | function Nav({
function CaptionLabel (line 381) | function CaptionLabel({
function MonthGrid (line 409) | function MonthGrid({
function YearGrid (line 445) | function YearGrid({
FILE: components/ui/card.tsx
type CardProps (line 62) | interface CardProps
FILE: components/ui/confetti.tsx
function Confetti (line 6) | function Confetti() {
FILE: components/ui/dialog.tsx
type DialogContentProps (line 33) | interface DialogContentProps extends React.ComponentPropsWithoutRef<type...
FILE: components/ui/error-alert.tsx
type ErrorAlertProps (line 4) | interface ErrorAlertProps {
function ErrorAlert (line 8) | function ErrorAlert({ message }: ErrorAlertProps) {
FILE: components/ui/form.tsx
type FormFieldContextValue (line 20) | type FormFieldContextValue<
type FormItemContextValue (line 67) | type FormItemContextValue = {
FILE: components/ui/input.tsx
type InputProps (line 81) | interface InputProps
FILE: components/ui/searchable-select.tsx
type SearchableSelectItem (line 17) | interface SearchableSelectItem {
type SearchableSelectGroup (line 23) | interface SearchableSelectGroup {
type SearchableSelectProps (line 28) | interface SearchableSelectProps {
function SearchableSelect (line 43) | function SearchableSelect({
FILE: components/ui/sheet.tsx
type SheetContentProps (line 52) | interface SheetContentProps
FILE: components/ui/sidebar.tsx
type SidebarUser (line 33) | interface SidebarUser {
type NavItem (line 41) | interface NavItem {
type NavSection (line 48) | interface NavSection {
type SidebarProps (line 53) | interface SidebarProps {
function DesktopSidebar (line 66) | function DesktopSidebar({
function MobileNav (line 314) | function MobileNav({
function Sidebar (line 462) | function Sidebar({
FILE: components/ui/skeleton.tsx
function Skeleton (line 3) | function Skeleton({
FILE: components/ui/switch.tsx
type SwitchProps (line 57) | interface SwitchProps
FILE: components/ui/textarea.tsx
type TextareaProps (line 5) | interface TextareaProps extends React.ComponentProps<"textarea"> {
FILE: components/ui/toast.tsx
type ToastProps (line 253) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement (line 254) | type ToastActionElement = React.ReactElement<typeof ToastAction>
FILE: components/ui/toaster.tsx
function Toaster (line 13) | function Toaster() {
FILE: context/auth.tsx
type AuthContextType (line 7) | interface AuthContextType {
constant REFRESH_INTERVAL (line 17) | const REFRESH_INTERVAL = 15 * 60 * 1000 // 15 minutes
constant TEST_REFRESH_INTERVAL (line 18) | const TEST_REFRESH_INTERVAL = 15 * 1000 // 15 seconds
constant REFRESH_THRESHOLD (line 19) | const REFRESH_THRESHOLD = 0.8 // Refresh at 80% of token lifetime
function AuthProvider (line 21) | function AuthProvider({
function useAuth (line 188) | function useAuth() {
FILE: emails/approval-notification.tsx
type ApprovalNotificationEmailProps (line 14) | interface ApprovalNotificationEmailProps {
FILE: emails/changelog.tsx
function sanitizeHtml (line 21) | function sanitizeHtml(html: string): string {
type Entry (line 36) | interface Entry {
type ChangelogEmailProps (line 45) | interface ChangelogEmailProps {
FILE: emails/password-reset.tsx
type PasswordResetEmailProps (line 17) | interface PasswordResetEmailProps {
FILE: emails/rejection-notification.tsx
type RejectionNotificationEmailProps (line 14) | interface RejectionNotificationEmailProps {
FILE: emails/schedule-published.tsx
type SchedulePublishedEmailProps (line 18) | interface SchedulePublishedEmailProps {
FILE: hooks/use-local-storage.ts
type SetValue (line 5) | type SetValue<T> = (value: T | ((prevValue: T) => T)) => void
function useLocalStorage (line 13) | function useLocalStorage<T>(
FILE: hooks/use-media-query.ts
function useMediaQuery (line 17) | function useMediaQuery(query: string): boolean {
function useBreakpoint (line 78) | function useBreakpoint(breakpoint: keyof typeof breakpoints): boolean {
FILE: hooks/use-timezone.ts
type TimezoneResponse (line 3) | interface TimezoneResponse {
function useTimezone (line 15) | function useTimezone(): string {
FILE: hooks/use-toast.ts
constant TOAST_LIMIT (line 11) | const TOAST_LIMIT = 1
constant TOAST_REMOVE_DELAY (line 12) | const TOAST_REMOVE_DELAY = 1000000
type ToasterToast (line 14) | type ToasterToast = ToastProps & {
function genId (line 30) | function genId() {
type ActionType (line 35) | type ActionType = typeof actionTypes
type Action (line 37) | type Action =
type State (line 55) | interface State {
function dispatch (line 136) | function dispatch(action: Action) {
type Toast (line 143) | type Toast = Omit<ToasterToast, "id">
function toast (line 145) | function toast({ ...props }: Toast) {
function useToast (line 174) | function useToast() {
FILE: hooks/useAIAssistant.ts
type AIAssistantState (line 12) | interface AIAssistantState {
type UseAIAssistantOptions (line 23) | interface UseAIAssistantOptions {
function useAIAssistant (line 33) | function useAIAssistant({
FILE: hooks/useBookmarks.ts
type UseBookmarksOptions (line 6) | interface UseBookmarksOptions {
type UseBookmarksReturn (line 11) | interface UseBookmarksReturn {
function useBookmarks (line 29) | function useBookmarks(options: UseBookmarksOptions = {}): UseBookmarksRe...
function useGlobalBookmarks (line 169) | function useGlobalBookmarks(): UseBookmarksReturn {
function useIsBookmarked (line 174) | function useIsBookmarked(entryId: string, projectId: string): boolean {
FILE: hooks/useChunkedData.ts
type ChunkedDataParams (line 5) | interface ChunkedDataParams<T extends { id: string | number }> {
FILE: hooks/useCommandPalette.ts
function useCommandPalette (line 6) | function useCommandPalette() {
FILE: hooks/useEditorHistory.ts
type EditorHistoryOptions (line 6) | interface EditorHistoryOptions {
function useEditorHistory (line 15) | function useEditorHistory({
FILE: hooks/useMarkdownState.ts
type MarkdownState (line 5) | interface MarkdownState {
type MarkdownActions (line 12) | interface MarkdownActions {
type UseMarkdownStateOptions (line 21) | interface UseMarkdownStateOptions {
function useMarkdownState (line 27) | function useMarkdownState(options: UseMarkdownStateOptions = {}) {
FILE: hooks/useSlashCommands.ts
type CommandGroup (line 5) | interface CommandGroup {
type Command (line 11) | interface Command {
type SlashCommandState (line 21) | interface SlashCommandState {
type UseSlashCommandsProps (line 29) | interface UseSlashCommandsProps {
function useSlashCommands (line 42) | function useSlashCommands({
FILE: hooks/useTelemetry.ts
type TelemetryConfig (line 4) | interface TelemetryConfig {
FILE: hooks/useWhatsNew.ts
constant VERSION_STORAGE_KEY (line 8) | const VERSION_STORAGE_KEY = 'changerawr-last-version'
constant WHATS_NEW_ENDPOINT (line 9) | const WHATS_NEW_ENDPOINT = 'https://dl.supers0ft.us/changerawr/whatsnew/'
function useWhatsNew (line 11) | function useWhatsNew() {
FILE: instrumentation.ts
function register (line 1) | async function register() {
FILE: lib/api/middleware.ts
type ApiError (line 7) | interface ApiError {
function enforceRoutePermissions (line 17) | async function enforceRoutePermissions(
function withPermissions (line 114) | function withPermissions<T extends Record<string, unknown>>(
function createErrorResponse (line 141) | function createErrorResponse(message: string, status: number): NextRespo...
function requirePermission (line 152) | async function requirePermission(
function requireProjectAccess (line 183) | async function requireProjectAccess(
FILE: lib/api/permissions.ts
constant API_PERMISSIONS (line 8) | const API_PERMISSIONS = {
type ApiPermission (line 42) | type ApiPermission = typeof API_PERMISSIONS[keyof typeof API_PERMISSIONS];
constant PERMISSION_GROUPS (line 48) | const PERMISSION_GROUPS = {
function hasPermission (line 81) | function hasPermission(
function hasAllPermissions (line 95) | function hasAllPermissions(
function hasAnyPermission (line 109) | function hasAnyPermission(
FILE: lib/api/route-permissions.ts
type RoutePermissionConfig (line 8) | interface RoutePermissionConfig {
constant ROUTE_PERMISSIONS (line 26) | const ROUTE_PERMISSIONS: Record<string, RoutePermissionConfig> = {
function matchRoute (line 381) | function matchRoute(path: string): RoutePermissionConfig | null {
function patternToRegex (line 413) | function patternToRegex(pattern: string): RegExp {
function extractProjectId (line 423) | function extractProjectId(path: string): string | null {
function methodAllowed (line 431) | function methodAllowed(config: RoutePermissionConfig, method: string): b...
FILE: lib/app-info.ts
function getVersionString (line 28) | function getVersionString(): string {
function getCopyrightYears (line 35) | function getCopyrightYears(): string {
FILE: lib/auth/api-key.ts
type AuthContext (line 6) | interface AuthContext {
type AuthRequest (line 15) | interface AuthRequest {
function getTokenFromRequest (line 28) | function getTokenFromRequest(request: AuthRequest): {type: 'jwt' | 'apik...
function authenticateWithJWT (line 54) | async function authenticateWithJWT(token: string): Promise<AuthContext |...
function authenticateWithAPIKey (line 78) | async function authenticateWithAPIKey(key: string): Promise<AuthContext ...
function authenticateRequest (line 131) | async function authenticateRequest(request: AuthRequest | NextRequest): ...
function hasProjectAccess (line 149) | function hasProjectAccess(ctx: AuthContext, projectId: string): boolean {
function hasRequiredPermission (line 169) | function hasRequiredPermission(ctx: AuthContext, permission: ApiPermissi...
function hasRequiredPermissions (line 181) | function hasRequiredPermissions(ctx: AuthContext, permissions: ApiPermis...
function validateRequestAuth (line 194) | async function validateRequestAuth(
FILE: lib/auth/authorization.ts
function getTokenFromHeader (line 1) | function getTokenFromHeader(req: Request) {
FILE: lib/auth/claim-validator.ts
type ClaimRule (line 6) | interface ClaimRule {
type ClaimValidationResult (line 12) | interface ClaimValidationResult {
function validateClaims (line 23) | function validateClaims(
function parseRequiredClaims (line 83) | function parseRequiredClaims(requiredClaimsJson: any): Record<string, st...
FILE: lib/auth/cli-auth.ts
function generateCLIAuthCode (line 10) | async function generateCLIAuthCode(userId: string, callbackUrl: string) {
function validateCLIAuthCode (line 50) | async function validateCLIAuthCode(code: string) {
function markCLIAuthCodeAsUsed (line 95) | async function markCLIAuthCodeAsUsed(code: string) {
function cleanupExpiredCLIAuthCodes (line 106) | async function cleanupExpiredCLIAuthCodes() {
function getActiveCLIAuthCodes (line 135) | async function getActiveCLIAuthCodes(userId: string) {
function revokeAllCLIAuthCodes (line 160) | async function revokeAllCLIAuthCodes(userId: string) {
FILE: lib/auth/email-domain-validator.ts
type DomainRestrictionConfig (line 5) | interface DomainRestrictionConfig {
type DomainValidationResult (line 10) | interface DomainValidationResult {
function extractEmailDomain (line 21) | function extractEmailDomain(email: string): string | null {
function validateEmailDomain (line 34) | function validateEmailDomain(
FILE: lib/auth/oauth.ts
function getOAuthProviders (line 8) | async function getOAuthProviders(includeDisabled = false) {
function getDefaultProvider (line 22) | async function getDefaultProvider() {
function getOAuthLoginUrl (line 30) | async function getOAuthLoginUrl(providerId: string, state?: string) {
function exchangeCodeForToken (line 53) | async function exchangeCodeForToken(providerId: string, code: string) {
function fetchUserInfo (line 163) | async function fetchUserInfo(providerId: string, accessToken: string): P...
function handleOAuthCallback (line 186) | async function handleOAuthCallback(providerName: string, code: string) {
FILE: lib/auth/password.ts
function hashPassword (line 3) | async function hashPassword(password: string) {
function verifyPassword (line 7) | async function verifyPassword(password: string, hashedPassword: string) {
FILE: lib/auth/providers/easypanel.ts
type EasypanelProviderConfig (line 3) | interface EasypanelProviderConfig {
function setupEasypanelProvider (line 9) | async function setupEasypanelProvider(config: EasypanelProviderConfig) {
FILE: lib/auth/providers/easypanel/auto-setup.ts
type AutoSetupResult (line 10) | interface AutoSetupResult {
type AutoSetupOptions (line 23) | interface AutoSetupOptions {
function performAutoOAuthSetup (line 31) | async function performAutoOAuthSetup(
function verifyAutoSetupConfiguration (line 178) | async function verifyAutoSetupConfiguration(): Promise<{
function getOAuthServerInfo (line 214) | function getOAuthServerInfo(): {
FILE: lib/auth/providers/easypanel/client.ts
type CreateClientRequest (line 44) | interface CreateClientRequest {
type UpdateClientRequest (line 51) | interface UpdateClientRequest {
type EasypanelClient (line 58) | interface EasypanelClient {
class EasypanelApiError (line 68) | class EasypanelApiError extends Error {
method constructor (line 69) | constructor(
class EasypanelApiClient (line 79) | class EasypanelApiClient {
method constructor (line 83) | constructor(baseUrl: string, apiKey: string) {
method makeRequest (line 88) | private async makeRequest<T>(
method testConnection (line 159) | async testConnection(): Promise<{ success: boolean; error?: string }> {
method listClients (line 199) | async listClients(): Promise<EasypanelClient[]> {
method createClient (line 220) | async createClient(clientData: CreateClientRequest): Promise<Easypanel...
method getClient (line 260) | async getClient(clientId: string): Promise<EasypanelClient> {
method updateClient (line 281) | async updateClient(clientId: string, updates: UpdateClientRequest): Pr...
method deleteClient (line 306) | async deleteClient(clientId: string): Promise<void> {
method regenerateSecret (line 318) | async regenerateSecret(clientId: string): Promise<{ newSecret: string ...
function createEasypanelApiClient (line 344) | function createEasypanelApiClient(): EasypanelApiClient | null {
function isAutoOAuthAvailable (line 358) | function isAutoOAuthAvailable(): boolean {
FILE: lib/auth/providers/pocketid.ts
type PocketIDProviderConfig (line 3) | interface PocketIDProviderConfig {
function setupPocketIDProvider (line 9) | async function setupPocketIDProvider(config: PocketIDProviderConfig) {
FILE: lib/auth/saml.ts
function getAppUrl (line 9) | function getAppUrl(): string {
function buildSAMLInstance (line 13) | function buildSAMLInstance(provider: {
function getSAMLProviders (line 41) | async function getSAMLProviders(includeDisabled = false) {
function getSAMLLoginUrl (line 48) | async function getSAMLLoginUrl(providerName: string, relayState?: string...
function validateSAMLResponse (line 65) | async function validateSAMLResponse(
function handleSAMLCallback (line 119) | async function handleSAMLCallback(providerName: string, samlResponse: st...
function getSAMLMetadata (line 236) | async function getSAMLMetadata(providerName: string): Promise<string> {
FILE: lib/auth/tokens.ts
constant ACCESS_SECRET (line 6) | const ACCESS_SECRET = new TextEncoder().encode(
function generateTokens (line 10) | async function generateTokens(userId: string) {
function generateCLITokens (line 49) | async function generateCLITokens(userId: string) {
function verifyAccessToken (line 88) | async function verifyAccessToken(token: string) {
type TokenResponse (line 97) | interface TokenResponse {
function refreshAccessToken (line 103) | async function refreshAccessToken(currentRefreshToken: string): Promise<...
function refreshCLIAccessToken (line 165) | async function refreshCLIAccessToken(currentRefreshToken: string): Promi...
FILE: lib/auth/webauthn.ts
function generateRegistrationOptionsForUser (line 22) | async function generateRegistrationOptionsForUser(
function verifyRegistration (line 47) | async function verifyRegistration(
function generateAuthenticationOptionsForUser (line 59) | async function generateAuthenticationOptionsForUser(
function verifyAuthentication (line 73) | async function verifyAuthentication(
FILE: lib/constants/timezones.ts
constant TIMEZONES (line 1) | const TIMEZONES = [
type TimezoneValue (line 66) | type TimezoneValue = typeof TIMEZONES[number]['value']
type TimezoneRegion (line 67) | type TimezoneRegion = typeof TIMEZONES[number]['region']
constant TIMEZONE_REGIONS (line 69) | const TIMEZONE_REGIONS = [...new Set(TIMEZONES.map(tz => tz.region))] as...
function getTimezoneLabel (line 71) | function getTimezoneLabel(value: string): string {
function getTimezonesByRegion (line 76) | function getTimezonesByRegion() {
FILE: lib/custom-domains/constants.ts
constant DOMAIN_CONSTANTS (line 1) | const DOMAIN_CONSTANTS = {
constant BLOCKED_DOMAINS (line 8) | const BLOCKED_DOMAINS = [
constant DOMAIN_ERRORS (line 17) | const DOMAIN_ERRORS = {
FILE: lib/custom-domains/dns.ts
function verifyDNSRecords (line 5) | async function verifyDNSRecords(
function verifyCNAME (line 44) | async function verifyCNAME(
function verifyTXTRecord (line 69) | async function verifyTXTRecord(
function verifyDomainViaHTTP (line 105) | async function verifyDomainViaHTTP(
function checkDomainResolution (line 150) | async function checkDomainResolution(domain: string): Promise<boolean> {
FILE: lib/custom-domains/service.ts
function createCustomDomain (line 7) | async function createCustomDomain(
function getDomainByDomain (line 67) | async function getDomainByDomain(domain: string): Promise<CustomDomain |...
function getDomainsByProject (line 84) | async function getDomainsByProject(projectId: string): Promise<CustomDom...
function getDomainsByUser (line 102) | async function getDomainsByUser(userId: string): Promise<CustomDomain[]> {
function getAllDomains (line 120) | async function getAllDomains(): Promise<CustomDomain[]> {
function updateDomainVerification (line 137) | async function updateDomainVerification(
function deleteDomain (line 165) | async function deleteDomain(domain: string): Promise<void> {
function canUserManageDomain (line 179) | async function canUserManageDomain(
FILE: lib/custom-domains/ssl/acme-account.ts
function getAcmeClient (line 23) | async function getAcmeClient(): Promise<acme.Client> {
FILE: lib/custom-domains/ssl/auto-renewal.ts
constant RENEWAL_THRESHOLD_DAYS (line 7) | const RENEWAL_THRESHOLD_DAYS = parseInt(process.env.SSL_RENEWAL_THRESHOL...
constant MAX_BATCH_SIZE (line 10) | const MAX_BATCH_SIZE = parseInt(process.env.SSL_RENEWAL_BATCH_SIZE || '1...
function runAutoRenewal (line 14) | async function runAutoRenewal(): Promise<{
function checkCertificateHealth (line 110) | async function checkCertificateHealth(): Promise<{
FILE: lib/custom-domains/ssl/encryption.ts
constant ALGORITHM (line 3) | const ALGORITHM = 'aes-256-gcm' as const
constant IV_LENGTH (line 4) | const IV_LENGTH = 12 // NIST-recommended for GCM
constant SEPARATOR (line 5) | const SEPARATOR = ':'
function getKey (line 7) | function getKey(): Buffer {
function encrypt (line 18) | function encrypt(plaintext: string): string {
function decrypt (line 36) | function decrypt(data: string): string {
FILE: lib/custom-domains/ssl/service.ts
function getOrderByUrl (line 9) | function getOrderByUrl(client: acme.Client, orderUrl: string): Promise<a...
function createDns01Order (line 15) | async function createDns01Order(client: acme.Client, hostname: string) {
function isOrderExpired (line 36) | function isOrderExpired(order: acme.Order): boolean {
function isUnusableDnsOrder (line 46) | function isUnusableDnsOrder(order: acme.Order): boolean {
function shouldReplaceOrderAfterLookupFailure (line 52) | function shouldReplaceOrderAfterLookupFailure(error: unknown): boolean {
function verifyDnsTxtPropagation (line 65) | async function verifyDnsTxtPropagation(hostname: string, expectedValue: ...
constant MAX_PER_WEEK (line 125) | const MAX_PER_WEEK = 45 // LE hard limit is 50, stay under it
constant ONE_WEEK_MS (line 126) | const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000
function getRegisteredDomain (line 128) | function getRegisteredDomain(hostname: string): string {
function checkRateLimit (line 133) | function checkRateLimit(hostname: string): void {
function initiateHttp01Certificate (line 152) | async function initiateHttp01Certificate(
function completeHttp01Challenge (line 213) | async function completeHttp01Challenge(
type Dns01ChallengeInfo (line 371) | interface Dns01ChallengeInfo {
function initiateDns01Certificate (line 379) | async function initiateDns01Certificate(
function completeDns01Certificate (line 423) | async function completeDns01Certificate(certId: string): Promise<void> {
type CertBundle (line 634) | interface CertBundle {
function getActiveCertBundle (line 641) | async function getActiveCertBundle(
function renewCertificate (line 673) | async function renewCertificate(cert: DomainCertificate & {
function markFailed (line 697) | async function markFailed(certId: string, error: unknown): Promise<void> {
FILE: lib/custom-domains/ssl/setup-renewal-job.ts
function setupDailySslRenewal (line 13) | async function setupDailySslRenewal(): Promise<void> {
function scheduleNextSslRenewal (line 61) | async function scheduleNextSslRenewal(): Promise<void> {
FILE: lib/custom-domains/ssl/ssrf-guard.ts
constant BLOCKED_PREFIXES (line 3) | const BLOCKED_PREFIXES = [
function is172Private (line 15) | function is172Private(ip: string): boolean {
function isPrivateIp (line 22) | function isPrivateIp(ip: string): boolean {
function assertNotInternal (line 30) | async function assertNotInternal(hostname: string): Promise<void> {
FILE: lib/custom-domains/ssl/webhook.ts
type AgentEvent (line 3) | type AgentEvent =
function sign (line 11) | function sign(body: string, secret: string): string {
function notifyAgent (line 19) | async function notifyAgent(event: AgentEvent): Promise<void> {
FILE: lib/custom-domains/utils.ts
function getAppDomain (line 5) | function getAppDomain(): string {
function isDevelopment (line 29) | function isDevelopment(): boolean {
function getAppUrl (line 37) | function getAppUrl(): string {
FILE: lib/custom-domains/validation.ts
type ValidationResult (line 4) | interface ValidationResult {
function validateDomain (line 9) | function validateDomain(domain: string): ValidationResult {
function validateProjectId (line 43) | function validateProjectId(projectId: string): boolean {
function generateVerificationToken (line 49) | function generateVerificationToken(): string {
FILE: lib/middleware/analytics.ts
type AnalyticsTrackingOptions (line 4) | interface AnalyticsTrackingOptions {
function trackChangelogView (line 15) | async function trackChangelogView(
function trackChangelogViewsBatch (line 66) | async function trackChangelogViewsBatch(
FILE: lib/services/analytics/geolocation.ts
type GeolocationResult (line 3) | interface GeolocationResult {
function getGeolocationFromIP (line 13) | async function getGeolocationFromIP(
function extractIPFromRequest (line 68) | function extractIPFromRequest(request: Request): string {
function getCountryFromCloudflare (line 95) | function getCountryFromCloudflare(request: Request): string | undefined {
FILE: lib/services/auth/password-breach.ts
type PasswordBreachResult (line 3) | interface PasswordBreachResult {
function checkPasswordBreach (line 14) | async function checkPasswordBreach(password: string): Promise<PasswordBr...
FILE: lib/services/auth/password-reset.ts
type PasswordResetOptions (line 9) | interface PasswordResetOptions {
type SendPasswordResetEmailResult (line 14) | interface SendPasswordResetEmailResult {
function createPasswordResetAndSendEmail (line 23) | async function createPasswordResetAndSendEmail(options: PasswordResetOpt...
function validatePasswordResetToken (line 155) | async function validatePasswordResetToken(token: string): Promise<{
function resetPassword (line 204) | async function resetPassword(token: string, newPassword: string): Promise<{
FILE: lib/services/bookmarks/bookmark.service.ts
type BookmarkedItem (line 3) | interface BookmarkedItem {
type BookmarkStorage (line 10) | interface BookmarkStorage {
class BookmarkService (line 15) | class BookmarkService {
method getProjectBookmarks (line 22) | static getProjectBookmarks(projectId: string): BookmarkedItem[] {
method getAllBookmarks (line 40) | static getAllBookmarks(): Record<string, BookmarkedItem[]> {
method isBookmarked (line 71) | static isBookmarked(entryId: string, projectId: string): boolean {
method addBookmark (line 79) | static addBookmark(entryId: string, title: string, projectId: string):...
method removeBookmark (line 113) | static removeBookmark(entryId: string, projectId: string): boolean {
method toggleBookmark (line 140) | static toggleBookmark(entryId: string, title: string, projectId: strin...
method updateBookmarkTitle (line 153) | static updateBookmarkTitle(entryId: string, newTitle: string, projectI...
method clearProjectBookmarks (line 181) | static clearProjectBookmarks(projectId: string): boolean {
method getProjectBookmarkCount (line 195) | static getProjectBookmarkCount(projectId: string): number {
method getRecentBookmarks (line 202) | static getRecentBookmarks(limit: number = 10): BookmarkedItem[] {
method searchBookmarks (line 218) | static searchBookmarks(query: string, projectId?: string): BookmarkedI...
method exportBookmarks (line 241) | static exportBookmarks(): string {
method importBookmarks (line 252) | static importBookmarks(jsonData: string): boolean {
method updateGlobalBookmarks (line 279) | private static updateGlobalBookmarks(): void {
method migrateOldBookmarks (line 308) | private static migrateOldBookmarks(): Record<string, BookmarkedItem[]> {
FILE: lib/services/changelog/rss.ts
type RSSFeedOptions (line 3) | interface RSSFeedOptions {
function generateRSSFeed (line 12) | function generateRSSFeed(entries: ChangelogEntry[], options: RSSFeedOpti...
FILE: lib/services/core/markdown/extensions/index.ts
function getExtension (line 23) | function getExtension(name: string): Extension | undefined {
function getExtensionNames (line 30) | function getExtensionNames(): string[] {
FILE: lib/services/core/markdown/useCustomExtensions.ts
function getMarkdownEngine (line 20) | function getMarkdownEngine(): ChangerawrMarkdown {
function renderMarkdown (line 43) | function renderMarkdown(markdown: string): string {
function renderMarkdownWithMetrics (line 52) | function renderMarkdownWithMetrics(markdown: string): { html: string; me...
function renderMarkdownStreamed (line 61) | async function renderMarkdownStreamed(
function getCacheStats (line 78) | function getCacheStats() {
function clearCaches (line 86) | function clearCaches() {
function parseMarkdown (line 94) | function parseMarkdown(markdown: string) {
function createEngineWithExtensions (line 102) | function createEngineWithExtensions(config?: EngineConfig): ChangerawrMa...
function resetEngineInstance (line 119) | function resetEngineInstance(): void {
FILE: lib/services/core/system-user/service.ts
constant SYSTEM_USER_ID (line 7) | const SYSTEM_USER_ID = 'system';
constant SYSTEM_USER (line 12) | const SYSTEM_USER = {
function ensureSystemUser (line 26) | async function ensureSystemUser(): Promise<string> {
function isSystemUser (line 58) | function isSystemUser(userId: string): boolean {
FILE: lib/services/easypanel/index.ts
class EasypanelService (line 6) | class EasypanelService {
method constructor (line 9) | constructor(config: EasypanelConfig) {
method isConfigured (line 16) | static isConfigured(): boolean {
method fromEnv (line 28) | static fromEnv(): EasypanelService | null {
method makeRequest (line 44) | private async makeRequest<T>(
method updateImage (line 76) | async updateImage(newImage: string): Promise<void> {
method deployService (line 98) | async deployService(): Promise<void> {
method performUpdate (line 114) | async performUpdate(version: string, customImage?: string): Promise<vo...
method testConnection (line 139) | async testConnection(): Promise<boolean> {
method getServiceInfo (line 153) | async getServiceInfo(): Promise<unknown> {
method getConfig (line 165) | getConfig(): { projectId: string; serviceId: string; panelUrl: string;...
method generateRecommendedImage (line 177) | static generateRecommendedImage(version: string): string {
FILE: lib/services/email/notification.ts
type SendEmailParams (line 11) | interface SendEmailParams {
type EmailResult (line 21) | interface EmailResult {
type NotificationRequestInfo (line 27) | interface NotificationRequestInfo {
type SendNotificationParams (line 34) | interface SendNotificationParams {
type SubscriberWithSubscriptions (line 41) | interface SubscriberWithSubscriptions {
type ManageSubscriptionParams (line 51) | interface ManageSubscriptionParams {
type SubscriptionResult (line 58) | interface SubscriptionResult {
function sendChangelogEmail (line 66) | async function sendChangelogEmail(params: SendEmailParams): Promise<Emai...
function sendNotificationEmail (line 328) | async function sendNotificationEmail({
function manageSubscription (line 440) | async function manageSubscription({
FILE: lib/services/email/schedule-notification.ts
type SendScheduleNotificationParams (line 8) | interface SendScheduleNotificationParams {
type AuditLogDetailsWithStaffUser (line 15) | interface AuditLogDetailsWithStaffUser {
function sendSchedulePublishedNotification (line 24) | async function sendSchedulePublishedNotification({
function getScheduleCreatorUserId (line 194) | async function getScheduleCreatorUserId(entryId: string): Promise<string...
FILE: lib/services/github/changelog-generator.ts
type FileChange (line 9) | interface FileChange {
type CommitData (line 17) | interface CommitData {
type ChangelogEntry (line 26) | interface ChangelogEntry {
type ChangelogGenerationOptions (line 35) | interface ChangelogGenerationOptions {
type GeneratedChangelog (line 51) | interface GeneratedChangelog {
function cleanText (line 69) | function cleanText(text: string | undefined | null): string {
function safeTruncate (line 90) | function safeTruncate(text: string, maxLength: number): string {
class GitHubChangelogGenerator (line 106) | class GitHubChangelogGenerator {
method constructor (line 109) | constructor(client: GitHubClient) {
method generateChangelogBetweenRefs (line 116) | async generateChangelogBetweenRefs(
method generateChangelogFromRecent (line 133) | async generateChangelogFromRecent(
method generateChangelogFromCommits (line 156) | async generateChangelogFromCommits(
method enrichCommitsWithFileData (line 236) | private async enrichCommitsWithFileData(
method generateEntriesWithAI (line 273) | private async generateEntriesWithAI(
method analyzeCommitWithAI (line 316) | private async analyzeCommitWithAI(
method buildStructuredPrompt (line 341) | private buildStructuredPrompt(commit: CommitData): string {
method parseStructuredResponse (line 372) | private parseStructuredResponse(response: string, commit: CommitData):...
method generateBasicEntries (line 417) | private generateBasicEntries(commits: CommitData[]): ChangelogEntry[] {
method generateBasicEntry (line 424) | private generateBasicEntry(commit: CommitData): ChangelogEntry {
method categorizeCommitMessage (line 436) | private categorizeCommitMessage(message: string): string {
method generateMarkdownContent (line 464) | private generateMarkdownContent(entries: ChangelogEntry[], options: Ch...
method getCategoryEmoji (line 526) | private getCategoryEmoji(category: string): string {
method inferVersion (line 542) | private async inferVersion(commits: CommitData[], repositoryUrl: strin...
method getAvailableTags (line 610) | async getAvailableTags(repositoryUrl: string): Promise<Array<{ name: s...
method getAvailableReleases (line 629) | async getAvailableReleases(repositoryUrl: string): Promise<Array<{ nam...
method delay (line 648) | private delay(ms: number): Promise<void> {
function createGitHubChangelogGenerator (line 656) | function createGitHubChangelogGenerator(client: GitHubClient): GitHubCha...
FILE: lib/services/github/client.ts
type GitHubConfig (line 3) | interface GitHubConfig {
type GitHubCommitAuthor (line 9) | interface GitHubCommitAuthor {
type GitHubCommitData (line 15) | interface GitHubCommitData {
type GitHubCommitStats (line 21) | interface GitHubCommitStats {
type GitHubFile (line 27) | interface GitHubFile {
type GitHubCommitResponse (line 40) | interface GitHubCommitResponse {
type GitHubCommit (line 51) | interface GitHubCommit {
type GitHubTag (line 63) | interface GitHubTag {
type GitHubReleaseAuthor (line 73) | interface GitHubReleaseAuthor {
type GitHubRelease (line 78) | interface GitHubRelease {
type GitHubRepository (line 90) | interface GitHubRepository {
type GitHubComparisonResponse (line 106) | interface GitHubComparisonResponse {
type GitHubUser (line 111) | interface GitHubUser {
type GitHubErrorResponse (line 117) | interface GitHubErrorResponse {
class GitHubError (line 127) | class GitHubError extends Error {
method constructor (line 131) | constructor(message: string, statusCode: number, response?: GitHubErro...
class GitHubClient (line 139) | class GitHubClient {
method constructor (line 143) | constructor(config: GitHubConfig) {
method makeRequest (line 147) | private async makeRequest<T>(endpoint: string, options: RequestInit = ...
method parseRepositoryUrl (line 192) | private parseRepositoryUrl(repositoryUrl: string): { owner: string; re...
method normalizeCommit (line 213) | private normalizeCommit(commit: GitHubCommitResponse): GitHubCommit {
method testConnection (line 233) | async testConnection(repositoryUrl: string): Promise<GitHubRepository> {
method getCommits (line 256) | async getCommits(
method getCommitsWithFiles (line 288) | async getCommitsWithFiles(
method getCommitsBetween (line 325) | async getCommitsBetween(
method getCommitsBetweenWithFiles (line 343) | async getCommitsBetweenWithFiles(
method getTags (line 374) | async getTags(
method getReleases (line 392) | async getReleases(
method getLatestRelease (line 410) | async getLatestRelease(repositoryUrl: string): Promise<GitHubRelease> {
method getCommit (line 418) | async getCommit(repositoryUrl: string, sha: string): Promise<GitHubCom...
method getRepository (line 427) | async getRepository(repositoryUrl: string): Promise<GitHubRepository> {
method getUser (line 435) | async getUser(): Promise<GitHubUser> {
method getFileContent (line 442) | async getFileContent(
method delay (line 476) | private delay(ms: number): Promise<void> {
function createGitHubClient (line 484) | function createGitHubClient(config: GitHubConfig): GitHubClient {
type ConventionalCommit (line 491) | interface ConventionalCommit {
function parseConventionalCommit (line 501) | function parseConventionalCommit(message: string | undefined): Conventio...
function groupCommitsByType (line 599) | function groupCommitsByType(commits: GitHubCommit[]): Record<string, Git...
type CommitFilterSettings (line 620) | interface CommitFilterSettings {
function shouldIncludeCommit (line 631) | function shouldIncludeCommit(
FILE: lib/services/jobs/executors/changelog-publish.executor.ts
type ScheduledJobExecutor (line 5) | interface ScheduledJobExecutor {
class ChangelogPublishExecutor (line 9) | class ChangelogPublishExecutor implements ScheduledJobExecutor {
method execute (line 10) | async execute(entityId: string): Promise<void> {
FILE: lib/services/jobs/executors/ssl-renewal.executor.ts
class SslRenewalExecutor (line 12) | class SslRenewalExecutor implements ScheduledJobExecutor {
method execute (line 13) | async execute(entityId: string): Promise<void> {
FILE: lib/services/jobs/executors/telemetry-send.executor.ts
class TelemetrySendExecutor (line 3) | class TelemetrySendExecutor implements ScheduledJobExecutor {
method execute (line 4) | async execute(entityId: string): Promise<void> {
FILE: lib/services/jobs/job-runner.service.ts
class JobRunnerService (line 4) | class JobRunnerService {
method start (line 12) | static start(intervalMs: number = 60000): void {
method stop (line 33) | static stop(): void {
method runJobs (line 51) | private static async runJobs(): Promise<void> {
method chunkArray (line 104) | private static chunkArray<T>(array: T[], size: number): T[][] {
method cleanup (line 115) | static async cleanup(olderThanDays: number = 30): Promise<number> {
method getStatus (line 144) | static getStatus(): { isRunning: boolean; intervalId: NodeJS.Timeout |...
FILE: lib/services/jobs/scheduled-job.service.ts
type CreateScheduledJobParams (line 8) | interface CreateScheduledJobParams {
type ScheduledJobExecutor (line 15) | interface ScheduledJobExecutor {
class ScheduledJobService (line 19) | class ScheduledJobService {
method registerExecutor (line 25) | static registerExecutor(type: ScheduledJobType, executor: ScheduledJob...
method createJob (line 32) | static async createJob(params: CreateScheduledJobParams): Promise<stri...
method cancelJob (line 48) | static async cancelJob(jobId: string, userId: string): Promise<boolean> {
method getDueJobs (line 86) | static async getDueJobs(): Promise<Array<{
method executeJob (line 118) | static async executeJob(jobId: string): Promise<boolean> {
method getJobsForEntity (line 174) | static async getJobsForEntity(entityId: string, type?: ScheduledJobTyp...
method cleanupOldJobs (line 202) | static async cleanupOldJobs(olderThanDays: number = 30): Promise<numbe...
FILE: lib/services/projects/catch-up/catch-up.service.ts
class CatchUpService (line 8) | class CatchUpService {
method getCatchUpData (line 12) | static async getCatchUpData(
method determineSinceDate (line 117) | private static async determineSinceDate(
method getVersionRange (line 216) | private static async getVersionRange(
method categorizeEntries (line 249) | private static categorizeEntries(entries: CatchUpEntry[]): CatchUpSumm...
FILE: lib/services/projects/importing/index.ts
class ChangelogImportService (line 32) | class ChangelogImportService {
method performCompleteImport (line 36) | static async performCompleteImport(
method previewImport (line 72) | static previewImport(content: string): {
method detectFormat (line 88) | static detectFormat(content: string) {
method getImportRecommendations (line 95) | static getImportRecommendations(content: string): {
method validateContent (line 166) | static validateContent(content: string): {
FILE: lib/services/projects/importing/integrations/canny.service.ts
class CannyService (line 10) | class CannyService {
method validateApiKey (line 16) | static async validateApiKey(apiKey: string): Promise<{ valid: boolean;...
method fetchEntries (line 44) | static async fetchEntries(options: CannyImportOptions): Promise<{
method convertEntries (line 105) | static convertEntries(entries: CannyEntry[], options: CannyImportOptio...
FILE: lib/services/projects/importing/markdown-parser.service.ts
class MarkdownParserService (line 11) | class MarkdownParserService {
method detectFormat (line 34) | static detectFormat(content: string): FormatDetectionResult {
method parseChangelog (line 109) | static parseChangelog(content: string): ParsedChangelog {
method parseHeaderInfo (line 222) | private static parseHeaderInfo(headerText: string): {
method parseListItemInfo (line 335) | private static parseListItemInfo(itemText: string): {
method extractTags (line 355) | private static extractTags(text: string): string[] {
method processContentBuffer (line 376) | private static processContentBuffer(contentBuffer: string[]): string {
method looksLikeVersionHeader (line 423) | private static looksLikeVersionHeader(headerText: string): boolean {
method finalizeEntry (line 455) | private static finalizeEntry(
FILE: lib/services/projects/importing/processor.service.ts
type ImportContext (line 12) | interface ImportContext {
type PrismaTransaction (line 20) | type PrismaTransaction = Prisma.TransactionClient;
class ImportProcessorService (line 22) | class ImportProcessorService {
method processImport (line 26) | static async processImport(
method ensureChangelog (line 95) | private static async ensureChangelog(projectId: string) {
method handleReplaceStrategy (line 112) | private static async handleReplaceStrategy(
method handleMergeOrAppendStrategy (line 135) | private static async handleMergeOrAppendStrategy(
method resolveConflicts (line 165) | private static async resolveConflicts(
method importEntries (line 209) | private static async importEntries(
method prepareEntryData (line 265) | private static async prepareEntryData(entry: ValidatedEntry, context: ...
method prepareTags (line 312) | private static async prepareTags(
method getImportHistory (line 347) | static async getImportHistory(projectId: string): Promise<{
method validateImportPermissions (line 386) | static async validateImportPermissions(
FILE: lib/services/projects/importing/validation.service.ts
class ImportValidationService (line 11) | class ImportValidationService {
method validateEntry (line 19) | static validateEntry(entry: ParsedChangelogEntry): ValidatedEntry {
method validateEntries (line 103) | static validateEntries(entries: ParsedChangelogEntry[]): {
method generateMappingSuggestions (line 154) | private static generateMappingSuggestions(entries: ValidatedEntry[]): {
method sanitizeVersion (line 185) | private static sanitizeVersion(version: string): string | null {
method normalizeTag (line 212) | private static normalizeTag(tag: string): string {
method checkConflicts (line 246) | static checkConflicts(
method validateImportOptions (line 275) | static validateImportOptions(options: Partial<ImportOptions>): {
FILE: lib/services/request/changelog-request.ts
type ProcessRequestOptions (line 6) | interface ProcessRequestOptions {
type PrismaTransaction (line 16) | type PrismaTransaction = Omit<typeof db, '$connect' | '$disconnect' | '$...
type DatabaseChangelogRequest (line 19) | interface DatabaseChangelogRequest {
type RequestContext (line 73) | interface RequestContext {
type RequestProcessor (line 79) | interface RequestProcessor {
class DeleteProjectProcessor (line 84) | class DeleteProjectProcessor implements RequestProcessor {
method processRequest (line 85) | async processRequest({tx, request}: RequestContext): Promise<void> {
class DeleteTagProcessor (line 119) | class DeleteTagProcessor implements RequestProcessor {
method processRequest (line 120) | async processRequest({tx, request}: RequestContext): Promise<void> {
method disconnectAndDeleteTag (line 159) | private async disconnectAndDeleteTag(tx: PrismaTransaction, tagId: str...
class DeleteEntryProcessor (line 185) | class DeleteEntryProcessor implements RequestProcessor {
method processRequest (line 186) | async processRequest({tx, request}: RequestContext): Promise<void> {
class AllowPublishProcessor (line 197) | class AllowPublishProcessor implements RequestProcessor {
method processRequest (line 198) | async processRequest({tx, request}: RequestContext): Promise<void> {
class AllowScheduleProcessor (line 218) | class AllowScheduleProcessor implements RequestProcessor {
method processRequest (line 219) | async processRequest({tx, request}: RequestContext): Promise<void> {
class RequestProcessorRegistry (line 267) | class RequestProcessorRegistry {
method getProcessor (line 276) | static getProcessor(type: string): RequestProcessor {
method registerProcessor (line 284) | static registerProcessor(type: string, processor: RequestProcessor): v...
class ChangelogRequestService (line 290) | class ChangelogRequestService {
method processRequest (line 291) | async processRequest(options: ProcessRequestOptions) {
method normalizeSafeOptions (line 358) | private normalizeSafeOptions(options: ProcessRequestOptions) {
method findRequest (line 368) | private async findRequest(tx: PrismaTransaction, requestId: string): P...
method updateRequestStatus (line 403) | private async updateRequestStatus(
method processApprovedRequest (line 436) | private async processApprovedRequest(tx: PrismaTransaction, request: D...
method createAuditLog (line 446) | private async createAuditLog(
FILE: lib/services/search/service.ts
type SearchUser (line 4) | interface SearchUser {
type SearchFilters (line 11) | interface SearchFilters {
type ChangelogEntryResult (line 22) | interface ChangelogEntryResult {
type TagResult (line 40) | interface TagResult {
type SearchResult (line 49) | type SearchResult = ChangelogEntryResult | TagResult;
type SearchResponse (line 51) | interface SearchResponse {
type CustomChangelogWhereInput (line 58) | interface CustomChangelogWhereInput {
type CustomChangelogEntryWhereInput (line 70) | interface CustomChangelogEntryWhereInput extends Omit<Prisma.ChangelogEn...
class ChangelogSearchService (line 74) | class ChangelogSearchService {
method searchAll (line 75) | async searchAll(
method searchEntries (line 115) | private async searchEntries(
method searchTags (line 184) | private async searchTags(
method buildSearchQuery (line 220) | private buildSearchQuery(query: string): string {
method buildEntryWhereConditions (line 235) | private buildEntryWhereConditions(user: SearchUser, filters?: SearchFi...
method truncateContent (line 318) | private truncateContent(content: string, maxLength: number): string {
method searchPublishedOnly (line 334) | async searchPublishedOnly(
FILE: lib/services/slack/logo.tsx
function SlackLogo (line 6) | function SlackLogo({ className }: { className?: string }) {
FILE: lib/services/slack/post-message.ts
type SlackMessageOptions (line 6) | interface SlackMessageOptions {
function postToSlack (line 22) | async function postToSlack(options: SlackMessageOptions) {
FILE: lib/services/sponsor/service.ts
type _VR (line 21) | interface _VR {
type _AR (line 28) | interface _AR {
function _vSig (line 36) | function _vSig(payload: string, signature: string): boolean {
function _vPayload (line 46) | function _vPayload(raw: string, instanceId: string): { valid: boolean; f...
function _fetch (line 60) | async function _fetch(path: string, opts: RequestInit): Promise<Response> {
class SponsorService (line 73) | class SponsorService {
method verifyLicense (line 75) | static async verifyLicense(licenseKey: string, instanceId: string): Pr...
method activateLicense (line 96) | static async activateLicense(licenseKey: string, instanceId: string, i...
method requestChallenge (line 118) | static async requestChallenge(licenseKey: string, instanceId: string):...
method confirmChallenge (line 138) | static async confirmChallenge(challengeId: string, responseCode: strin...
method deactivateLicense (line 160) | static async deactivateLicense(licenseKey: string, instanceId: string)...
method getLicenseStatus (line 171) | static async getLicenseStatus(): Promise<{ active: boolean, features: ...
method getEffectiveMaxEntries (line 215) | static getEffectiveMaxEntries(configMax: number, isLicensed: boolean):...
method storeLicenseActivation (line 219) | static async storeLicenseActivation(
method clearLicenseState (line 234) | static async clearLicenseState(): Promise<void> {
method getStoredLicenseKey (line 246) | static async getStoredLicenseKey(): Promise<string | null> {
method getInstanceId (line 256) | static async getInstanceId(): Promise<string | null> {
method checkEntryAllowed (line 261) | static async checkEntryAllowed(projectId: string): Promise<boolean> {
method needsReverification (line 292) | static async needsReverification(): Promise<boolean> {
method _bgr (line 300) | static async _bgr(): Promise<void> {
method _rs (line 311) | private static async _rs(): Promise<void> {
FILE: lib/services/telemetry/service.ts
class TelemetryService (line 6) | class TelemetryService {
method shouldLog (line 17) | private static shouldLog(): boolean {
method log (line 24) | private static log(...args: unknown[]): void {
method logError (line 33) | private static logError(...args: unknown[]): void {
method logWarn (line 40) | private static logWarn(...args: unknown[]): void {
method getTelemetryConfig (line 47) | static async getTelemetryConfig(): Promise<TelemetryConfig> {
method reactivateInstance (line 75) | static async reactivateInstance(instanceId: string): Promise<void> {
method updateTelemetryConfig (line 98) | static async updateTelemetryConfig(config: TelemetryConfig): Promise<v...
method registerInstance (line 135) | static async registerInstance(): Promise<string> {
method sendTelemetry (line 226) | static async sendTelemetry(data: TelemetryData): Promise<TelemetryResp...
method sendTelemetryNow (line 304) | static async sendTelemetryNow(): Promise<void> {
method deactivateInstance (line 328) | static async deactivateInstance(instanceId: string): Promise<void> {
method getTelemetryStats (line 363) | static async getTelemetryStats(): Promise<TelemetryStats> {
method scheduleTelemetryJob (line 391) | private static async scheduleTelemetryJob(): Promise<void> {
method cancelTelemetryJobs (line 414) | private static async cancelTelemetryJobs(): Promise<void> {
method initialize (line 440) | static async initialize(): Promise<void> {
method shutdown (line 466) | static async shutdown(): Promise<void> {
method testConnection (line 485) | static async testConnection(): Promise<void> {
method mapTelemetryState (line 534) | private static mapTelemetryState(state: TelemetryState): TelemetryConf...
method mapToDbTelemetryState (line 550) | private static mapToDbTelemetryState(state: TelemetryConfig['allowTele...
FILE: lib/types/analytics.ts
type AnalyticsView (line 3) | interface AnalyticsView {
type AnalyticsTimeRange (line 15) | interface AnalyticsTimeRange {
type AnalyticsPeriod (line 20) | type AnalyticsPeriod = '7d' | '30d' | '90d' | '1y';
type DailyAnalytics (line 22) | interface DailyAnalytics {
type CountryAnalytics (line 28) | interface CountryAnalytics {
type EntryAnalytics (line 34) | interface EntryAnalytics {
type ReferrerAnalytics (line 42) | interface ReferrerAnalytics {
type ProjectAnalyticsData (line 48) | interface ProjectAnalyticsData {
type ProjectAnalyticsSummary (line 60) | interface ProjectAnalyticsSummary {
type SystemAnalyticsData (line 68) | interface SystemAnalyticsData extends ProjectAnalyticsData {
type AnalyticsMetric (line 72) | interface AnalyticsMetric {
type AnalyticsChartData (line 79) | interface AnalyticsChartData {
type AnalyticsExportData (line 90) | interface AnalyticsExportData {
type AnalyticsApiResponse (line 110) | interface AnalyticsApiResponse<T = ProjectAnalyticsData> {
type AnalyticsApiError (line 116) | interface AnalyticsApiError {
type AnalyticsQueryParams (line 123) | interface AnalyticsQueryParams {
type TrackingEventData (line 129) | interface TrackingEventData {
FILE: lib/types/auth.ts
type Role (line 3) | enum Role {
type User (line 9) | interface User {
type LoginCredentials (line 20) | interface LoginCredentials {
type AuthTokens (line 25) | interface AuthTokens {
type LoginResponse (line 30) | interface LoginResponse {
type RefreshTokenResponse (line 42) | interface RefreshTokenResponse {
FILE: lib/types/changelog.ts
type RequestType (line 1) | type RequestType = 'DELETE_PROJECT' | 'DELETE_TAG'
type RequestStatus (line 2) | type RequestStatus = 'PENDING' | 'APPROVED' | 'REJECTED'
type ChangelogEntry (line 4) | interface ChangelogEntry {
type ChangelogTag (line 16) | interface ChangelogTag {
type Changelog (line 22) | interface Changelog {
type ChangelogRequest (line 30) | interface ChangelogRequest {
type Project (line 62) | interface Project {
type RequestDataType (line 79) | interface RequestDataType {
type TagColorOption (line 116) | type TagColorOption = {
constant TAG_COLOR_OPTIONS (line 123) | const TAG_COLOR_OPTIONS: TagColorOption[] = [
constant DEFAULT_TAG_COLOR (line 138) | const DEFAULT_TAG_COLOR = '#6b7280';
function getTagColorInfo (line 141) | function getTagColorInfo(color: string | null | undefined): TagColorOpti...
FILE: lib/types/cli/project-api.ts
type ProjectLinkRequest (line 3) | interface ProjectLinkRequest {
type ProjectLinkResponse (line 9) | interface ProjectLinkResponse {
type ProjectUnlinkRequest (line 16) | interface ProjectUnlinkRequest {
type ProjectUnlinkResponse (line 21) | interface ProjectUnlinkResponse {
type CommitData (line 27) | interface CommitData {
type SyncRequest (line 41) | interface SyncRequest {
type SyncResponse (line 53) | interface SyncResponse {
type SyncStatusResponse (line 64) | interface SyncStatusResponse {
type ConventionalCommitType (line 87) | type ConventionalCommitType =
type ProjectSyncMetadata (line 100) | interface ProjectSyncMetadata {
type SyncedCommit (line 112) | interface SyncedCommit {
type ProjectApiError (line 131) | interface ProjectApiError {
type ProjectApiSuccess (line 140) | interface ProjectApiSuccess<T = unknown> {
FILE: lib/types/custom-domains.ts
type CustomDomain (line 1) | interface CustomDomain {
type DomainCertificate (line 18) | interface DomainCertificate {
type DomainBrowserRule (line 39) | interface DomainBrowserRule {
type DomainThrottleConfig (line 49) | interface DomainThrottleConfig {
type DNSVerificationResult (line 59) | interface DNSVerificationResult {
type DNSInstructions (line 67) | interface DNSInstructions {
type AddDomainRequest (line 80) | interface AddDomainRequest {
type AddDomainResponse (line 86) | interface AddDomainResponse {
type VerifyDomainRequest (line 98) | interface VerifyDomainRequest {
type VerifyDomainResponse (line 102) | interface VerifyDomainResponse {
type ListDomainsResponse (line 110) | interface ListDomainsResponse {
type DeleteDomainResponse (line 116) | interface DeleteDomainResponse {
FILE: lib/types/dashboard.ts
type ProjectPreview (line 1) | interface ProjectPreview {
type Activity (line 8) | interface Activity {
type DashboardStats (line 16) | interface DashboardStats {
FILE: lib/types/easypanel.ts
type EasypanelConfig (line 1) | interface EasypanelConfig {
type EasypanelUpdateImagePayload (line 8) | interface EasypanelUpdateImagePayload {
type EasypanelDeployPayload (line 18) | interface EasypanelDeployPayload {
type EasypanelApiResponse (line 26) | interface EasypanelApiResponse<T = unknown> {
type UpdateStatus (line 36) | interface UpdateStatus {
FILE: lib/types/oauth.ts
type OAuthProvider (line 1) | interface OAuthProvider {
type OAuthProviderUpdateData (line 15) | type OAuthProviderUpdateData = {
type OAuthUserInfo (line 31) | interface OAuthUserInfo {
type OAuthCallbackParams (line 39) | interface OAuthCallbackParams {
type OAuthConfig (line 45) | interface OAuthConfig {
FILE: lib/types/projects/catch-up/types.ts
type CatchUpSummary (line 1) | interface CatchUpSummary {
type CatchUpTag (line 7) | interface CatchUpTag {
type CatchUpEntry (line 13) | interface CatchUpEntry {
type CatchUpResponse (line 22) | interface CatchUpResponse {
type SinceOption (line 31) | interface SinceOption {
type CatchUpFilters (line 38) | interface CatchUpFilters {
type AICapabilities (line 44) | interface AICapabilities {
type UserAIStatus (line 54) | interface UserAIStatus {
FILE: lib/types/projects/importing.ts
type ParsedChangelogEntry (line 3) | interface ParsedChangelogEntry {
type ChangelogSection (line 12) | interface ChangelogSection {
type ParsedChangelog (line 20) | interface ParsedChangelog {
type ImportPreview (line 33) | interface ImportPreview {
type ImportOptions (line 48) | interface ImportOptions {
type ImportResult (line 58) | interface ImportResult {
type ImportStats (line 76) | interface ImportStats {
type ImportFormat (line 86) | type ImportFormat =
type FormatDetectionResult (line 92) | interface FormatDetectionResult {
type ValidationError (line 106) | interface ValidationError {
type ValidatedEntry (line 114) | interface ValidatedEntry extends ParsedChangelogEntry {
FILE: lib/types/projects/importing/canny.ts
type CannyUser (line 1) | interface CannyUser {
type CannyBoard (line 11) | interface CannyBoard {
type CannyCategory (line 19) | interface CannyCategory {
type CannyTag (line 27) | interface CannyTag {
type CannyPost (line 34) | interface CannyPost {
type CannyLabel (line 73) | interface CannyLabel {
type CannyEntry (line 81) | interface CannyEntry {
type CannyApiResponse (line 100) | interface CannyApiResponse {
type CannyImportOptions (line 105) | interface CannyImportOptions {
FILE: lib/types/saml.ts
type SAMLProvider (line 1) | interface SAMLProvider {
type SAMLConnection (line 17) | interface SAMLConnection {
type SAMLUserInfo (line 28) | interface SAMLUserInfo {
type SAMLCallbackParams (line 36) | interface SAMLCallbackParams {
type SAMLProviderUpdateData (line 41) | type SAMLProviderUpdateData = {
FILE: lib/types/settings.ts
type ProjectSettings (line 5) | interface ProjectSettings {
type ProjectSettingsFormData (line 15) | type ProjectSettingsFormData = Omit<ProjectSettings, 'id' | 'updatedAt'>
type ProjectSettingsUpdateRequest (line 17) | interface ProjectSettingsUpdateRequest extends Partial<ProjectSettingsFo...
type ProjectSettingsResponse (line 21) | interface ProjectSettingsResponse {
type SettingsPermission (line 28) | interface SettingsPermission {
type SettingsAction (line 33) | type SettingsAction =
type SettingsTab (line 41) | interface SettingsTab {
type TabId (line 48) | type TabId = 'general' | 'access' | 'tags' | 'danger'
type SettingsAPIRoutes (line 65) | interface SettingsAPIRoutes {
type SettingsError (line 82) | interface SettingsError extends Error {
type SettingsErrorCode (line 87) | type SettingsErrorCode =
type SettingsChangeEvent (line 95) | interface SettingsChangeEvent {
type SettingsKey (line 104) | type SettingsKey = keyof ProjectSettings
type SettingsValue (line 105) | type SettingsValue = ProjectSettings[SettingsKey]
type SettingsAuditLog (line 107) | interface SettingsAuditLog {
FILE: lib/types/sso.ts
type OAuthProvider (line 1) | interface OAuthProvider {
type OAuthConnection (line 16) | interface OAuthConnection {
type UserConnectionsResponse (line 28) | interface UserConnectionsResponse {
type ConnectionStatus (line 33) | type ConnectionStatus = 'connected' | 'expired' | 'disabled';
type SsoConnectionsData (line 35) | interface SsoConnectionsData {
type ConnectionApiResponse (line 41) | interface ConnectionApiResponse {
type ProviderStatusInfo (line 48) | interface ProviderStatusInfo {
type ConnectionAnalytics (line 55) | interface ConnectionAnalytics {
FILE: lib/types/telemetry.ts
type TelemetryState (line 1) | type TelemetryState = 'prompt' | 'enabled' | 'disabled';
type TelemetryConfig (line 3) | interface TelemetryConfig {
type TelemetryData (line 8) | interface TelemetryData {
type TelemetryResponse (line 16) | interface TelemetryResponse {
type TelemetryStats (line 22) | interface TelemetryStats {
FILE: lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: lib/utils/ai/prompts.ts
constant SYSTEM_MESSAGES (line 10) | const SYSTEM_MESSAGES: Record<AICompletionType, string> = {
function generatePrompt (line 37) | function generatePrompt(request: AIEditorRequest): string {
function getMessagesForRequest (line 126) | function getMessagesForRequest(request: AIEditorRequest): AIMessage[] {
function getCompletionTypeLabel (line 142) | function getCompletionTypeLabel(type: AICompletionType): string {
function getCompletionTypeDescription (line 166) | function getCompletionTypeDescription(type: AICompletionType): string {
FILE: lib/utils/ai/secton.ts
type SectonConfig (line 11) | interface SectonConfig {
constant DEFAULT_CONFIG (line 20) | const DEFAULT_CONFIG: Partial<SectonConfig> = {
class SectonClient (line 28) | class SectonClient {
method constructor (line 31) | constructor(config: SectonConfig) {
method getModels (line 48) | async getModels(): Promise<AIModel[]> {
method createCompletion (line 95) | async createCompletion(request: Partial<CompletionRequest>): Promise<C...
method generateText (line 229) | async generateText(prompt: string, options: Partial<CompletionRequest>...
method validateApiKey (line 270) | async validateApiKey(): Promise<boolean> {
function createSectonClient (line 287) | function createSectonClient(config: SectonConfig): SectonClient {
FILE: lib/utils/ai/types.ts
type AICompletionType (line 6) | enum AICompletionType {
type AIEditorRequest (line 17) | interface AIEditorRequest {
type AIEditorResult (line 33) | interface AIEditorResult {
type AIMessage (line 48) | interface AIMessage {
type CompletionRequest (line 54) | interface CompletionRequest {
type CompletionResponse (line 62) | interface CompletionResponse {
type AIModel (line 74) | interface AIModel {
class AIError (line 83) | class AIError extends Error {
method constructor (line 87) | constructor(message: string, statusCode: number, details?: unknown) {
FILE: lib/utils/analytics.ts
type AnalyticsTimeRange (line 4) | interface AnalyticsTimeRange {
type ProjectAnalytics (line 9) | interface ProjectAnalytics {
type SystemAnalytics (line 23) | interface SystemAnalytics extends ProjectAnalytics {
function getTimeRange (line 35) | function getTimeRange(period: '7d' | '30d' | '90d' | '1y'): AnalyticsTim...
function getProjectAnalytics (line 60) | async function getProjectAnalytics(
function getSystemAnalytics (line 203) | async function getSystemAnalytics(timeRange: AnalyticsTimeRange): Promis...
FILE: lib/utils/api.ts
function fetchWithAuth (line 1) | async function fetchWithAuth(
FILE: lib/utils/auditLog.ts
type AuditLogDetails (line 5) | interface AuditLogDetails {
function createAuditLog (line 16) | async function createAuditLog(
FILE: lib/utils/changelog.ts
type ChangelogEntryInput (line 19) | type ChangelogEntryInput = z.infer<typeof changelogEntrySchema>
function validateAuthAndGetUser (line 22) | async function validateAuthAndGetUser() {
function sendError (line 57) | function sendError(message: string, status: number = 400) {
function sendSuccess (line 67) | function sendSuccess(data: unknown, status: number = 200) {
constant EXCERPT_LENGTH (line 78) | const EXCERPT_LENGTH = 300;
function generateExcerpt (line 80) | function generateExcerpt(content: string): string {
FILE: lib/utils/cookies.ts
function shouldUseSecureCookies (line 9) | function shouldUseSecureCookies(request: Request): boolean {
FILE: lib/utils/docker.ts
type DockerImageConfig (line 1) | interface DockerImageConfig {
constant DEFAULT_DOCKER_CONFIG (line 8) | const DEFAULT_DOCKER_CONFIG: DockerImageConfig = {
constant DEBUG_MODE (line 17) | const DEBUG_MODE = false;
constant DEBUG_IMAGE (line 18) | const DEBUG_IMAGE = 'traefik/whoami';
function generateDockerImage (line 23) | function generateDockerImage(
function isDebugMode (line 42) | function isDebugMode(): boolean {
function getDebugImage (line 49) | function getDebugImage(): string {
function generateDockerImageWithDebugInfo (line 56) | function generateDockerImageWithDebugInfo(
function parseDockerImage (line 83) | function parseDockerImage(imageString: string): {
function validateDockerImage (line 132) | function validateDockerImage(imageString: string): {
function getImageVariants (line 172) | function getImageVariants(
function logDockerDebugInfo (line 197) | function logDockerDebugInfo(version: string): void {
FILE: lib/utils/encryption.ts
constant ENCRYPTION_KEY_HEX (line 4) | const ENCRYPTION_KEY_HEX = process.env.GITHUB_ENCRYPTION_KEY || 'default...
constant ALGORITHM (line 5) | const ALGORITHM = 'aes-256-cbc';
constant IV_LENGTH (line 6) | const IV_LENGTH = 16;
function getKeyBuffer (line 9) | function getKeyBuffer(): Buffer {
function encryptToken (line 34) | function encryptToken(token: string): string {
function decryptToken (line 52) | function decryptToken(encryptedToken: string): string {
FILE: lib/utils/format-date.ts
type DateInput (line 17) | type DateInput = string | Date | number
function toDate (line 19) | function toDate(input: DateInput): Date {
function formatDateLong (line 26) | function formatDateLong(input: DateInput, timeZone = 'UTC'): string {
function formatDateMedium (line 36) | function formatDateMedium(input: DateInput, timeZone = 'UTC'): string {
function formatDateShort (line 46) | function formatDateShort(input: DateInput, timeZone = 'UTC'): string {
function formatDateTime (line 56) | function formatDateTime(input: DateInput, timeZone = 'UTC'): string {
function formatDateTimeMedium (line 68) | function formatDateTimeMedium(input: DateInput, timeZone = 'UTC'): string {
function formatTime (line 80) | function formatTime(input: DateInput, timeZone = 'UTC'): string {
function formatDateTimeWithZone (line 89) | function formatDateTimeWithZone(input: DateInput, timeZone = 'UTC'): str...
FILE: lib/utils/gravatar.ts
function getGravatarUrl (line 3) | function getGravatarUrl(email: string, size: number = 80): string {
FILE: lib/utils/rate-limit.ts
type RateLimitEntry (line 6) | interface RateLimitEntry {
type RateLimitResult (line 21) | interface RateLimitResult {
function checkRateLimit (line 34) | function checkRateLimit(
function getClientIp (line 62) | function getClientIp(request: Request): string {
FILE: lib/utils/text.ts
function truncateText (line 15) | function truncateText(text: string, maxLength: number = 60): string {
function truncateMarkdown (line 32) | function truncateMarkdown(content: string, charLimit: number = 400): str...
FILE: prisma/migrations/20250215042251_init_authentication/migration.sql
type "User" (line 5) | CREATE TABLE "User" (
type "RefreshToken" (line 18) | CREATE TABLE "RefreshToken" (
type "Settings" (line 30) | CREATE TABLE "Settings" (
type "User" (line 41) | CREATE UNIQUE INDEX "User_email_key" ON "User"("email")
type "RefreshToken" (line 44) | CREATE UNIQUE INDEX "RefreshToken_token_key" ON "RefreshToken"("token")
type "RefreshToken" (line 47) | CREATE INDEX "RefreshToken_userId_idx" ON "RefreshToken"("userId")
type "Settings" (line 50) | CREATE UNIQUE INDEX "Settings_userId_key" ON "Settings"("userId")
type "Settings" (line 53) | CREATE INDEX "Settings_userId_idx" ON "Settings"("userId")
FILE: prisma/migrations/20250215051621_add_invitation_links/migration.sql
type "InvitationLink" (line 2) | CREATE TABLE "InvitationLink" (
type "InvitationLink" (line 16) | CREATE UNIQUE INDEX "InvitationLink_token_key" ON "InvitationLink"("token")
type "InvitationLink" (line 19) | CREATE INDEX "InvitationLink_token_idx" ON "InvitationLink"("token")
type "InvitationLink" (line 22) | CREATE INDEX "InvitationLink_email_idx" ON "InvitationLink"("email")
FILE: prisma/migrations/20250215075317_add_changelog_models/migration.sql
type "Project" (line 8) | CREATE TABLE "Project" (
type "Changelog" (line 18) | CREATE TABLE "Changelog" (
type "ChangelogEntry" (line 28) | CREATE TABLE "ChangelogEntry" (
type "ChangelogTag" (line 42) | CREATE TABLE "ChangelogTag" (
type "ChangelogRequest" (line 52) | CREATE TABLE "ChangelogRequest" (
type "_ChangelogEntryToChangelogTag" (line 68) | CREATE TABLE "_ChangelogEntryToChangelogTag" (
type "Changelog" (line 76) | CREATE UNIQUE INDEX "Changelog_projectId_key" ON "Changelog"("projectId")
type "ChangelogEntry" (line 79) | CREATE INDEX "ChangelogEntry_changelogId_idx" ON "ChangelogEntry"("chang...
type "ChangelogTag" (line 82) | CREATE UNIQUE INDEX "ChangelogTag_name_key" ON "ChangelogTag"("name")
type "ChangelogRequest" (line 85) | CREATE INDEX "ChangelogRequest_staffId_idx" ON "ChangelogRequest"("staff...
type "ChangelogRequest" (line 88) | CREATE INDEX "ChangelogRequest_adminId_idx" ON "ChangelogRequest"("admin...
type "ChangelogRequest" (line 91) | CREATE INDEX "ChangelogRequest_changelogEntryId_idx" ON "ChangelogReques...
type "ChangelogRequest" (line 94) | CREATE INDEX "ChangelogRequest_changelogTagId_idx" ON "ChangelogRequest"...
type "_ChangelogEntryToChangelogTag" (line 97) | CREATE INDEX "_ChangelogEntryToChangelogTag_B_index" ON "_ChangelogEntry...
FILE: prisma/migrations/20250215232640_add_api_keys/migration.sql
type "ApiKey" (line 2) | CREATE TABLE "ApiKey" (
type "ApiKey" (line 17) | CREATE UNIQUE INDEX "ApiKey_key_key" ON "ApiKey"("key")
type "ApiKey" (line 20) | CREATE INDEX "ApiKey_userId_idx" ON "ApiKey"("userId")
type "ApiKey" (line 23) | CREATE INDEX "ApiKey_key_idx" ON "ApiKey"("key")
FILE: prisma/migrations/20250216005356_add_audit_logs/migration.sql
type "AuditLog" (line 2) | CREATE TABLE "AuditLog" (
type "AuditLog" (line 14) | CREATE INDEX "AuditLog_userId_idx" ON "AuditLog"("userId")
type "AuditLog" (line 17) | CREATE INDEX "AuditLog_targetUserId_idx" ON "AuditLog"("targetUserId")
type "AuditLog" (line 20) | CREATE INDEX "AuditLog_action_idx" ON "AuditLog"("action")
type "AuditLog" (line 23) | CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt")
FILE: prisma/migrations/20250216050812_add_system_configuration/migration.sql
type "SystemConfig" (line 2) | CREATE TABLE "SystemConfig" (
FILE: prisma/migrations/20250216053424_update_changelog_requests_schema/migration.sql
type "ChangelogRequest" (line 26) | CREATE INDEX "ChangelogRequest_projectId_idx" ON "ChangelogRequest"("pro...
type "ChangelogRequest" (line 29) | CREATE INDEX "ChangelogRequest_status_idx" ON "ChangelogRequest"("status")
FILE: prisma/migrations/20250305225249_add_oauth/migration.sql
type "OAuthProvider" (line 2) | CREATE TABLE "OAuthProvider" (
type "OAuthConnection" (line 21) | CREATE TABLE "OAuthConnection" (
type "OAuthConnection" (line 36) | CREATE INDEX "OAuthConnection_userId_idx" ON "OAuthConnection"("userId")
type "OAuthConnection" (line 39) | CREATE INDEX "OAuthConnection_providerId_idx" ON "OAuthConnection"("prov...
type "OAuthConnection" (line 42) | CREATE UNIQUE INDEX "OAuthConnection_providerId_providerUserId_key" ON "...
type "OAuthConnection" (line 45) | CREATE UNIQUE INDEX "OAuthConnection_providerId_userId_key" ON "OAuthCon...
FILE: prisma/migrations/20250418044737_add_email_integration/migration.sql
type "EmailConfig" (line 2) | CREATE TABLE "EmailConfig" (
type "EmailConfig" (line 24) | CREATE UNIQUE INDEX "EmailConfig_projectId_key" ON "EmailConfig"("projec...
FILE: prisma/migrations/20250418045834_add_email_logs/migration.sql
type "EmailLog" (line 2) | CREATE TABLE "EmailLog" (
FILE: prisma/migrations/20250418163601_add_email_subscription/migration.sql
type "EmailSubscriber" (line 5) | CREATE TABLE "EmailSubscriber" (
type "ProjectSubscription" (line 19) | CREATE TABLE "ProjectSubscription" (
type "EmailSubscriber" (line 31) | CREATE UNIQUE INDEX "EmailSubscriber_unsubscribeToken_key" ON "EmailSubs...
type "EmailSubscriber" (line 34) | CREATE INDEX "EmailSubscriber_email_idx" ON "EmailSubscriber"("email")
type "EmailSubscriber" (line 37) | CREATE INDEX "EmailSubscriber_unsubscribeToken_idx" ON "EmailSubscriber"...
type "EmailSubscriber" (line 40) | CREATE UNIQUE INDEX "EmailSubscriber_email_key" ON "EmailSubscriber"("em...
type "ProjectSubscription" (line 43) | CREATE INDEX "ProjectSubscription_projectId_idx" ON "ProjectSubscription...
type "ProjectSubscription" (line 46) | CREATE INDEX "ProjectSubscription_subscriberId_idx" ON "ProjectSubscript...
type "ProjectSubscription" (line 49) | CREATE UNIQUE INDEX "ProjectSubscription_projectId_subscriberId_key" ON ...
FILE: prisma/migrations/20250419000001_add_password_reset/migration.sql
type "PasswordReset" (line 2) | CREATE TABLE "PasswordReset" (
type "PasswordReset" (line 15) | CREATE UNIQUE INDEX "PasswordReset_token_key" ON "PasswordReset"("token")
type "PasswordReset" (line 18) | CREATE INDEX "PasswordReset_token_idx" ON "PasswordReset"("token")
type "PasswordReset" (line 21) | CREATE INDEX "PasswordReset_userId_idx" ON "PasswordReset"("userId")
type "PasswordReset" (line 24) | CREATE INDEX "PasswordReset_email_idx" ON "PasswordReset"("email")
FILE: prisma/migrations/20250504203021_passkey_support/migration.sql
type "Passkey" (line 2) | CREATE TABLE "Passkey" (
type "Passkey" (line 17) | CREATE UNIQUE INDEX "Passkey_credentialId_key" ON "Passkey"("credentialId")
type "Passkey" (line 20) | CREATE INDEX "Passkey_userId_idx" ON "Passkey"("userId")
type "Passkey" (line 23) | CREATE INDEX "Passkey_credentialId_idx" ON "Passkey"("credentialId")
FILE: prisma/migrations/20250504212400_add_two_factor_session/migration.sql
type "TwoFactorSession" (line 2) | CREATE TABLE "TwoFactorSession" (
type "TwoFactorSession" (line 13) | CREATE INDEX "TwoFactorSession_userId_idx" ON "TwoFactorSession"("userId")
type "TwoFactorSession" (line 16) | CREATE INDEX "TwoFactorSession_expiresAt_idx" ON "TwoFactorSession"("exp...
FILE: prisma/migrations/20250512000000_add_github_integration/migration.sql
type "GitHubIntegration" (line 2) | CREATE TABLE "GitHubIntegration" (
type "GitHubIntegration" (line 23) | CREATE UNIQUE INDEX "GitHubIntegration_projectId_key" ON "GitHubIntegrat...
type "GitHubIntegration" (line 26) | CREATE INDEX "GitHubIntegration_projectId_idx" ON "GitHubIntegration"("p...
type "GitHubIntegration" (line 29) | CREATE INDEX "GitHubIntegration_enabled_idx" ON "GitHubIntegration"("ena...
FILE: prisma/migrations/20250613040144_changelog_analytics/migration.sql
type "PublicChangelogAnalytics" (line 2) | CREATE TABLE "PublicChangelogAnalytics"
type "PublicChangelogAnalytics" (line 18) | CREATE INDEX "PublicChangelogAnalytics_projectId_idx" ON "PublicChangelo...
type "PublicChangelogAnalytics" (line 21) | CREATE INDEX "PublicChangelogAnalytics_changelogEntryId_idx" ON "PublicC...
type "PublicChangelogAnalytics" (line 24) | CREATE INDEX "PublicChangelogAnalytics_viewedAt_idx" ON "PublicChangelog...
type "PublicChangelogAnalytics" (line 27) | CREATE INDEX "PublicChangelogAnalytics_country_idx" ON "PublicChangelogA...
type "PublicChangelogAnalytics" (line 30) | CREATE INDEX "PublicChangelogAnalytics_sessionHash_idx" ON "PublicChange...
FILE: prisma/migrations/20250624024525_add_custom_domains/migration.sql
type "custom_domains" (line 5) | CREATE TABLE "custom_domains" (
type "custom_domains" (line 19) | CREATE UNIQUE INDEX "custom_domains_domain_key" ON "custom_domains"("dom...
type "custom_domains" (line 22) | CREATE UNIQUE INDEX "custom_domains_verificationToken_key" ON "custom_do...
type "custom_domains" (line 25) | CREATE INDEX "custom_domains_domain_idx" ON "custom_domains"("domain")
type "custom_domains" (line 28) | CREATE INDEX "custom_domains_projectId_idx" ON "custom_domains"("project...
type "custom_domains" (line 31) | CREATE INDEX "custom_domains_userId_idx" ON "custom_domains"("userId")
type "custom_domains" (line 34) | CREATE INDEX "custom_domains_verified_idx" ON "custom_domains"("verified")
FILE: prisma/migrations/20250625030954_add_custom_domains_to_email_notifications/migration.sql
type idx_project_subscriptions_custom_domain (line 5) | CREATE INDEX idx_project_subscriptions_custom_domain ON "ProjectSubscrip...
FILE: prisma/migrations/20250625120000_add_changelog_scheduling/migration.sql
type "ChangelogEntry" (line 5) | CREATE INDEX "ChangelogEntry_scheduledAt_idx" ON "ChangelogEntry"("sched...
type "ChangelogEntry" (line 6) | CREATE INDEX "ChangelogEntry_scheduled_published_idx" ON "ChangelogEntry...
type "ScheduledJob" (line 15) | CREATE TABLE "ScheduledJob" (
type "ScheduledJob" (line 32) | CREATE INDEX "ScheduledJob_type_idx" ON "ScheduledJob"("type")
type "ScheduledJob" (line 33) | CREATE INDEX "ScheduledJob_entityId_idx" ON "ScheduledJob"("entityId")
type "ScheduledJob" (line 34) | CREATE INDEX "ScheduledJob_scheduledAt_idx" ON "ScheduledJob"("scheduled...
type "ScheduledJob" (line 35) | CREATE INDEX "ScheduledJob_status_idx" ON "ScheduledJob"("status")
type "ScheduledJob" (line 36) | CREATE INDEX "ScheduledJob_scheduled_pending_idx" ON "ScheduledJob"("sch...
FILE: prisma/migrations/20250627020135_add_color_field_to_changelog_tag/migration.sql
type "ProjectSubscription" (line 21) | CREATE INDEX "ProjectSubscription_customDomain_idx" ON "ProjectSubscript...
FILE: prisma/migrations/20250628171029_add_telemetry/migration.sql
type "SystemConfig" (line 19) | CREATE UNIQUE INDEX "SystemConfig_telemetryInstanceId_key" ON "SystemCon...
FILE: prisma/migrations/20250702092443_add_cli_auth_codes/migration.sql
type "CliAuthCode" (line 2) | CREATE TABLE "CliAuthCode" (
type "CliAuthCode" (line 15) | CREATE UNIQUE INDEX "CliAuthCode_code_key" ON "CliAuthCode"("code")
type "CliAuthCode" (line 16) | CREATE INDEX "CliAuthCode_code_idx" ON "CliAuthCode"("code")
type "CliAuthCode" (line 17) | CREATE INDEX "CliAuthCode_userId_idx" ON "CliAuthCode"("userId")
type "CliAuthCode" (line 18) | CREATE INDEX "CliAuthCode_expiresAt_idx" ON "CliAuthCode"("expiresAt")
FILE: prisma/migrations/20250702121802_add_synced_cli_migrations_and_metadata/migration.sql
type "ProjectSyncMetadata" (line 2) | CREATE TABLE "ProjectSyncMetadata" (
type "SyncedCommit" (line 17) | CREATE TABLE "SyncedCommit" (
type "ProjectSyncMetadata" (line 38) | CREATE UNIQUE INDEX "ProjectSyncMetadata_projectId_key" ON "ProjectSyncM...
type "ProjectSyncMetadata" (line 41) | CREATE INDEX "ProjectSyncMetadata_projectId_idx" ON "ProjectSyncMetadata...
type "ProjectSyncMetadata" (line 44) | CREATE INDEX "ProjectSyncMetadata_lastSyncedAt_idx" ON "ProjectSyncMetad...
type "SyncedCommit" (line 47) | CREATE INDEX "SyncedCommit_projectId_idx" ON "SyncedCommit"("projectId")
type "SyncedCommit" (line 50) | CREATE INDEX "SyncedCommit_commitHash_idx" ON "SyncedCommit"("commitHash")
type "SyncedCommit" (line 53) | CREATE INDEX "SyncedCommit_syncedAt_idx" ON "SyncedCommit"("syncedAt")
type "SyncedCommit" (line 56) | CREATE INDEX "SyncedCommit_conventionalType_idx" ON "SyncedCommit"("conv...
type "SyncedCommit" (line 59) | CREATE INDEX "SyncedCommit_branch_idx" ON "SyncedCommit"("branch")
type "SyncedCommit" (line 62) | CREATE UNIQUE INDEX "SyncedCommit_projectId_commitHash_key" ON "SyncedCo...
FILE: prisma/migrations/20250703092000_fix_missing_changelogentryid_column/migration.sql
type "ScheduledJob" (line 8) | CREATE INDEX IF NOT EXISTS "ScheduledJob_changelogEntryId_idx" ON "Sched...
FILE: prisma/migrations/20251111210147_add_api_key_and_widgets/migration.sql
type "Widget" (line 15) | CREATE TABLE "Widget" (
type "Widget" (line 30) | CREATE INDEX "Widget_projectId_idx" ON "Widget"("projectId")
type "Widget" (line 33) | CREATE INDEX "Widget_projectId_isActive_idx" ON "Widget"("projectId", "i...
type "ApiKey" (line 36) | CREATE INDEX "ApiKey_projectId_idx" ON "ApiKey"("projectId")
FILE: prisma/migrations/20251127011835_add_slack_integration/migration.sql
type "SlackIntegration" (line 2) | CREATE TABLE "SlackIntegration" (
type "SlackIntegration" (line 26) | CREATE UNIQUE INDEX "SlackIntegration_projectId_key" ON "SlackIntegratio...
type "SlackIntegration" (line 29) | CREATE INDEX "SlackIntegration_projectId_idx" ON "SlackIntegration"("pro...
type "SlackIntegration" (line 32) | CREATE INDEX "SlackIntegration_teamId_idx" ON "SlackIntegration"("teamId")
FILE: prisma/seed.ts
function generateVersionSequence (line 8) | function generateVersionSequence(count: number): string[] {
function generateSequentialDates (line 40) | function generateSequentialDates(count: number, startDate = new Date('20...
function generateMarkdownContent (line 53) | function generateMarkdownContent(): string {
function main (line 112) | async function main() {
FILE: proxy.ts
type IpConfig (line 9) | interface IpConfig { enabled: boolean; whitelist: string[] }
constant IP_CACHE_TTL_MS (line 12) | const IP_CACHE_TTL_MS = 30_000
function getIpConfig (line 14) | async function getIpConfig(baseUrl: string): Promise<IpConfig> {
function getClientIp (line 32) | function getClientIp(req: NextRequest): string {
function ipMatchesCidr (line 38) | function ipMatchesCidr(ip: string, cidr: string): boolean {
constant IP_WHITELIST_PUBLIC_PREFIXES (line 51) | const IP_WHITELIST_PUBLIC_PREFIXES = [
function isIpProtectedPath (line 58) | function isIpProtectedPath(pathname: string): boolean {
constant ALWAYS_PUBLIC_PATHS (line 65) | const ALWAYS_PUBLIC_PATHS = [
constant FUCK_OFF_THRESHOLD (line 78) | const FUCK_OFF_THRESHOLD = 5 // I love this variable, sorry professional...
constant DEFAULT_ALLOWED_EXTERNAL_DOMAINS (line 81) | const DEFAULT_ALLOWED_EXTERNAL_DOMAINS = [
function getAllowedExternalDomains (line 92) | function getAllowedExternalDomains(): string[] {
type DomainSecurityConfig (line 99) | interface DomainSecurityConfig {
constant CACHE_TTL_MS (line 105) | const CACHE_TTL_MS = 60_000 // 60 seconds
function getDomainSecurityConfig (line 107) | async function getDomainSecurityConfig(hostname: string): Promise<Domain...
constant PUBLIC_API_PATHS (line 139) | const PUBLIC_API_PATHS = [
constant AUTH_ROUTES (line 146) | const AUTH_ROUTES = ['/login', '/register', '/setup']
constant PUBLIC_CONTENT_PATHS (line 148) | const PUBLIC_CONTENT_PATHS = [
function isAlwaysPublicPath (line 158) | function isAlwaysPublicPath(pathname: string): boolean {
function isPublicApiPath (line 163) | function isPublicApiPath(pathname: string): boolean {
function isPublicContentPath (line 167) | function isPublicContentPath(pathname: string): boolean {
function isAllowedExternalDomain (line 171) | function isAllowedExternalDomain(hostname: string): boolean {
function isCustomDomain (line 178) | function isCustomDomain(hostname: string): boolean {
function normalizeHostname (line 193) | function normalizeHostname(rawHost: string): string {
function handleCustomDomain (line 198) | function handleCustomDomain(request: NextRequest, hostname: string, path...
function isSetupComplete (line 204) | async function isSetupComplete(): Promise<boolean> {
function proxy (line 226) | async function proxy(request: NextRequest) {
FILE: scripts/api/generateSwagger.js
function adopt (line 14) | function adopt(value) { return value instanceof P ? value : new P(functi...
function fulfilled (line 16) | function fulfilled(value) { try { step(generator.next(value)); } catch (...
function rejected (line 17) | function rejected(value) { try { step(generator["throw"](value)); } catc...
function step (line 18) | function step(result) { result.done ? resolve(result.value) : adopt(resu...
function verb (line 25) | function verb(n) { return function (v) { return step([n, v]); }; }
function step (line 26) | function step(op) {
function generateSwagger (line 56) | function generateSwagger() {
FILE: scripts/api/generateSwagger.ts
type CommentTag (line 12) | interface CommentTag {
type CommentBlock (line 23) | interface CommentBlock {
type OpenAPIDocumentWithExtensions (line 31) | interface OpenAPIDocumentWithExtensions extends OpenAPIV3.Document {
type HttpMethod (line 35) | type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
type SwaggerRoute (line 37) | interface SwaggerRoute {
type RouteReport (line 45) | interface RouteReport {
function pathToSectionTitle (line 58) | function pathToSectionTitle(path: string): string {
function parseSchema (line 68) | function parseSchema(schema: Record<string, unknown>): OpenAPIV3.SchemaO...
function tryParseJSON (line 132) | function tryParseJSON(str: string, defaultValue: unknown = undefined): u...
function extractFullDescription (line 141) | function extractFullDescription(tag: CommentTag): string {
function processRouteOperation (line 150) | function processRouteOperation(route: SwaggerRoute, routeDocs: CommentBl...
function processRouteFiles (line 285) | async function processRouteFiles(
function generateSwaggerDocs (line 359) | async function generateSwaggerDocs() {
FILE: scripts/ftb/server.js
constant PORT (line 6) | const PORT = process.env.PORT || 3000;
FILE: scripts/maintenance/server.js
constant PORT (line 5) | const PORT = process.env.PORT || 3000;
constant MAINTENANCE_HTML_PATH (line 6) | const MAINTENANCE_HTML_PATH = path.join(__dirname, '../maintenance', 'in...
FILE: scripts/utils/scan.ts
type PageInfo (line 7) | interface PageInfo {
type ScreenshotConfig (line 16) | interface ScreenshotConfig {
type RouteTreeNode (line 43) | interface RouteTreeNode {
class NextJSPageScanner (line 51) | class NextJSPageScanner {
method constructor (line 55) | constructor(appDir: string = './app', screenshotConfig?: ScreenshotCon...
method scanPages (line 68) | public async scanPages(): Promise<RouteTreeNode[]> {
method ensureDirectoryExists (line 79) | private ensureDirectoryExists(dirPath: string): void {
method takeScreenshots (line 85) | private async takeScreenshots(pages: PageInfo[]): Promise<void> {
method filterScreenshotablePages (line 122) | private filterScreenshotablePages(pages: PageInfo[]): PageInfo[] {
method extractRequiredParams (line 139) | private extractRequiredParams(segments: string[]): string[] {
method hasRequiredParams (line 145) | private hasRequiredParams(requiredParams: string[]): boolean {
method buildRouteUrl (line 153) | private buildRouteUrl(pageInfo: PageInfo): string {
method performLogin (line 172) | private async performLogin(page: Page): Promise<void> {
method screenshotPage (line 196) | private async screenshotPage(page: Page, pageInfo: PageInfo): Promise<...
method generateScreenshotFilename (line 231) | private generateScreenshotFilename(routePath: string): string {
method scanDirectory (line 242) | private scanDirectory(dirPath: string, relativePath: string, pages: Pa...
method shouldSkipDirectory (line 263) | private shouldSkipDirectory(dirName: string): boolean {
method analyzeFile (line 277) | private analyzeFile(fullPath: string, relativePath: string): PageInfo ...
method checkIfDynamic (line 335) | private checkIfDynamic(segments: string[]): boolean {
method buildRouteTree (line 342) | private buildRouteTree(pages: PageInfo[]): RouteTreeNode[] {
method generateTreeView (line 389) | public generateTreeView(nodes: RouteTreeNode[], prefix: string = ''): ...
function main (line 437) | async function main(): Promise<void> {
FILE: scripts/widget/build.ts
function copyDirectory (line 13) | function copyDirectory(src: string, dest: string) {
function buildWidget (line 36) | async function buildWidget() {
FILE: widgets/changelog/index.js
class ChangelogWidget (line 1) | class ChangelogWidget {
method constructor (line 2) | constructor(container, options) {
method getScriptOptions (line 22) | getScriptOptions() {
method updatePosition (line 39) | updatePosition() {
method addStyles (line 70) | addStyles() {
method init (line 312) | async init() {
method setupKeyboardNavigation (line 342) | setupKeyboardNavigation() {
method setupTriggerButton (line 366) | setupTriggerButton() {
method render (line 391) | render() {
method renderLoading (line 433) | renderLoading() {
method loadEntries (line 442) | async loadEntries() {
method renderEntries (line 463) | renderEntries(entries) {
method renderError (line 504) | renderError() {
method open (line 520) | open() {
method close (line 538) | close() {
method toggle (line 561) | toggle() {
FILE: widgets/variants/announcement.js
class ChangelogAnnouncementWidget (line 6) | class ChangelogAnnouncementWidget {
method constructor (line 7) | constructor(container, options) {
method loadStyles (line 27) | async loadStyles() {
method checkDismissed (line 60) | checkDismissed() {
method markDismissed (line 71) | markDismissed() {
method init (line 79) | async init() {
method loadLatestEntry (line 114) | async loadLatestEntry() {
method render (line 140) | render() {
method dismiss (line 214) | dismiss() {
method hide (line 222) | hide() {
method show (line 233) | show() {
FILE: widgets/variants/classic.js
class ChangelogWidget (line 6) | class ChangelogWidget {
method constructor (line 7) | constructor(container, options) {
method loadStyles (line 26) | async loadStyles() {
method updatePosition (line 58) | updatePosition() {
method init (line 71) | async init() {
method setupKeyboardNavigation (line 108) | setupKeyboardNavigation() {
method setupTriggerButton (line 136) | setupTriggerButton() {
method loadEntries (line 148) | async loadEntries() {
method render (line 173) | render() {
method createHeader (line 186) | createHeader() {
method createFooter (line 206) | createFooter() {
method renderLoading (line 225) | renderLoading() {
method renderError (line 232) | renderError() {
method renderEntries (line 244) | renderEntries(entries) {
method open (line 313) | open() {
method close (line 334) | close() {
method toggle (line 351) | toggle() {
FILE: widgets/variants/floating.js
class ChangelogFloatingWidget (line 6) | class ChangelogFloatingWidget {
method constructor (line 7) | constructor(container, options) {
method loadStyles (line 29) | async loadStyles() {
method init (line 63) | async init() {
method render (line 82) | render() {
method attachEventListeners (line 227) | attachEventListeners() {
method loadEntries (line 254) | async loadEntries() {
method updateBadge (line 286) | updateBadge() {
method renderEntries (line 297) | renderEntries() {
method open (line 432) | open() {
method close (line 457) | close() {
method toggle (line 475) | toggle() {
FILE: widgets/variants/modal.js
class ChangelogModalWidget (line 6) | class ChangelogModalWidget {
method constructor (line 7) | constructor(container, options) {
method loadStyles (line 25) | async loadStyles() {
method init (line 58) | async init() {
method setupKeyboardNavigation (line 90) | setupKeyboardNavigation() {
method setupTriggerButton (line 118) | setupTriggerButton() {
method loadEntries (line 130) | async loadEntries() {
method render (line 155) | render() {
method createHeader (line 180) | createHeader() {
method createFooter (line 200) | createFooter() {
method renderLoading (line 219) | renderLoading() {
method renderError (line 226) | renderError() {
method renderEntries (line 238) | renderEntries(entries) {
method open (line 318) | open() {
method close (line 344) | close() {
method toggle (line 364) | toggle() {
Condensed preview — 624 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (6,342K chars).
[
{
"path": ".dockerignore",
"chars": 344,
"preview": ".git\n.github\n.next\nnode_modules\n.env\n.env.*\n*.log\nREADME.md\n.DS_Store\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.id"
},
{
"path": ".github/FUNDING.yml",
"chars": 834,
"preview": "# These are supported funding model platforms\n\ngithub: supernova3339 # Replace with up to 4 GitHub Sponsors-enabled user"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.yml",
"chars": 6923,
"preview": "name: 🐛 Bug Report\ndescription: Found a bug? Help us squash it! Every bug report makes Changerawr better.\ntitle: \"[Bug]:"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.yml",
"chars": 4916,
"preview": "name: 🦖 Feature Request\ndescription: Got an idea to make Changerawr even better? We'd love to hear it!\ntitle: \"[Feature]"
},
{
"path": ".github/workflows/build-platform.yml",
"chars": 3082,
"preview": "name: Build Platform Image\n\non:\n workflow_call:\n inputs:\n platform:\n required: true\n type: string"
},
{
"path": ".github/workflows/docker-build.yml",
"chars": 4298,
"preview": "name: Build and Push Docker Image\n\non:\n push:\n tags:\n - 'v*'\n workflow_dispatch:\n inputs:\n version:\n "
},
{
"path": ".gitignore",
"chars": 1246,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n.idea/dbnavigator.xml\n\n# dependen"
},
{
"path": ".idea/.gitignore",
"chars": 176,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local sto"
},
{
"path": ".idea/changerawr.iml",
"chars": 458,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n <component name=\"NewModuleRootManager\">\n"
},
{
"path": ".idea/dataSources.xml",
"chars": 921,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"DataSourceManagerImpl\" format=\"xml\" mult"
},
{
"path": ".idea/data_source_mapping.xml",
"chars": 289,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"DataSourcePerFileMappings\">\n <file ur"
},
{
"path": ".idea/db-forest-config.xml",
"chars": 300,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"db-tree-configuration\">\n <option name"
},
{
"path": ".idea/discord.xml",
"chars": 222,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"DiscordProjectSettings\">\n <option nam"
},
{
"path": ".idea/inspectionProfiles/Project_Default.xml",
"chars": 585,
"preview": "<component name=\"InspectionProjectProfileManager\">\n <profile version=\"1.0\">\n <option name=\"myName\" value=\"Project De"
},
{
"path": ".idea/jsLibraryMappings.xml",
"chars": 187,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"JavaScriptLibraryMappings\">\n <include"
},
{
"path": ".idea/modules.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n "
},
{
"path": ".idea/sqldialects.xml",
"chars": 258,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"SqlDialectMappings\">\n <file url=\"file"
},
{
"path": ".idea/vcs.xml",
"chars": 180,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"VcsDirectoryMappings\">\n <mapping dire"
},
{
"path": "APIDOCGUIDE.md",
"chars": 5705,
"preview": "# API Documentation Generation Guide\n\n## Overview\nWhen writing API documentation for routes, focus on creating clear, co"
},
{
"path": "CHANGELOG.md",
"chars": 11962,
"preview": "<!-- Generated by Changerawr CLI on 2026-04-16T07:21:32.297Z -->\n# Changelog\n\nAll notable changes to this project will b"
},
{
"path": "Caddyfile",
"chars": 900,
"preview": "# Caddyfile for Changerawr with on-demand TLS\n# Caddy automatically obtains and renews Let's Encrypt certificates\n\n{\n "
},
{
"path": "Dockerfile",
"chars": 2819,
"preview": "FROM node:22-alpine AS base\n\n# Install dependencies only when needed\nFROM base AS deps\nWORKDIR /app\n\n# Copy package file"
},
{
"path": "Dockerfile.compose",
"chars": 1905,
"preview": "FROM node:20-alpine AS base\n\n# Install dependencies only when needed\nFROM base AS deps\nWORKDIR /app\n\n# Copy package file"
},
{
"path": "LICENSE",
"chars": 8728,
"preview": "CHANGERAWR NON-COMMERCIAL OPEN SOURCE LICENSE\n\nCopyright (c) 2025 Supernova Software, LLC. All rights reserved.\n\nThis so"
},
{
"path": "PROJECT_INDEX.md",
"chars": 9009,
"preview": "# Project Index: Changerawr\n\nGenerated: 2026-04-15\n\n## Overview\n\nChangerawr is a self-hosted changelog management platfo"
},
{
"path": "README.md",
"chars": 7839,
"preview": "<p align=\"center\">\n <img src=\"public/logo.png\" alt=\"logo\" /><br/>\n <strong>Ship, Change, Rawr</strong>\n</p>\n\n\n[![Versi"
},
{
"path": "app/(auth)/forgot-password/layout.tsx",
"chars": 1101,
"preview": "import React from 'react';\nimport { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n title: 'Forgot Pass"
},
{
"path": "app/(auth)/forgot-password/page.tsx",
"chars": 28180,
"preview": "'use client';\n\nimport React, { useState, useEffect, useRef } from 'react';\nimport { zodResolver } from '@hookform/resolv"
},
{
"path": "app/(auth)/layout.tsx",
"chars": 934,
"preview": "'use client'\n\nimport { useAuth } from '@/context/auth'\nimport { useRouter, usePathname } from 'next/navigation'\nimport R"
},
{
"path": "app/(auth)/login/layout.tsx",
"chars": 1071,
"preview": "import React from 'react';\nimport { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n title: 'Login - Cha"
},
{
"path": "app/(auth)/login/page.tsx",
"chars": 48594,
"preview": "'use client'\n\nimport React, {useEffect, useState} from 'react'\nimport {useForm} from 'react-hook-form'\nimport {zodResolv"
},
{
"path": "app/(auth)/oauth-callback/layout.tsx",
"chars": 1074,
"preview": "import { cookies, headers } from 'next/headers'\nimport React from \"react\";\n\nexport default async function OAuthCallbackL"
},
{
"path": "app/(auth)/oauth-callback/page.tsx",
"chars": 2068,
"preview": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Loader2 }"
},
{
"path": "app/(auth)/register/[token]/layout.tsx",
"chars": 1072,
"preview": "import React from 'react';\nimport { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n title: 'Register - "
},
{
"path": "app/(auth)/register/[token]/page.tsx",
"chars": 32498,
"preview": "'use client'\n\nimport React, {useEffect, useState, use} from 'react'\nimport {useForm} from 'react-hook-form'\nimport {zodR"
},
{
"path": "app/(auth)/reset-password/[token]/layout.tsx",
"chars": 1112,
"preview": "import React from 'react';\nimport { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n title: 'Reset Passw"
},
{
"path": "app/(auth)/reset-password/[token]/page.tsx",
"chars": 537,
"preview": "import { Suspense } from 'react';\nimport ResetPasswordForm from './reset-password-form';\nimport { LoadingSpinner } from "
},
{
"path": "app/(auth)/reset-password/[token]/reset-password-form.tsx",
"chars": 29353,
"preview": "\n'use client'\n\nimport React, { useState, useEffect } from 'react'\nimport { useForm } from 'react-hook-form'\nimport { zod"
},
{
"path": "app/(auth)/setup/layout.tsx",
"chars": 560,
"preview": "import React from 'react';\nimport { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n title: 'Setup - Cha"
},
{
"path": "app/(auth)/setup/page.tsx",
"chars": 7566,
"preview": "'use client';\n\nimport React, {useState, useEffect} from 'react';\nimport {Loader2, CheckCircle2} from 'lucide-react';\nimp"
},
{
"path": "app/(auth)/two-factor/layout.tsx",
"chars": 426,
"preview": "import React from 'react';\nimport { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n title: 'Two-Factor "
},
{
"path": "app/(auth)/two-factor/page.tsx",
"chars": 15262,
"preview": "'use client'\n\nimport React, { useEffect, useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Bu"
},
{
"path": "app/(email)/unsubscribed/page.tsx",
"chars": 2035,
"preview": "'use client';\n\nimport {JSX, Suspense, useEffect, useState} from 'react';\nimport {useSearchParams} from 'next/navigation'"
},
{
"path": "app/.well-known/acme-challenge/[token]/route.ts",
"chars": 4245,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\n\nexport const runtime = 'nodejs'\n\n// C"
},
{
"path": "app/.well-known/acme-challenge/[token]/route.ts.disabled",
"chars": 3790,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\n\nexport const runtime = 'nodejs'\n\n"
},
{
"path": "app/.well-known/acme-challenge/route.ts",
"chars": 4483,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\n\nexport const runtime = 'nodejs'\n\n// T"
},
{
"path": "app/api/acme/cancel/[certId]/route.ts",
"chars": 1432,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\n\nexport const runtime = 'nodejs'\n\n"
},
{
"path": "app/api/acme/issue/route.ts",
"chars": 4907,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { sslSupported } from '@/li"
},
{
"path": "app/api/acme/renew/[certId]/route.ts",
"chars": 1331,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { renewCertificate } from '"
},
{
"path": "app/api/acme/revoke/[certId]/route.ts",
"chars": 2183,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\nimport {getAcmeClient, isAcmeStagingEn"
},
{
"path": "app/api/acme/status/[certId]/route.ts",
"chars": 2074,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { sslSupported } from '@/li"
},
{
"path": "app/api/acme/verify-dns/route.ts",
"chars": 5154,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { sslSupported } from '@/lib/custom-domains/ssl/is-suppor"
},
{
"path": "app/api/admin/ai-settings/route.ts",
"chars": 6319,
"preview": "import { NextResponse } from 'next/server'\nimport { PrismaClient } from '@prisma/client'\nimport { validateAuthAndGetUser"
},
{
"path": "app/api/admin/ai-settings/test-key/route.ts",
"chars": 3626,
"preview": "import { NextResponse } from 'next/server'\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog'\nimport { creat"
},
{
"path": "app/api/admin/analytics/route.ts",
"chars": 5369,
"preview": "import {NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimport {getSyste"
},
{
"path": "app/api/admin/api-keys/[keyId]/route.ts",
"chars": 12004,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { db "
},
{
"path": "app/api/admin/api-keys/route.ts",
"chars": 6959,
"preview": "import { NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport { validateAuthAndGetUser } from '@/lib/utils"
},
{
"path": "app/api/admin/audit-logs/actions/route.ts",
"chars": 1721,
"preview": "import { NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { validateAuthAndGetUser } from '@/lib/u"
},
{
"path": "app/api/admin/audit-logs/route.ts",
"chars": 18634,
"preview": "import {NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\nimport {validateAuthAndGetUser} from '@/lib/utils/c"
},
{
"path": "app/api/admin/config/route.ts",
"chars": 19868,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog'\nimpor"
},
{
"path": "app/api/admin/config/system-email/route.ts",
"chars": 15967,
"preview": "// app/api/admin/config/system-email/route.ts\nimport { NextResponse } from 'next/server';\nimport { validateAuthAndGetUse"
},
{
"path": "app/api/admin/dashboard/route.ts",
"chars": 3999,
"preview": "import { NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { validateAuthAndGetUser } from '@/lib/u"
},
{
"path": "app/api/admin/oauth/providers/[id]/route.ts",
"chars": 11162,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { db "
},
{
"path": "app/api/admin/oauth/providers/route.ts",
"chars": 8901,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { cre"
},
{
"path": "app/api/admin/saml/providers/[id]/route.ts",
"chars": 5387,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { cre"
},
{
"path": "app/api/admin/saml/providers/route.ts",
"chars": 4449,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { cre"
},
{
"path": "app/api/admin/sponsor/route.ts",
"chars": 6853,
"preview": "import {NextResponse} from 'next/server'\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog'\nimport {createAudi"
},
{
"path": "app/api/admin/system/slack/route.ts",
"chars": 3494,
"preview": "import {NextResponse} from 'next/server'\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog'\nimport {db} from '"
},
{
"path": "app/api/admin/users/[userId]/role/route.ts",
"chars": 4525,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';"
},
{
"path": "app/api/admin/users/[userId]/route.ts",
"chars": 20617,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimp"
},
{
"path": "app/api/admin/users/invitations/[id]/route.ts",
"chars": 8380,
"preview": "import { validateAuthAndGetUser } from \"@/lib/utils/changelog\";\nimport { NextResponse } from \"next/server\";\nimport { cre"
},
{
"path": "app/api/admin/users/invitations/route.ts",
"chars": 1706,
"preview": "import { validateAuthAndGetUser } from \"@/lib/utils/changelog\";\nimport { NextResponse } from \"next/server\";\nimport {db} "
},
{
"path": "app/api/admin/users/route.ts",
"chars": 10437,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { cre"
},
{
"path": "app/api/ai/decrypt/route.ts",
"chars": 1447,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {validateAuthAndGetUser} from \"@/lib/utils/changelog\"\nimpor"
},
{
"path": "app/api/ai/settings/route.ts",
"chars": 2275,
"preview": "import {NextResponse} from 'next/server'\nimport {PrismaClient} from '@prisma/client'\nimport {validateAuthAndGetUser} fro"
},
{
"path": "app/api/analytics/track/route.ts",
"chars": 1924,
"preview": "// app/api/analytics/track/route.ts\nimport {NextResponse} from 'next/server';\nimport {trackChangelogView} from '@/lib/mi"
},
{
"path": "app/api/auth/change-password/route.ts",
"chars": 3015,
"preview": "// app/api/auth/change-password/route.ts\nimport { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\nimpo"
},
{
"path": "app/api/auth/cli/generate/route.ts",
"chars": 3136,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {validateAuthAndGetUser} from '@/li"
},
{
"path": "app/api/auth/cli/refresh/route.ts",
"chars": 2893,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport { refreshCLIAccessToken } from "
},
{
"path": "app/api/auth/cli/token/route.ts",
"chars": 4494,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {db} from '@/lib/db';\nimport {gener"
},
{
"path": "app/api/auth/connections/route.ts",
"chars": 5697,
"preview": "import {NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimport {db} from"
},
{
"path": "app/api/auth/forgot-password/route.ts",
"chars": 2927,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {createPasswordResetAndSendEmail} f"
},
{
"path": "app/api/auth/invitation/[token]/route.ts",
"chars": 2363,
"preview": "import { NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\n\n/**\n * Handles validation of invitation tokens"
},
{
"path": "app/api/auth/login/route.ts",
"chars": 13799,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport bcrypt from 'bcryptjs'\nimport {z} from 'zod'\nimport {genera"
},
{
"path": "app/api/auth/login/second-factor/route.ts",
"chars": 4969,
"preview": "// app/api/auth/login/second-factor/route.ts\nimport { NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nim"
},
{
"path": "app/api/auth/logout/route.ts",
"chars": 1268,
"preview": "import { NextResponse } from 'next/server'\nimport { cookies } from 'next/headers'\nimport { db } from '@/lib/db'\n\n/**\n * "
},
{
"path": "app/api/auth/me/route.ts",
"chars": 3508,
"preview": "import {NextResponse} from 'next/server'\nimport {cookies, headers} from 'next/headers'\nimport {verifyAccessToken} from '"
},
{
"path": "app/api/auth/oauth/authorize/[providerName]/route.ts",
"chars": 2500,
"preview": "import { NextResponse } from 'next/server';\nimport { getOAuthLoginUrl } from '@/lib/auth/oauth';\nimport { db } from '@/l"
},
{
"path": "app/api/auth/oauth/callback/[providerName]/route.ts",
"chars": 6953,
"preview": "import { NextResponse } from 'next/server';\nimport { handleOAuthCallback } from '@/lib/auth/oauth';\nimport { db } from '"
},
{
"path": "app/api/auth/oauth/providers/route.ts",
"chars": 1305,
"preview": "import { NextResponse } from 'next/server';\nimport { getOAuthProviders } from '@/lib/auth/oauth';\n\n/**\n * @method GET\n *"
},
{
"path": "app/api/auth/passkeys/[id]/route.ts",
"chars": 1175,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { db "
},
{
"path": "app/api/auth/passkeys/authenticate/options/route.ts",
"chars": 1561,
"preview": "import { NextResponse } from 'next/server';\nimport { generateAuthenticationOptionsForUser } from '@/lib/auth/webauthn';\n"
},
{
"path": "app/api/auth/passkeys/authenticate/verify/route.ts",
"chars": 9395,
"preview": "import { NextResponse } from 'next/server';\nimport { verifyAuthentication } from '@/lib/auth/webauthn';\nimport { generat"
},
{
"path": "app/api/auth/passkeys/register/options/route.ts",
"chars": 1898,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { gen"
},
{
"path": "app/api/auth/passkeys/register/verify/route.ts",
"chars": 2159,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { ver"
},
{
"path": "app/api/auth/passkeys/route.ts",
"chars": 915,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { db "
},
{
"path": "app/api/auth/preview/route.ts",
"chars": 1687,
"preview": "import {NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\nimport {z} from 'zod'\nimport {getGravatarUrl} from "
},
{
"path": "app/api/auth/refresh/route.ts",
"chars": 2973,
"preview": "import { NextResponse } from 'next/server'\nimport { refreshAccessToken } from '@/lib/auth/tokens'\nimport { cookies } fro"
},
{
"path": "app/api/auth/register/route.ts",
"chars": 4014,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { hashPassword } from '@/li"
},
{
"path": "app/api/auth/reset-password/[token]/route.ts",
"chars": 4133,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport {\n validatePasswordResetToke"
},
{
"path": "app/api/auth/reset-password/request/route.ts",
"chars": 1583,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { cre"
},
{
"path": "app/api/auth/saml/authorize/[providerName]/route.ts",
"chars": 1148,
"preview": "import { NextResponse } from 'next/server';\nimport { getSAMLLoginUrl } from '@/lib/auth/saml';\n\n/**\n * @method GET\n * @d"
},
{
"path": "app/api/auth/saml/callback/[providerName]/route.ts",
"chars": 2983,
"preview": "import { NextResponse } from 'next/server';\nimport { handleSAMLCallback } from '@/lib/auth/saml';\nimport { shouldUseSecu"
},
{
"path": "app/api/auth/saml/metadata/[providerName]/route.ts",
"chars": 785,
"preview": "import { NextResponse } from 'next/server';\nimport { getSAMLMetadata } from '@/lib/auth/saml';\n\n/**\n * @method GET\n * @d"
},
{
"path": "app/api/auth/saml/providers/route.ts",
"chars": 613,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\n/**\n * @method GET\n * @description Returns e"
},
{
"path": "app/api/auth/security-settings/route.ts",
"chars": 2485,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { db "
},
{
"path": "app/api/auth/settings/route.ts",
"chars": 4111,
"preview": "// app/api/auth/settings/route.ts\nimport { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\nimport { va"
},
{
"path": "app/api/auth/validate/route.ts",
"chars": 2874,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {verifyAccessToken} from '@/lib/auth/tokens';\nimport {db} "
},
{
"path": "app/api/avatar/[hash]/route.ts",
"chars": 1816,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\n/**\n * Proxy Gravatar images to avoid tracking prevention bloc"
},
{
"path": "app/api/changelog/[projectId]/entries/all/route.ts",
"chars": 6681,
"preview": "import {NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\n\nconst ITEMS_PER_PAGE = 10\n\n// Define type for sear"
},
{
"path": "app/api/changelog/[projectId]/entries/route.ts",
"chars": 7973,
"preview": "import {NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\n\nconst ITEMS_PER_PAGE = 10\n\n// Define type for sear"
},
{
"path": "app/api/changelog/entries/[entryId]/route.ts",
"chars": 10524,
"preview": "import {db} from '@/lib/db'\nimport {\n validateAuthAndGetUser,\n changelogEntrySchema,\n sendError,\n sendSucces"
},
{
"path": "app/api/changelog/requests/[requestId]/route.ts",
"chars": 4214,
"preview": "import { NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport { validateAuthAndGetUser } from '@/lib/utils"
},
{
"path": "app/api/changelog/requests/route.ts",
"chars": 6927,
"preview": "// app/api/changelog/requests/route.ts\nimport {NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\nimport {vali"
},
{
"path": "app/api/changelog/subscribe/route.ts",
"chars": 4404,
"preview": "import { NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport { db } from '@/lib/db';\nimport { nanoid } fr"
},
{
"path": "app/api/changelog/unsubscribe/[token]/route.ts",
"chars": 3132,
"preview": "// app/api/changelog/unsubscribe/[token]/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\nimport { db } "
},
{
"path": "app/api/changelog/verify-domain/route.ts",
"chars": 1577,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {getDomainByDomain} from '@/lib/custom-domains/service'\n\nex"
},
{
"path": "app/api/check-setup/route.ts",
"chars": 1310,
"preview": "import { NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\n\n/**\n * This is a special API route that only h"
},
{
"path": "app/api/config/runtime/route.ts",
"chars": 412,
"preview": "import { NextResponse } from 'next/server'\n\n/**\n * Runtime configuration endpoint\n * Returns environment variables that "
},
{
"path": "app/api/config/timezone/route.ts",
"chars": 2065,
"preview": "import {NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\nimport {cookies} from 'next/headers'\nimport {verify"
},
{
"path": "app/api/cron/ssl-renewal/route.ts",
"chars": 2260,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { sslSupported } from '@/lib/custom-domains/ssl/is-suppor"
},
{
"path": "app/api/custom-domains/[domain]/browser-rules/[id]/route.ts",
"chars": 3639,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { validateAuthAndGetUser } "
},
{
"path": "app/api/custom-domains/[domain]/browser-rules/route.ts",
"chars": 2584,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { validateAuthAndGetUser } "
},
{
"path": "app/api/custom-domains/[domain]/dns-instructions/route.ts",
"chars": 2500,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport type { DNSInstructions } fr"
},
{
"path": "app/api/custom-domains/[domain]/route.ts",
"chars": 2150,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {deleteDomain, getDomainByDomain, canUserManageDomain} from"
},
{
"path": "app/api/custom-domains/[domain]/ssl/mode/route.ts",
"chars": 2296,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { SslMode } from '@prisma/c"
},
{
"path": "app/api/custom-domains/[domain]/ssl/revoke/route.ts",
"chars": 3345,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { validateAuthAndGetUser } "
},
{
"path": "app/api/custom-domains/[domain]/ssl/toggle-https/route.ts",
"chars": 2152,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { validateAuthAndGetUser } "
},
{
"path": "app/api/custom-domains/[domain]/throttle/route.ts",
"chars": 3044,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\nimport { validateAuthAndGetUser } "
},
{
"path": "app/api/custom-domains/add/route.ts",
"chars": 3139,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {createCustomDomain} from '@/lib/custom-domains/service'\nim"
},
{
"path": "app/api/custom-domains/list/route.ts",
"chars": 1801,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport {\n getAllDomains,\n getDomainsByUser,\n getDomains"
},
{
"path": "app/api/custom-domains/verify/route.ts",
"chars": 3020,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {getDomainByDomain, updateDomainVerification, canUserManage"
},
{
"path": "app/api/dashboard/stats/route.ts",
"chars": 6595,
"preview": "import {NextResponse} from 'next/server'\nimport {db} from '@/lib/db'\nimport {validateAuthAndGetUser} from '@/lib/utils/c"
},
{
"path": "app/api/domain-check/route.ts",
"chars": 1250,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\n\nexport const runtime = 'nodejs'\n\n"
},
{
"path": "app/api/health/route.ts",
"chars": 1294,
"preview": "// app/api/health/route.ts\nimport { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\n/**\n * @method GE"
},
{
"path": "app/api/integrations/slack/callback/route.ts",
"chars": 9473,
"preview": "import {NextRequest, NextResponse} from 'next/server'\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog'\nimpor"
},
{
"path": "app/api/integrations/slack/manifest/route.ts",
"chars": 3612,
"preview": "import {NextRequest, NextResponse} from 'next/server'\n\n/**\n * Get the correct app URL, handling proxies, internal IPs, a"
},
{
"path": "app/api/integrations/widget/[projectId]/[widgetId]/route.ts",
"chars": 10202,
"preview": "import {NextResponse} from \"next/server\";\nimport {db} from \"@/lib/db\";\nimport {validateAuthAndGetUser, sendError, sendSu"
},
{
"path": "app/api/integrations/widget/[projectId]/list/route.ts",
"chars": 914,
"preview": "import {NextResponse} from \"next/server\";\nimport {db} from \"@/lib/db\";\nimport {validateAuthAndGetUser, sendError} from \""
},
{
"path": "app/api/integrations/widget/[projectId]/route.ts",
"chars": 7480,
"preview": "import { NextResponse } from \"next/server\";\nimport { db } from \"@/lib/db\";\n\n/**\n * @method GET\n * @description Gets the "
},
{
"path": "app/api/internal/agent/version/route.ts",
"chars": 1660,
"preview": "import { NextRequest, NextResponse } from 'next/server'\n\nexport const runtime = 'nodejs'\n\n/**\n * Internal API to fetch n"
},
{
"path": "app/api/internal/cert/[domain]/route.ts",
"chars": 1785,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { getActiveCertBundle } from '@/lib/custom-domains/ssl/se"
},
{
"path": "app/api/internal/ip-config/route.ts",
"chars": 1079,
"preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { db } from '@/lib/db'\n\n/**\n * Internal endpoint used by "
},
{
"path": "app/api/projects/[projectId]/analytics/export/route.ts",
"chars": 5449,
"preview": "import {NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimport {getProje"
},
{
"path": "app/api/projects/[projectId]/analytics/route.ts",
"chars": 6109,
"preview": "import {NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimport {getProje"
},
{
"path": "app/api/projects/[projectId]/api-keys/[keyId]/route.ts",
"chars": 5619,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport { authenticateRequest } from '@"
},
{
"path": "app/api/projects/[projectId]/api-keys/route.ts",
"chars": 6695,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport { authenticateRequest } from '@"
},
{
"path": "app/api/projects/[projectId]/catch-up/ai-summary/route.ts",
"chars": 8206,
"preview": "import {NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimport {db} from"
},
{
"path": "app/api/projects/[projectId]/catch-up/route.ts",
"chars": 3893,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { Cat"
},
{
"path": "app/api/projects/[projectId]/changelog/[entryId]/route.ts",
"chars": 44835,
"preview": "import {NextResponse} from 'next/server'\nimport {validateAuthAndGetUser, generateExcerpt} from '@/lib/utils/changelog'\ni"
},
{
"path": "app/api/projects/[projectId]/changelog/[entryId]/schedule/approval/route.ts",
"chars": 13959,
"preview": "// app/api/projects/[projectId]/changelog/[entryId]/schedule/approval/route.ts\nimport {NextResponse} from \"next/server\";"
},
{
"path": "app/api/projects/[projectId]/changelog/[entryId]/schedule/route.ts",
"chars": 13137,
"preview": "import {NextResponse} from \"next/server\";\nimport {z} from \"zod\";\nimport {validateAuthAndGetUser} from \"@/lib/utils/chang"
},
{
"path": "app/api/projects/[projectId]/changelog/route.ts",
"chars": 17076,
"preview": "import { NextResponse } from 'next/server'\nimport { validateAuthAndGetUser, generateExcerpt } from '@/lib/utils/changelo"
},
{
"path": "app/api/projects/[projectId]/changelog/tags/[tagId]/route.ts",
"chars": 8971,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {db} from '@/lib/db';\nimport {valid"
},
{
"path": "app/api/projects/[projectId]/changelog/tags/route.ts",
"chars": 19091,
"preview": "import {validateAuthAndGetUser} from \"@/lib/utils/changelog\"\nimport {NextRequest, NextResponse} from \"next/server\"\nimpor"
},
{
"path": "app/api/projects/[projectId]/cli/link/route.ts",
"chars": 5406,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {db} from '@/lib/db';\nimport {valid"
},
{
"path": "app/api/projects/[projectId]/cli/sync/route.ts",
"chars": 10493,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {db} from '@/lib/db';\nimport {valid"
},
{
"path": "app/api/projects/[projectId]/cli/sync/status/route.ts",
"chars": 6570,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\nimport { validateAuthAndGetUser "
},
{
"path": "app/api/projects/[projectId]/cli/unlink/route.ts",
"chars": 5759,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {db} from '@/lib/db';\nimport {valid"
},
{
"path": "app/api/projects/[projectId]/integrations/email/route.ts",
"chars": 5261,
"preview": "import { NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';\nimport { db "
},
{
"path": "app/api/projects/[projectId]/integrations/email/send/route.ts",
"chars": 5133,
"preview": "import {NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimport {db} from"
},
{
"path": "app/api/projects/[projectId]/integrations/email/test/route.ts",
"chars": 7777,
"preview": "// app/api/projects/[projectId]/integrations/email/test/route.ts\nimport { NextResponse } from 'next/server';\nimport { va"
},
{
"path": "app/api/projects/[projectId]/integrations/github/generate/route.ts",
"chars": 14975,
"preview": "// app/api/projects/[projectId]/integrations/github/generate/route.ts\nimport { NextResponse } from 'next/server';\nimport"
},
{
"path": "app/api/projects/[projectId]/integrations/github/route.ts",
"chars": 8584,
"preview": "// app/api/projects/[projectId]/integrations/github/route.ts\nimport { NextResponse } from 'next/server';\nimport { valida"
},
{
"path": "app/api/projects/[projectId]/integrations/github/tags/route.ts",
"chars": 6090,
"preview": "// app/api/projects/[projectId]/integrations/github/tags/route.ts\nimport { NextResponse } from 'next/server';\nimport { v"
},
{
"path": "app/api/projects/[projectId]/integrations/github/test/route.ts",
"chars": 4856,
"preview": "import {NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimport {z} from "
},
{
"path": "app/api/projects/[projectId]/integrations/slack/channels/route.ts",
"chars": 2926,
"preview": "import {NextResponse} from 'next/server'\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog'\nimport {db} from '"
},
{
"path": "app/api/projects/[projectId]/integrations/slack/route.ts",
"chars": 3865,
"preview": "import {NextResponse} from 'next/server'\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog'\nimport {db} from '"
},
{
"path": "app/api/projects/[projectId]/route.ts",
"chars": 5154,
"preview": "import { db } from '@/lib/db'\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog'\nimport { z } from 'zod'\n\nco"
},
{
"path": "app/api/projects/[projectId]/settings/route.ts",
"chars": 14552,
"preview": "import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { db } from '@/lib/db'\nimport { validateAuthAn"
},
{
"path": "app/api/projects/[projectId]/versions/route.ts",
"chars": 1639,
"preview": "import { NextResponse } from 'next/server'\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog'\nimport { db } "
},
{
"path": "app/api/projects/import/canny/fetch/route.ts",
"chars": 2356,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {CannyService} from '@/lib/services/projects/importing/int"
},
{
"path": "app/api/projects/import/canny/validate/route.ts",
"chars": 801,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { CannyService } from '@/lib/services/projects/importing"
},
{
"path": "app/api/projects/import/parse/route.ts",
"chars": 2615,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { MarkdownParserService } from '@/lib/services/projects/"
},
{
"path": "app/api/projects/import/process/route.ts",
"chars": 6973,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {ImportProcessorService} from '@/lib/services/projects/imp"
},
{
"path": "app/api/projects/route.ts",
"chars": 4123,
"preview": "import { db } from '@/lib/db'\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog'\nimport { z } from 'zod'\n\n/*"
},
{
"path": "app/api/requests/route.ts",
"chars": 3016,
"preview": "import {NextResponse} from 'next/server';\nimport {db} from '@/lib/db';\nimport {validateAuthAndGetUser} from '@/lib/utils"
},
{
"path": "app/api/search/route.ts",
"chars": 6331,
"preview": "// app/api/search/route.ts\nimport {NextRequest, NextResponse} from 'next/server';\nimport {verifyAccessToken} from '@/lib"
},
{
"path": "app/api/setup/admin/route.ts",
"chars": 3809,
"preview": "import {NextResponse} from 'next/server'\nimport {z} from 'zod'\nimport {db} from '@/lib/db'\nimport {hashPassword} from '@"
},
{
"path": "app/api/setup/invitations/route.ts",
"chars": 6676,
"preview": "// app/api/invitations/route.ts\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { db } from '@"
},
{
"path": "app/api/setup/oauth/auto/route.ts",
"chars": 8354,
"preview": "// app/api/setup/oauth/auto/route.ts\nimport { NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport {\n p"
},
{
"path": "app/api/setup/oauth/debug/route.ts",
"chars": 3835,
"preview": "// app/api/setup/oauth/debug/route.ts\nimport { NextResponse } from 'next/server';\nimport {\n getOAuthServerInfo\n} from"
},
{
"path": "app/api/setup/oauth/route.ts",
"chars": 2614,
"preview": "import {NextResponse} from 'next/server';\nimport {z} from 'zod';\nimport {setupEasypanelProvider} from '@/lib/auth/provid"
},
{
"path": "app/api/setup/route.ts",
"chars": 4105,
"preview": "// app/api/setup/route.ts\nimport {NextResponse} from 'next/server';\nimport {db} from '@/lib/db';\nimport {SetupProgress} "
},
{
"path": "app/api/setup/settings/route.ts",
"chars": 7596,
"preview": "import {NextResponse} from 'next/server'\nimport {z} from 'zod'\nimport {db} from '@/lib/db'\n\n/**\n * Schema for validating"
},
{
"path": "app/api/setup/setupProgressSchema.ts",
"chars": 416,
"preview": "// Schema for tracking setup progress\nimport {z} from \"zod\";\n\nexport const setupProgressSchema = z.object({\n currentS"
},
{
"path": "app/api/setup/status/route.ts",
"chars": 2617,
"preview": "import { NextResponse } from \"next/server\"\nimport { db } from \"@/lib/db\"\n\n/**\n * @method GET\n * @description Checks if t"
},
{
"path": "app/api/setup/types.ts",
"chars": 299,
"preview": "import { z } from 'zod';\nimport { setupProgressSchema } from './setupProgressSchema';\n\nexport const setupSteps = [\n '"
},
{
"path": "app/api/subscribers/[subscriberId]/route.ts",
"chars": 5998,
"preview": "// app/api/subscribers/[subscriberId]/route.ts\n\nimport { NextResponse } from 'next/server';\nimport { db } from '@/lib/db"
},
{
"path": "app/api/subscribers/generate-mock/route.ts",
"chars": 5898,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\nimport { validateAuthAndGetUser } from '@/lib"
},
{
"path": "app/api/subscribers/route.ts",
"chars": 6589,
"preview": "import { NextResponse } from 'next/server';\nimport { z } from 'zod';\nimport { db } from '@/lib/db';\nimport { nanoid } fr"
},
{
"path": "app/api/system/agent-version/route.ts",
"chars": 1365,
"preview": "import { NextResponse } from 'next/server'\n\nexport const runtime = 'nodejs'\n\n/**\n * Public API to fetch nginx-agent vers"
},
{
"path": "app/api/system/easypanel/status/route.ts",
"chars": 3100,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {validateAuthAndGetUser} from '@/lib/utils/changelog';\nimp"
},
{
"path": "app/api/system/perform-update/route.ts",
"chars": 5482,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { validateAuthAndGetUser } from '@/lib/utils/changelog';"
},
{
"path": "app/api/system/update-status/route.ts",
"chars": 2727,
"preview": "// app/api/system/update-status/route.ts\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { validateAuth"
},
{
"path": "app/api/system/version/route.ts",
"chars": 695,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\nexport async function GET(): Promise<NextRes"
},
{
"path": "app/api/telemetry/config/route.ts",
"chars": 3056,
"preview": "// app/api/telemetry/config/route.ts\nimport {NextRequest, NextResponse} from 'next/server';\nimport {TelemetryService} fr"
},
{
"path": "app/api/telemetry/debug/route.ts",
"chars": 3323,
"preview": "import {NextRequest, NextResponse} from 'next/server';\nimport {TelemetryService} from '@/lib/services/telemetry/service'"
},
{
"path": "app/api-docs/route.ts",
"chars": 182,
"preview": "import { ApiReference } from '@scalar/nextjs-api-reference'\n\nconst config = {\n url: '/swagger.json',\n theme: 'kepl"
},
{
"path": "app/changelog/[projectId]/[entryId]/page.tsx",
"chars": 8863,
"preview": "'use client';\n\nimport {useEffect, useState} from 'react';\nimport {useRouter} from 'next/navigation';\nimport {ArrowLeft, "
}
]
// ... and 424 more files (download for full content)
About this extraction
This page contains the full source code of the Supernova3339/changerawr GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 624 files (5.8 MB), approximately 1.5M tokens, and a symbol index with 1951 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.