Full Code of Portabase/portabase for AI

main dc80d1d752bb cached
665 files
4.5 MB
1.2M tokens
920 symbols
1 requests
Download .txt
Showing preview only (4,828K chars total). Download the full file or copy to clipboard to get everything.
Repository: Portabase/portabase
Branch: main
Commit: dc80d1d752bb
Files: 665
Total size: 4.5 MB

Directory structure:
gitextract_jp3gfjy6/

├── .dockerignore
├── .eslintrc.json
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── SECURITY.md
│   └── workflows/
│       ├── discord.yml
│       ├── docker.yml
│       ├── e2e.yml
│       ├── helm.yml
│       ├── release-candidate.yml
│       ├── release.yml
│       └── security.yml
├── .gitignore
├── .gitleaks.toml
├── .pre-commit-config.yaml
├── .release-it.json
├── CITATION.cff
├── LICENSE
├── Makefile
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── forgot-password/
│   │   │   └── page.tsx
│   │   ├── guard/
│   │   │   └── page.tsx
│   │   ├── layout.tsx
│   │   ├── login/
│   │   │   └── page.tsx
│   │   ├── register/
│   │   │   └── page.tsx
│   │   └── reset-password/
│   │       └── page.tsx
│   ├── (customer)/
│   │   └── dashboard/
│   │       ├── (admin)/
│   │       │   ├── admin/
│   │       │   │   ├── organizations/
│   │       │   │   │   ├── [organizationId]/
│   │       │   │   │   │   └── page.tsx
│   │       │   │   │   └── page.tsx
│   │       │   │   ├── settings/
│   │       │   │   │   └── page.tsx
│   │       │   │   └── users/
│   │       │   │       └── page.tsx
│   │       │   ├── agents/
│   │       │   │   ├── [agentId]/
│   │       │   │   │   └── page.tsx
│   │       │   │   └── page.tsx
│   │       │   ├── layout.tsx
│   │       │   ├── notifications/
│   │       │   │   ├── channels/
│   │       │   │   │   └── page.tsx
│   │       │   │   └── logs/
│   │       │   │       └── page.tsx
│   │       │   └── storages/
│   │       │       └── channels/
│   │       │           └── page.tsx
│   │       ├── (organization)/
│   │       │   ├── migration/
│   │       │   │   └── page.tsx
│   │       │   ├── projects/
│   │       │   │   ├── [projectId]/
│   │       │   │   │   ├── database/
│   │       │   │   │   │   └── [databaseId]/
│   │       │   │   │   │       └── page.tsx
│   │       │   │   │   └── page.tsx
│   │       │   │   └── page.tsx
│   │       │   ├── settings/
│   │       │   │   ├── agents/
│   │       │   │   │   ├── [agentId]/
│   │       │   │   │   │   └── page.tsx
│   │       │   │   │   └── page.tsx
│   │       │   │   └── page.tsx
│   │       │   └── statistics/
│   │       │       └── page.tsx
│   │       ├── home/
│   │       │   └── page.tsx
│   │       ├── layout.tsx
│   │       └── loading.tsx
│   ├── (landing)/
│   │   ├── home/
│   │   │   └── page.tsx
│   │   └── page.tsx
│   ├── api/
│   │   ├── agent/
│   │   │   └── [agentId]/
│   │   │       ├── backup/
│   │   │       │   ├── helpers.ts
│   │   │       │   ├── route.ts
│   │   │       │   └── upload/
│   │   │       │       ├── init/
│   │   │       │       │   └── route.ts
│   │   │       │       └── status/
│   │   │       │           └── route.ts
│   │   │       ├── restore/
│   │   │       │   └── route.ts
│   │   │       └── status/
│   │   │           ├── helpers.ts
│   │   │           └── route.ts
│   │   ├── auth/
│   │   │   └── [...all]/
│   │   │       └── route.ts
│   │   ├── config/
│   │   │   └── route.ts
│   │   ├── events/
│   │   │   └── route.ts
│   │   ├── files/
│   │   │   ├── backups/
│   │   │   │   └── route.ts
│   │   │   └── images/
│   │   │       └── [fileName]/
│   │   │           └── route.ts
│   │   ├── google/
│   │   │   └── drive/
│   │   │       └── callback/
│   │   │           └── route.ts
│   │   └── tus/
│   │       └── hooks/
│   │           └── route.ts
│   ├── error/
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   ├── manifest.json
│   ├── not-found.tsx
│   └── providers.tsx
├── components.json
├── docker/
│   ├── dockerfile/
│   │   └── Dockerfile
│   ├── entrypoints/
│   │   ├── app-dev-entrypoint.sh
│   │   └── app-prod-entrypoint.sh
│   └── nginx/
│       └── nginx.conf
├── docker-compose.e2e.yml
├── docker-compose.func.yml
├── docker-compose.prod.yml
├── docker-compose.yml
├── drizzle.config.ts
├── e2e/
│   ├── access-management.spec.ts
│   ├── agent.spec.ts
│   ├── auth.spec.ts
│   ├── cleanup.spec.ts
│   ├── helpers/
│   │   ├── access-management.ts
│   │   ├── agent-cli.ts
│   │   ├── agent.ts
│   │   ├── auth.ts
│   │   ├── env.ts
│   │   ├── notification.ts
│   │   ├── project.ts
│   │   ├── session.ts
│   │   └── storage.ts
│   ├── notification/
│   │   ├── discord.spec.ts
│   │   ├── gotify.spec.ts
│   │   ├── ntfy.spec.ts
│   │   ├── slack.spec.ts
│   │   ├── smtp.spec.ts
│   │   ├── teams.spec.ts
│   │   ├── telegram.spec.ts
│   │   └── webhook.spec.ts
│   ├── project.spec.ts
│   ├── setup.spec.ts
│   └── storage/
│       ├── azure.spec.ts
│       ├── gcs.spec.ts
│       ├── google-drive.spec.ts
│       └── s3.spec.ts
├── eslint.config.mjs
├── helm/
│   ├── .helmignore
│   ├── Chart.yaml
│   ├── README.md
│   ├── templates/
│   │   ├── deployment.yaml
│   │   ├── pvc.yaml
│   │   └── service.yaml
│   └── values.yaml
├── instrumentation.ts
├── next.config.ts
├── package.json
├── playwright.config.ts
├── pnpm-workspace.yaml
├── portabase.config.ts
├── postcss.config.mjs
├── proxy.ts
├── seeds/
│   └── keycloak/
│       └── master-realm.json
├── src/
│   ├── components/
│   │   ├── emails/
│   │   │   ├── auth/
│   │   │   │   ├── email-forgot-password.tsx
│   │   │   │   ├── email-new-login.tsx
│   │   │   │   └── email-verification.tsx
│   │   │   ├── email-create-user.tsx
│   │   │   ├── email-layout.tsx
│   │   │   ├── email-notification.tsx
│   │   │   ├── email-settings-test.tsx
│   │   │   └── email-text.tsx
│   │   ├── layout.tsx
│   │   ├── ui/
│   │   │   ├── accordion.tsx
│   │   │   ├── alert-dialog.tsx
│   │   │   ├── alert.tsx
│   │   │   ├── aspect-ratio.tsx
│   │   │   ├── avatar.tsx
│   │   │   ├── badge.tsx
│   │   │   ├── breadcrumb.tsx
│   │   │   ├── button.tsx
│   │   │   ├── calendar.tsx
│   │   │   ├── card.tsx
│   │   │   ├── carousel.tsx
│   │   │   ├── chart.tsx
│   │   │   ├── checkbox.tsx
│   │   │   ├── collapsible.tsx
│   │   │   ├── command.tsx
│   │   │   ├── context-menu.tsx
│   │   │   ├── dialog.tsx
│   │   │   ├── drawer.tsx
│   │   │   ├── dropdown-menu.tsx
│   │   │   ├── dropzone.tsx
│   │   │   ├── form.tsx
│   │   │   ├── github-button.tsx
│   │   │   ├── hover-card.tsx
│   │   │   ├── input-otp.tsx
│   │   │   ├── input.tsx
│   │   │   ├── label.tsx
│   │   │   ├── menubar.tsx
│   │   │   ├── navigation-menu.tsx
│   │   │   ├── pagination.tsx
│   │   │   ├── password-input-indicator.tsx
│   │   │   ├── password-input.tsx
│   │   │   ├── popover.tsx
│   │   │   ├── progress.tsx
│   │   │   ├── radio-group.tsx
│   │   │   ├── resizable.tsx
│   │   │   ├── scroll-area.tsx
│   │   │   ├── search-input.tsx
│   │   │   ├── select.tsx
│   │   │   ├── separator.tsx
│   │   │   ├── sheet.tsx
│   │   │   ├── sidebar.tsx
│   │   │   ├── skeleton.tsx
│   │   │   ├── slider.tsx
│   │   │   ├── sliding-number.tsx
│   │   │   ├── sonner.tsx
│   │   │   ├── switch.tsx
│   │   │   ├── table.tsx
│   │   │   ├── tabs.tsx
│   │   │   ├── textarea.tsx
│   │   │   ├── toast.tsx
│   │   │   ├── toaster.tsx
│   │   │   ├── toggle-group.tsx
│   │   │   ├── toggle.tsx
│   │   │   └── tooltip.tsx
│   │   └── wrappers/
│   │       ├── auth/
│   │       │   ├── auth-logo-section.tsx
│   │       │   ├── guard/
│   │       │   │   └── guard-form.tsx
│   │       │   ├── login/
│   │       │   │   ├── forgot-password-form/
│   │       │   │   │   ├── forgot-password-form.schema.ts
│   │       │   │   │   ├── forgot-password-form.tsx
│   │       │   │   │   └── forgot-password.actions.ts
│   │       │   │   ├── login-form/
│   │       │   │   │   ├── login-form.schema.ts
│   │       │   │   │   └── login-form.tsx
│   │       │   │   └── reset-password-form/
│   │       │   │       ├── reset-password-form.action.ts
│   │       │   │       ├── reset-password-form.schema.ts
│   │       │   │       └── reset-password-form.tsx
│   │       │   ├── register/
│   │       │   │   └── register-form/
│   │       │   │       ├── register-form.schema.ts
│   │       │   │       └── register-form.tsx
│   │       │   ├── reset-password/
│   │       │   │   ├── reset-password-form.tsx
│   │       │   │   ├── reset-password-schema.ts
│   │       │   │   └── reset-password-section.tsx
│   │       │   └── social-buttons.tsx
│   │       ├── common/
│   │       │   ├── bread-crumbs/
│   │       │   │   └── bread-crumbs.tsx
│   │       │   ├── button/
│   │       │   │   ├── back-button.tsx
│   │       │   │   ├── button-with-confirm.tsx
│   │       │   │   ├── button-with-loading.tsx
│   │       │   │   └── copy-button.tsx
│   │       │   ├── cards-with-pagination.tsx
│   │       │   ├── code-snippet.tsx
│   │       │   ├── combobox.tsx
│   │       │   ├── connection-indicator.tsx
│   │       │   ├── console-silencer.tsx
│   │       │   ├── day-time-picker.tsx
│   │       │   ├── dialog.tsx
│   │       │   ├── dropzone/
│   │       │   │   └── dropzone-file.tsx
│   │       │   ├── empty-state-placeholder.tsx
│   │       │   ├── error-layout.tsx
│   │       │   ├── file-uploader.tsx
│   │       │   ├── github/
│   │       │   │   └── github-button.tsx
│   │       │   ├── loading/
│   │       │   │   └── loading-spinner.tsx
│   │       │   ├── multiselect/
│   │       │   │   └── multi-select.tsx
│   │       │   ├── pagination/
│   │       │   │   ├── pagination-indexes.tsx
│   │       │   │   ├── pagination-navigation.tsx
│   │       │   │   └── pagination-size.tsx
│   │       │   ├── provider-switch.tsx
│   │       │   ├── status-badge.tsx
│   │       │   ├── table/
│   │       │   │   ├── data-table.tsx
│   │       │   │   ├── filters.tsx
│   │       │   │   ├── table-pagination-navigation.tsx
│   │       │   │   ├── table-pagination-size.tsx
│   │       │   │   ├── table-pagination.tsx
│   │       │   │   └── table-sort-button.tsx
│   │       │   └── tooltip-custom.tsx
│   │       └── dashboard/
│   │           ├── admin/
│   │           │   ├── channels/
│   │           │   │   ├── channel/
│   │           │   │   │   ├── channel-add-edit-modal.tsx
│   │           │   │   │   ├── channel-card/
│   │           │   │   │   │   ├── button-delete-channel.tsx
│   │           │   │   │   │   ├── button-edit-channel.tsx
│   │           │   │   │   │   └── channel-card.tsx
│   │           │   │   │   └── channel-form/
│   │           │   │   │       ├── channel-form.schema.ts
│   │           │   │   │       ├── channel-form.tsx
│   │           │   │   │       ├── channel-test-button.tsx
│   │           │   │   │       └── providers/
│   │           │   │   │           ├── notifications/
│   │           │   │   │           │   ├── action.ts
│   │           │   │   │           │   └── forms/
│   │           │   │   │           │       ├── discord.form.tsx
│   │           │   │   │           │       ├── discord.schema.ts
│   │           │   │   │           │       ├── gotify.form.tsx
│   │           │   │   │           │       ├── gotify.schema.ts
│   │           │   │   │           │       ├── ntfy.form.tsx
│   │           │   │   │           │       ├── ntfy.schema.ts
│   │           │   │   │           │       ├── slack.form.tsx
│   │           │   │   │           │       ├── slack.schema.ts
│   │           │   │   │           │       ├── smtp.form.tsx
│   │           │   │   │           │       ├── smtp.schema.ts
│   │           │   │   │           │       ├── telegram.form.tsx
│   │           │   │   │           │       ├── telegram.schema.ts
│   │           │   │   │           │       ├── webhook.form.tsx
│   │           │   │   │           │       └── webhook.schema.ts
│   │           │   │   │           └── storages/
│   │           │   │   │               ├── action.ts
│   │           │   │   │               └── forms/
│   │           │   │   │                   ├── google-drive/
│   │           │   │   │                   │   └── helpers.ts
│   │           │   │   │                   ├── google-drive.form.tsx
│   │           │   │   │                   ├── google-drive.schema.ts
│   │           │   │   │                   ├── local.schema.ts
│   │           │   │   │                   ├── s3.form.tsx
│   │           │   │   │                   └── s3.schema.ts
│   │           │   │   ├── channels-section.tsx
│   │           │   │   ├── helpers/
│   │           │   │   │   ├── common.tsx
│   │           │   │   │   ├── notification.tsx
│   │           │   │   │   └── storage.tsx
│   │           │   │   └── organization/
│   │           │   │       ├── channels-organization-form.tsx
│   │           │   │       ├── channels-organization.action.ts
│   │           │   │       └── channels-organization.schema.ts
│   │           │   ├── notifications/
│   │           │   │   └── logs/
│   │           │   │       ├── columns.tsx
│   │           │   │       ├── notification-log-modal.tsx
│   │           │   │       └── notification-logs-list.tsx
│   │           │   ├── organizations/
│   │           │   │   ├── admin-organizations-table.tsx
│   │           │   │   ├── columns-organizations.tsx
│   │           │   │   └── organization/
│   │           │   │       ├── admin-organization-add-modal.tsx
│   │           │   │       ├── admin-organization-form.tsx
│   │           │   │       ├── admin-organization-section.tsx
│   │           │   │       ├── admin-orgnization-list.tsx
│   │           │   │       ├── button-delete-organization.tsx
│   │           │   │       ├── details/
│   │           │   │       │   ├── add-member.action.ts
│   │           │   │       │   ├── organization-add-member-form.tsx
│   │           │   │       │   ├── organization-add-member-modal.tsx
│   │           │   │       │   ├── organization-delete-member-modal.tsx
│   │           │   │       │   ├── organization-member-card.tsx
│   │           │   │       │   ├── organization-member-change-role.tsx
│   │           │   │       │   ├── role-member.action.ts
│   │           │   │       │   └── update-organization-form.tsx
│   │           │   │       ├── organization-management.tsx
│   │           │   │       ├── organization.schema.ts
│   │           │   │       └── table-colums.tsx
│   │           │   ├── settings/
│   │           │   │   ├── email/
│   │           │   │   │   ├── email-form/
│   │           │   │   │   │   ├── email-form.action.ts
│   │           │   │   │   │   ├── email-form.schema.ts
│   │           │   │   │   │   └── email-form.tsx
│   │           │   │   │   └── settings-email-section.tsx
│   │           │   │   ├── notification/
│   │           │   │   │   ├── settings-notification-section.tsx
│   │           │   │   │   ├── settings-notification.action.ts
│   │           │   │   │   └── settings-notification.schema.ts
│   │           │   │   ├── settings-tabs.tsx
│   │           │   │   └── storage/
│   │           │   │       ├── settings-storage-section.tsx
│   │           │   │       ├── settings-storage.action.ts
│   │           │   │       ├── settings-storage.schema.ts
│   │           │   │       └── storage-s3/
│   │           │   │           ├── s3-form.action.ts
│   │           │   │           ├── s3-form.schema.ts
│   │           │   │           └── storage-s3-form.tsx
│   │           │   └── users/
│   │           │       ├── admin-user-add-modal.tsx
│   │           │       ├── admin-user-change-password-modal.tsx
│   │           │       ├── admin-user-change-role-modal.tsx
│   │           │       ├── admin-user-delete-modal.tsx
│   │           │       ├── admin-user-edit-form.tsx
│   │           │       ├── admin-user-edit-modal.tsx
│   │           │       ├── admin-user-form.tsx
│   │           │       ├── admin-user-list.tsx
│   │           │       ├── table-colums.tsx
│   │           │       ├── user-actions-cell.tsx
│   │           │       ├── user.action.ts
│   │           │       └── user.schema.ts
│   │           ├── agent/
│   │           │   ├── agent-card/
│   │           │   │   └── agent-card.tsx
│   │           │   ├── agent-card-key/
│   │           │   │   └── agent-card-key.tsx
│   │           │   ├── agent-content.tsx
│   │           │   ├── agent-database-card.tsx
│   │           │   ├── agent-database-columns.tsx
│   │           │   ├── agent-modal-key/
│   │           │   │   └── agent-modal-key.tsx
│   │           │   └── button-delete-agent/
│   │           │       ├── button-delete-agent.tsx
│   │           │       └── delete-agent.action.ts
│   │           ├── backup/
│   │           │   └── backup-button/
│   │           │       ├── backup-button.action.ts
│   │           │       └── backup-button.tsx
│   │           ├── common/
│   │           │   ├── logged-in/
│   │           │   │   ├── logged-in-button.server.tsx
│   │           │   │   ├── logged-in-button.tsx
│   │           │   │   └── logged-in-dropdown.tsx
│   │           │   ├── profile/
│   │           │   │   ├── profile-modal.tsx
│   │           │   │   └── profile-sidebar.tsx
│   │           │   └── sidebar/
│   │           │       ├── app-sidebar.tsx
│   │           │       ├── logo-sidebar.tsx
│   │           │       ├── menu-sidebar-main.tsx
│   │           │       ├── menu-sidebar.tsx
│   │           │       ├── side-bar-footer-credit.tsx
│   │           │       └── side-bar-logo.tsx
│   │           ├── database/
│   │           │   ├── backup/
│   │           │   │   ├── actions/
│   │           │   │   │   ├── backup-actions-cell.tsx
│   │           │   │   │   ├── backup-actions-form.tsx
│   │           │   │   │   ├── backup-actions-modal.tsx
│   │           │   │   │   ├── backup-actions.action.ts
│   │           │   │   │   ├── backup-actions.schema.ts
│   │           │   │   │   └── get-data.action.ts
│   │           │   │   └── backup-modal-context.tsx
│   │           │   ├── channels-policy/
│   │           │   │   ├── policy-form.tsx
│   │           │   │   ├── policy-modal.tsx
│   │           │   │   ├── policy.action.ts
│   │           │   │   └── policy.schema.ts
│   │           │   ├── cron-button/
│   │           │   │   ├── advanced-cron-select.tsx
│   │           │   │   ├── cron-button.tsx
│   │           │   │   ├── cron-input.tsx
│   │           │   │   └── cron.action.ts
│   │           │   ├── database-form/
│   │           │   │   ├── database-form.tsx
│   │           │   │   ├── form-database.action.ts
│   │           │   │   └── form-database.schema.ts
│   │           │   ├── health/
│   │           │   │   └── health-modal.tsx
│   │           │   ├── import/
│   │           │   │   ├── import-modal.tsx
│   │           │   │   ├── upload-backup-zone.tsx
│   │           │   │   └── upload-backup.action.ts
│   │           │   ├── restore-form.schema.ts
│   │           │   ├── restore-form.tsx
│   │           │   └── retention-policy/
│   │           │       ├── backup-retention-settings-form.tsx
│   │           │       ├── backup-retention-settings.action.tsx
│   │           │       ├── backup-retention-settings.schema.ts
│   │           │       ├── backup-retention-settings.tsx
│   │           │       └── retention-policy-sheet.tsx
│   │           ├── health/
│   │           │   └── heath-grid.tsx
│   │           ├── organization/
│   │           │   ├── create-organisation-modal.tsx
│   │           │   ├── delete-organization-button.tsx
│   │           │   ├── migration/
│   │           │   │   ├── migration-flow.tsx
│   │           │   │   ├── migration-tool.tsx
│   │           │   │   ├── migration.action.ts
│   │           │   │   ├── source-panel.tsx
│   │           │   │   └── target-panel.tsx
│   │           │   ├── organization-combobox.tsx
│   │           │   ├── settings/
│   │           │   │   ├── columns-organization-members.tsx
│   │           │   │   ├── member.schema.ts
│   │           │   │   ├── settings-organization-members-table.tsx
│   │           │   │   └── update-member.action.ts
│   │           │   └── tabs/
│   │           │       ├── organization-channels-tab/
│   │           │       │   ├── organization-agents-tab.tsx
│   │           │       │   ├── organization-notifiers-tab.tsx
│   │           │       │   └── organization-storages-tab.tsx
│   │           │       └── organization-tabs.tsx
│   │           ├── profile/
│   │           │   ├── actions/
│   │           │   │   ├── avatar.action.ts
│   │           │   │   ├── profile.action.ts
│   │           │   │   ├── provider.action.ts
│   │           │   │   └── security.action.ts
│   │           │   ├── components/
│   │           │   │   ├── avatar-with-upload.tsx
│   │           │   │   └── backup-codes-list.tsx
│   │           │   ├── form/
│   │           │   │   ├── 2fa-form.tsx
│   │           │   │   ├── 2fa.schema.ts
│   │           │   │   ├── reset-password-form.tsx
│   │           │   │   └── set-password-form.tsx
│   │           │   ├── modal/
│   │           │   │   ├── disable-2fa-modal.tsx
│   │           │   │   ├── reset-password-modal.tsx
│   │           │   │   ├── set-password-modal.tsx
│   │           │   │   ├── setup-2fa-modal.tsx
│   │           │   │   └── view-backup-codes-modal.tsx
│   │           │   ├── profile-account.tsx
│   │           │   ├── profile-apperance.tsx
│   │           │   ├── profile-general.tsx
│   │           │   ├── profile-providers.tsx
│   │           │   ├── profile-security.tsx
│   │           │   └── schemas/
│   │           │       ├── account.schema.ts
│   │           │       ├── general.schema.ts
│   │           │       ├── provider.schema.ts
│   │           │       └── security.schema.ts
│   │           ├── projects/
│   │           │   ├── button-delete-project/
│   │           │   │   ├── button-delete-project.tsx
│   │           │   │   └── delete-project.action.ts
│   │           │   ├── database/
│   │           │   │   ├── database-backup-list.tsx
│   │           │   │   ├── database-content.tsx
│   │           │   │   ├── database-kpi.tsx
│   │           │   │   ├── database-restore-list.tsx
│   │           │   │   └── database-tabs.tsx
│   │           │   └── project-card/
│   │           │       ├── project-card.tsx
│   │           │       └── project-database-card.tsx
│   │           └── statistics/
│   │               └── charts/
│   │                   ├── evolution-line-chart.tsx
│   │                   ├── fake-data.ts
│   │                   ├── line-chart.tsx
│   │                   ├── percentage-line-chart.tsx
│   │                   └── utils/
│   │                       └── placeholder.tsx
│   ├── db/
│   │   ├── index.ts
│   │   ├── migrations/
│   │   │   ├── 0000_awesome_nomad.sql
│   │   │   ├── 0001_wealthy_leo.sql
│   │   │   ├── 0002_pink_groot.sql
│   │   │   ├── 0003_absent_maestro.sql
│   │   │   ├── 0004_dazzling_hawkeye.sql
│   │   │   ├── 0005_old_swarm.sql
│   │   │   ├── 0006_moaning_pete_wisdom.sql
│   │   │   ├── 0007_last_umar.sql
│   │   │   ├── 0008_aberrant_scorpion.sql
│   │   │   ├── 0009_lucky_edwin_jarvis.sql
│   │   │   ├── 0010_past_trauma.sql
│   │   │   ├── 0011_outgoing_blob.sql
│   │   │   ├── 0012_peaceful_leopardon.sql
│   │   │   ├── 0013_past_logan.sql
│   │   │   ├── 0014_strong_galactus.sql
│   │   │   ├── 0015_absurd_next_avengers.sql
│   │   │   ├── 0016_broken_morgan_stark.sql
│   │   │   ├── 0017_wild_purple_man.sql
│   │   │   ├── 0018_smiling_mole_man.sql
│   │   │   ├── 0019_overjoyed_butterfly.sql
│   │   │   ├── 0020_low_giant_girl.sql
│   │   │   ├── 0020_thankful_sunspot.sql
│   │   │   ├── 0021_soft_blockbuster.sql
│   │   │   ├── 0022_purple_retro_girl.sql
│   │   │   ├── 0023_common_the_captain.sql
│   │   │   ├── 0024_lush_blindfold.sql
│   │   │   ├── 0025_past_franklin_richards.sql
│   │   │   ├── 0026_storage-backend.sql
│   │   │   ├── 0027_special_the_santerians.sql
│   │   │   ├── 0028_graceful_ben_parker.sql
│   │   │   ├── 0029_lowly_white_tiger.sql
│   │   │   ├── 0030_dizzy_morlocks.sql
│   │   │   ├── 0031_chemical_edwin_jarvis.sql
│   │   │   ├── 0032_sparkling_thunderbolt_ross.sql
│   │   │   ├── 0033_handy_valeria_richards.sql
│   │   │   ├── 0034_lush_speed.sql
│   │   │   ├── 0035_late_young_avengers.sql
│   │   │   ├── 0036_chief_night_thrasher.sql
│   │   │   ├── 0037_neat_talon.sql
│   │   │   ├── 0038_misty_red_hulk.sql
│   │   │   ├── 0039_conscious_solo.sql
│   │   │   ├── 0040_quick_lester.sql
│   │   │   ├── 0041_spooky_radioactive_man.sql
│   │   │   ├── 0042_breezy_namora.sql
│   │   │   ├── 0043_peaceful_chat.sql
│   │   │   ├── 0044_steep_wiccan.sql
│   │   │   ├── 0045_needy_martin_li.sql
│   │   │   ├── 0046_mysterious_menace.sql
│   │   │   ├── 0047_wet_carnage.sql
│   │   │   ├── 0048_yellow_eddie_brock.sql
│   │   │   ├── 0049_chief_terrax.sql
│   │   │   ├── 0050_dark_saracen.sql
│   │   │   ├── 0051_young_senator_kelly.sql
│   │   │   ├── 0052_cute_punisher.sql
│   │   │   ├── 0053_lyrical_union_jack.sql
│   │   │   └── meta/
│   │   │       ├── 0000_snapshot.json
│   │   │       ├── 0001_snapshot.json
│   │   │       ├── 0002_snapshot.json
│   │   │       ├── 0003_snapshot.json
│   │   │       ├── 0004_snapshot.json
│   │   │       ├── 0005_snapshot.json
│   │   │       ├── 0006_snapshot.json
│   │   │       ├── 0007_snapshot.json
│   │   │       ├── 0008_snapshot.json
│   │   │       ├── 0009_snapshot.json
│   │   │       ├── 0010_snapshot.json
│   │   │       ├── 0011_snapshot.json
│   │   │       ├── 0012_snapshot.json
│   │   │       ├── 0013_snapshot.json
│   │   │       ├── 0014_snapshot.json
│   │   │       ├── 0015_snapshot.json
│   │   │       ├── 0016_snapshot.json
│   │   │       ├── 0017_snapshot.json
│   │   │       ├── 0018_snapshot.json
│   │   │       ├── 0019_snapshot.json
│   │   │       ├── 0020_snapshot.json
│   │   │       ├── 0021_snapshot.json
│   │   │       ├── 0022_snapshot.json
│   │   │       ├── 0023_snapshot.json
│   │   │       ├── 0024_snapshot.json
│   │   │       ├── 0025_snapshot.json
│   │   │       ├── 0026_snapshot.json
│   │   │       ├── 0027_snapshot.json
│   │   │       ├── 0028_snapshot.json
│   │   │       ├── 0029_snapshot.json
│   │   │       ├── 0030_snapshot.json
│   │   │       ├── 0031_snapshot.json
│   │   │       ├── 0032_snapshot.json
│   │   │       ├── 0033_snapshot.json
│   │   │       ├── 0034_snapshot.json
│   │   │       ├── 0035_snapshot.json
│   │   │       ├── 0036_snapshot.json
│   │   │       ├── 0037_snapshot.json
│   │   │       ├── 0038_snapshot.json
│   │   │       ├── 0039_snapshot.json
│   │   │       ├── 0040_snapshot.json
│   │   │       ├── 0041_snapshot.json
│   │   │       ├── 0042_snapshot.json
│   │   │       ├── 0043_snapshot.json
│   │   │       ├── 0044_snapshot.json
│   │   │       ├── 0045_snapshot.json
│   │   │       ├── 0046_snapshot.json
│   │   │       ├── 0047_snapshot.json
│   │   │       ├── 0048_snapshot.json
│   │   │       ├── 0049_snapshot.json
│   │   │       ├── 0050_snapshot.json
│   │   │       ├── 0051_snapshot.json
│   │   │       ├── 0052_snapshot.json
│   │   │       ├── 0053_snapshot.json
│   │   │       └── _journal.json
│   │   ├── schema/
│   │   │   ├── 00_common.ts
│   │   │   ├── 01_setting.ts
│   │   │   ├── 02_user.ts
│   │   │   ├── 03_organization.ts
│   │   │   ├── 04_member.ts
│   │   │   ├── 05_invitation.ts
│   │   │   ├── 06_project.ts
│   │   │   ├── 07_database.ts
│   │   │   ├── 08_agent.ts
│   │   │   ├── 09_notification-channel.ts
│   │   │   ├── 10_alert-policy.ts
│   │   │   ├── 11_notification-log.ts
│   │   │   ├── 12_storage-channel.ts
│   │   │   ├── 13_storage-policy.ts
│   │   │   ├── 14_storage-backup.ts
│   │   │   ├── 15_healthcheck-log.ts
│   │   │   └── types.ts
│   │   ├── services/
│   │   │   ├── agent.ts
│   │   │   ├── backup.ts
│   │   │   ├── database.ts
│   │   │   ├── healthcheck.ts
│   │   │   ├── notification-channel.ts
│   │   │   ├── notification-log.ts
│   │   │   ├── storage-channel.ts
│   │   │   └── user.ts
│   │   └── utils/
│   │       └── index.ts
│   ├── env.mjs
│   ├── features/
│   │   ├── agents/
│   │   │   ├── agents.action.ts
│   │   │   ├── agents.schema.ts
│   │   │   ├── components/
│   │   │   │   ├── agent-organizations.action.ts
│   │   │   │   ├── agent-organizations.form.tsx
│   │   │   │   ├── agent-organizations.schema.ts
│   │   │   │   ├── agent.dialog.tsx
│   │   │   │   └── agent.form.tsx
│   │   │   └── hooks/
│   │   │       └── use-agent-update-check.ts
│   │   ├── browser/
│   │   │   ├── theme-meta-updater-root.tsx
│   │   │   └── theme-meta-updater.tsx
│   │   ├── dashboard/
│   │   │   ├── backup/
│   │   │   │   └── columns.tsx
│   │   │   ├── organization-cookie.ts
│   │   │   └── restore/
│   │   │       ├── columns.tsx
│   │   │       └── restore.action.ts
│   │   ├── keys/
│   │   │   └── keys.action.ts
│   │   ├── layout/
│   │   │   ├── Header.tsx
│   │   │   ├── card-auth.tsx
│   │   │   └── page.tsx
│   │   ├── notifications/
│   │   │   ├── dispatch.ts
│   │   │   ├── helpers.ts
│   │   │   ├── providers/
│   │   │   │   ├── discord.ts
│   │   │   │   ├── gotify.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── ntfy.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── smtp.ts
│   │   │   │   ├── telegram.ts
│   │   │   │   └── webhook.ts
│   │   │   └── types.ts
│   │   ├── organization/
│   │   │   ├── components/
│   │   │   │   ├── edit-organization.dialog.tsx
│   │   │   │   └── organization.form.tsx
│   │   │   ├── organization.action.ts
│   │   │   └── organization.schema.ts
│   │   ├── projects/
│   │   │   ├── components/
│   │   │   │   ├── project.dialog.tsx
│   │   │   │   └── project.form.tsx
│   │   │   ├── projects.action.ts
│   │   │   └── projects.schema.ts
│   │   ├── shared/
│   │   │   └── event.ts
│   │   ├── storages/
│   │   │   ├── dispatch.ts
│   │   │   ├── helpers.ts
│   │   │   ├── providers/
│   │   │   │   ├── google-drive/
│   │   │   │   │   ├── helpers.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── local.ts
│   │   │   │   └── s3.ts
│   │   │   └── types.ts
│   │   ├── theme/
│   │   │   ├── mode-toggle.tsx
│   │   │   └── theme-provider.tsx
│   │   ├── updates/
│   │   │   ├── components/
│   │   │   │   └── update-notification.tsx
│   │   │   ├── hooks/
│   │   │   │   └── use-update-check.ts
│   │   │   └── services/
│   │   │       └── github.ts
│   │   └── upload/
│   │       └── public/
│   │           └── upload.action.ts
│   ├── fonts/
│   │   └── fonts.ts
│   ├── hooks/
│   │   ├── use-mobile.ts
│   │   ├── use-mobile.tsx
│   │   └── use-organization-permissions.ts
│   ├── lib/
│   │   ├── acl/
│   │   │   └── organization-acl.ts
│   │   ├── auth/
│   │   │   ├── auth-client.ts
│   │   │   ├── auth.ts
│   │   │   ├── config.ts
│   │   │   ├── current-user.ts
│   │   │   ├── oauth.ts
│   │   │   ├── oidc.ts
│   │   │   └── permissions.ts
│   │   ├── email/
│   │   │   ├── helpers.ts
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   ├── logger.ts
│   │   ├── safe-actions/
│   │   │   └── actions.ts
│   │   ├── services.ts
│   │   ├── tasks/
│   │   │   ├── cleaning/
│   │   │   │   └── index.ts
│   │   │   ├── database/
│   │   │   │   ├── index.ts
│   │   │   │   ├── retention-count.ts
│   │   │   │   ├── retention-days.ts
│   │   │   │   ├── retention-gsf.ts
│   │   │   │   └── utils/
│   │   │   │       └── delete.ts
│   │   │   └── index.ts
│   │   ├── twx.tsx
│   │   ├── utils.ts
│   │   └── zod.ts
│   ├── middleware/
│   │   ├── errorHandler.ts
│   │   └── loggingMiddleware.ts
│   ├── types/
│   │   ├── action-type.ts
│   │   ├── auth.ts
│   │   ├── common.ts
│   │   └── next.ts
│   └── utils/
│       ├── common.ts
│       ├── cron.ts
│       ├── date-formatting.ts
│       ├── detection.ts
│       ├── edge_key.ts
│       ├── get-server-url.ts
│       ├── init.ts
│       ├── mock-data.ts
│       ├── name-from-email.ts
│       ├── os-parser.ts
│       ├── password.ts
│       ├── rsa-keys.ts
│       ├── slugify.ts
│       ├── text.ts
│       └── verify-uuid.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
# Ignore Docker-related configs
.dockerignore
docker-compose.yml
docker-compose*.yml

# Development/Editor config
.idea
.vscode

# Node-related
node_modules
npm-debug.log
yarn-debug.log
yarn-error.log
pnpm-debug.log

# Next.js build output
.next
out



# Docs and markdowns
README.md
*.md


================================================
FILE: .eslintrc.json
================================================
{
  "extends": [
    "next/core-web-vitals",
    "plugin:tailwindcss/recommended"
  ],
  "rules": {
    "react/no-unescaped-entities": "off",
    "react-hooks/rules-of-hooks": "off"

  }
}


================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
contact@portabase.io.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior,  harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

================================================
FILE: .github/CONTRIBUTING.md
================================================
---

# Contributing to Portabase

Thank you for considering contributing to **Portabase!** 🎉 Contributions help make this project better for everyone.

Please take a moment to review this guide. It will help you understand how to contribute effectively.

---

## Table of Contents

1. [How to Get Started](#how-to-get-started)
2. [Reporting Issues](#reporting-issues)
3. [Submitting Changes](#submitting-changes)
4. [Code Style Guidelines](#code-style-guidelines)
5. [Pull Request Process](#pull-request-process)
6. [Community Guidelines](#community-guidelines)

---

## How to Get Started

1. **Fork the repository**  
   Click the "Fork" button at the top-right corner of this repository.

2. **Clone the repository**
   ```bash
   git clone https://github.com/Portabase/portabase
   ```

3. **Set up the development environment**  
   Follow the steps in the `README.md` to install dependencies and configure the project.

4. **Create a branch**  
   Use the feature branch to work on changes.
   ```bash
   git checkout -b feature/<feature-name>
   ```

---

## Reporting Issues

If you encounter a bug or have a suggestion for improvement, follow these steps:

1. **Check existing issues** to avoid duplicates.
2. **Open a new issue** if needed:
    - Provide a clear and descriptive title.
    - Describe the issue with steps to reproduce it (if applicable).
    - Include relevant logs, screenshots, or code snippets.

---

## Submitting Changes

1. **Ensure your branch is up to date**
   ```bash
   git pull origin main
   ```

2. **Write meaningful commit messages**  
   Follow this format:
   ```
   [type] Summary of changes
   ```
   Example:
   ```
   feat: add user authentication
   fix: resolve crash on login page
   ```

3. **Push your branch**
   ```bash
   git push origin feat/<feature-name>
   ```

4. **Open a Pull Request (PR)**  
   Go to the repository on GitHub and click "New Pull Request."

---

## Code Style Guidelines

- Follow the [specific coding style guide] (e.g., Prettier, ESLint, PEP8,Biome).
- Use meaningful variable names and include comments where necessary.
- Tests before submitting your changes.

---

## Pull Request Process

1. Ensure your code passes all tests and linters.
2. Provide a clear description of what your PR does.
3. Reference any related issues (e.g., `Closes #123`).
4. Wait for a review from a maintainer.

---

## Community Guidelines

- Be respectful and inclusive to all contributors.
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md).
- Feel free to ask questions if you’re unsure about something.

---

Thank you for contributing! 🙌

---


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # 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
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: portabase
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/SECURITY.md
================================================

---

# Security Policy

## Supported Versions

We take security seriously and aim to support the following versions of the project with security updates:

| Version | Supported          |  
|---------|--------------------|  
| Latest  | ✅ Fully Supported |  

---

## Reporting a Vulnerability

If you discover a security vulnerability in this project, we appreciate your help in disclosing it responsibly.

1. **Contact Us**  
   Please report the vulnerability by emailing **[contact@portabase.io](mailto:contact@portabase.io)**. Include the following details:
    - A detailed description of the issue.
    - Steps to reproduce the vulnerability (if applicable).
    - Any potential impacts or risks.

2. **Response Time**  
   We aim to respond to security reports within **72 hours**. Once the issue is verified, we will:
    - Acknowledge receipt of your report.
    - Provide a timeline for addressing the issue.
    - Keep you informed throughout the process.

3. **Public Disclosure**  
   We will coordinate with you before publicly disclosing the vulnerability. Credit will be given to the reporter unless otherwise requested.

---

## Security Best Practices

We encourage all users to:
- Use the latest stable version of the project.
- Review the project’s dependencies and update them regularly.
- Follow secure coding practices when using this project.

---

## Thanks

We thank the security community for their vigilance and help in keeping this project secure!

---

================================================
FILE: .github/workflows/discord.yml
================================================
name: Discord Notification

on:
  workflow_call:
    inputs:
      release_tag:
        required: true
        type: string
      discord_title:
        required: true
        type: string
      discord_color:
        required: true
        type: number
      discord_footer:
        required: true
        type: string
    secrets:
      DISCORD_WEBHOOK:
        required: true
      GH_TOKEN:
        required: true

jobs:
  notify-discord:
    runs-on: ubuntu-latest
    steps:
      - name: Send Discord Notification
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
        run: |
          RELEASE_INFO=$(gh release view "${{ inputs.release_tag }}" -R ${{ github.repository }} --json name,url,body,author)

          RELEASE_TITLE=$(echo "$RELEASE_INFO" | jq -r .name)
          if [ -z "$RELEASE_TITLE" ] || [ "$RELEASE_TITLE" = "null" ]; then RELEASE_TITLE="${{ inputs.release_tag }}"; fi

          RELEASE_URL=$(echo "$RELEASE_INFO" | jq -r .url)
          RELEASE_BODY=$(echo "$RELEASE_INFO" | jq -r .body)

          AUTHOR_NAME="Portabase"
          AUTHOR_ICON="https://github.com/Portabase.png"

          PAYLOAD=$(jq -n \
            --arg title "$RELEASE_TITLE" \
            --arg description "$RELEASE_BODY" \
            --arg url "$RELEASE_URL" \
            --arg author "$AUTHOR_NAME" \
            --arg icon "$AUTHOR_ICON" \
            --arg discord_title "${{ inputs.discord_title }}" \
            --arg discord_footer "${{ inputs.discord_footer }}" \
            --argjson discord_color ${{ inputs.discord_color }} \
            '{
              content: $discord_title,
              embeds: [{
                title: $title,
                url: $url,
                description: $description,
                color: $discord_color,
                author: {
                  name: $author,
                  icon_url: $icon
                },
                footer: {
                  text: $discord_footer
                }
              }]
            }'
          )

          curl -H "Content-Type: application/json" \
               -d "$PAYLOAD" \
               "$DISCORD_WEBHOOK"


================================================
FILE: .github/workflows/docker.yml
================================================
name: Docker Publish

on:
  workflow_call:
    inputs:
      version:
        required: true
        type: string
      ref:
        required: true
        type: string
      image_name:
        required: false
        type: string
        default: "portabase/portabase"
      add_latest:
        required: false
        type: boolean
        default: false
      target:
        required: false
        type: string
        default: "prod"
      dockerfile:
        required: false
        type: string
        default: "./docker/dockerfile/Dockerfile"
    secrets:
      DOCKER_USERNAME:
        required: true
      DOCKER_PASSWORD:
        required: true

jobs:
  build:
    name: Build architectures
    runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
    strategy:
      fail-fast: false
      matrix:
        platform: [ linux/amd64, linux/arm64 ]
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          fetch-depth: 0

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Prepare Image Tags
        id: prep
        run: |
          ARCH=${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
          echo "image=${{ inputs.image_name }}:${{inputs.version}}-$ARCH" >> $GITHUB_OUTPUT
          echo "safe_platform=${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }}" >> $GITHUB_OUTPUT


      - name: Build and push image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ${{ inputs.dockerfile }}
          platforms: ${{ matrix.platform }}
          push: true
          tags: ${{ steps.prep.outputs.image }}
          target: ${{ inputs.target }}
          cache-from: type=gha,scope=build-${{ matrix.platform }}
          cache-to: type=gha,mode=max,scope=build-${{ matrix.platform }}

      - name: Save image name for manifest
        run: echo "${{ steps.prep.outputs.image }}" > image.txt

      - uses: actions/upload-artifact@v4
        with:
          name: image-${{ steps.prep.outputs.safe_platform }}
          path: image.txt
          retention-days: 1

  create-manifest:
    name: Create multi-arch manifest
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: image-linux-amd64
          path: /tmp/digests/amd64

      - uses: actions/download-artifact@v4
        with:
          name: image-linux-arm64
          path: /tmp/digests/arm64

      - name: Login to Docker
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Generate semantic tags
        id: tags
        run: |
          VERSION="${{ inputs.version }}"
          IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
          
          echo "VERSION_TAG=$VERSION" >> $GITHUB_OUTPUT
          if [ -n "$MINOR" ]; then
            echo "MINOR_TAG=$MAJOR.$MINOR" >> $GITHUB_OUTPUT
          fi
          if [ -n "$MAJOR" ]; then
            echo "MAJOR_TAG=$MAJOR" >> $GITHUB_OUTPUT
          fi
          if [ "${{ inputs.add_latest }}" = "true" ]; then
            echo "LATEST_TAG=latest" >> $GITHUB_OUTPUT
          fi

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ inputs.image_name }}
          tags: |
            type=raw,value=${{ steps.tags.outputs.VERSION_TAG }}
            type=raw,value=${{ steps.tags.outputs.MINOR_TAG }}
            type=raw,value=${{ steps.tags.outputs.MAJOR_TAG }}
            type=raw,value=${{ steps.tags.outputs.LATEST_TAG }}

      - name: Create and push manifest list
        working-directory: /tmp/digests
        run: |
          DOCKER_IMAGES="$(cat amd64/image.txt) $(cat arm64/image.txt)"
          TAG_ARGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
          echo $TAG_ARGS
          docker buildx imagetools create $TAG_ARGS $DOCKER_IMAGES


================================================
FILE: .github/workflows/e2e.yml
================================================
name: End-to-end testing

on:
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    if: github.event.pull_request.head.repo.full_name == github.repository

    steps:
      - uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./docker/dockerfile/Dockerfile
          push: false
          load: true
          tags: portabase/portabase:test

      - name: Run app container
        run: |
          docker run -d --name myapp \
            -p 8887:80 \
            -e NODE_ENV=production \
            -e PROJECT_URL=http://localhost:8887 \
            -e PROJECT_SECRET=80af5e4c875dfded3a6ba511c6ed8b0160cad723f848ee0851403ed6141f6ba1 \
            portabase/portabase:test

      - name: Wait for app port to be listening
        run: |
          for i in {1..40}; do
          
            if curl -fsS http://localhost:8887/ >/dev/null; then
              echo "App is responding on port 8887"
              exit 0
            fi
            echo "Waiting for app readiness... ($i/40)"
            sleep 2
          done
          echo "Timeout: app never became ready"
          docker logs myapp
          exit 1

      - name: Install Playwright browsers
        run: pnpm exec playwright install --with-deps chromium

      - name: Run Playwright tests
        run: pnpm exec playwright test
        env:
          PROJECT_URL: http://localhost:8887
          # E2E Notification
          E2E_NOTIFICATION_SMTP_HOST: ${{ secrets.E2E_NOTIFICATION_SMTP_HOST }}
          E2E_NOTIFICATION_SMTP_PORT: ${{ secrets.E2E_NOTIFICATION_SMTP_PORT }}
          E2E_NOTIFICATION_SMTP_USER: ${{ secrets.E2E_NOTIFICATION_SMTP_USER }}
          E2E_NOTIFICATION_SMTP_PASSWORD: ${{ secrets.E2E_NOTIFICATION_SMTP_PASSWORD }}
          E2E_NOTIFICATION_SMTP_FROM: ${{ secrets.E2E_NOTIFICATION_SMTP_FROM }}
          E2E_NOTIFICATION_SMTP_TO: ${{ secrets.E2E_NOTIFICATION_SMTP_TO }}
          E2E_NOTIFICATION_SLACK_WEBHOOK: ${{ secrets.E2E_NOTIFICATION_SLACK_WEBHOOK }}
          E2E_NOTIFICATION_DISCORD_WEBHOOK: ${{ secrets.E2E_NOTIFICATION_DISCORD_WEBHOOK }}
          E2E_NOTIFICATION_TELEGRAM_BOT_TOKEN: ${{ secrets.E2E_NOTIFICATION_TELEGRAM_BOT_TOKEN }}
          E2E_NOTIFICATION_TELEGRAM_CHAT_ID: ${{ secrets.E2E_NOTIFICATION_TELEGRAM_CHAT_ID }}
          E2E_NOTIFICATION_TELEGRAM_TOPIC_ID: ${{ secrets.E2E_NOTIFICATION_TELEGRAM_TOPIC_ID }}
          E2E_NOTIFICATION_GOTIFY_SERVER_URL: ${{ secrets.E2E_NOTIFICATION_GOTIFY_SERVER_URL }}
          E2E_NOTIFICATION_GOTIFY_APP_TOKEN: ${{ secrets.E2E_NOTIFICATION_GOTIFY_APP_TOKEN }}
          E2E_NOTIFICATION_NTFY_TOPIC: ${{ secrets.E2E_NOTIFICATION_NTFY_TOPIC }}
          E2E_NOTIFICATION_NTFY_SERVER_URL: ${{ secrets.E2E_NOTIFICATION_NTFY_SERVER_URL }}
          E2E_NOTIFICATION_NTFY_TOKEN: ${{ secrets.E2E_NOTIFICATION_NTFY_TOKEN }}
          E2E_NOTIFICATION_NTFY_USERNAME: ${{ secrets.E2E_NOTIFICATION_NTFY_USERNAME }}
          E2E_NOTIFICATION_NTFY_PASSWORD: ${{ secrets.E2E_NOTIFICATION_NTFY_PASSWORD }}
          E2E_NOTIFICATION_WEBHOOK_URL: ${{ secrets.E2E_NOTIFICATION_WEBHOOK_URL }}
          E2E_NOTIFICATION_WEBHOOK_SECRET_HEADER: ${{ secrets.E2E_NOTIFICATION_WEBHOOK_SECRET_HEADER }}
          E2E_NOTIFICATION_WEBHOOK_SECRET: ${{ secrets.E2E_NOTIFICATION_WEBHOOK_SECRET }}

          # E2E Storage
          E2E_STORAGE_AWS_S3_ENDPOINT_URL: ${{ secrets.E2E_STORAGE_AWS_S3_ENDPOINT_URL }}
          E2E_STORAGE_AWS_S3_REGION: ${{ secrets.E2E_STORAGE_AWS_S3_REGION }}
          E2E_STORAGE_AWS_S3_ACCESS_KEY: ${{ secrets.E2E_STORAGE_AWS_S3_ACCESS_KEY }}
          E2E_STORAGE_AWS_S3_SECRET_KEY: ${{ secrets.E2E_STORAGE_AWS_S3_SECRET_KEY }}
          E2E_STORAGE_AWS_S3_BUCKET_NAME: ${{ secrets.E2E_STORAGE_AWS_S3_BUCKET_NAME }}
          E2E_STORAGE_AWS_S3_PORT: ${{ secrets.E2E_STORAGE_AWS_S3_PORT }}
          E2E_STORAGE_R2_ENDPOINT_URL: ${{ secrets.E2E_STORAGE_R2_ENDPOINT_URL }}
          E2E_STORAGE_R2_REGION: ${{ secrets.E2E_STORAGE_R2_REGION }}
          E2E_STORAGE_R2_ACCESS_KEY: ${{ secrets.E2E_STORAGE_R2_ACCESS_KEY }}
          E2E_STORAGE_R2_SECRET_KEY: ${{ secrets.E2E_STORAGE_R2_SECRET_KEY }}
          E2E_STORAGE_R2_BUCKET_NAME: ${{ secrets.E2E_STORAGE_R2_BUCKET_NAME }}
          E2E_STORAGE_R2_PORT: ${{ secrets.E2E_STORAGE_R2_PORT }}
          E2E_STORAGE_GOOGLE_DRIVE_CLIENT_ID: ${{ secrets.E2E_STORAGE_GOOGLE_DRIVE_CLIENT_ID }}
          E2E_STORAGE_GOOGLE_DRIVE_CLIENT_SECRET: ${{ secrets.E2E_STORAGE_GOOGLE_DRIVE_CLIENT_SECRET }}
          E2E_STORAGE_GOOGLE_DRIVE_FOLDER_ID: ${{ secrets.E2E_STORAGE_GOOGLE_DRIVE_FOLDER_ID }}

      - name: Upload report if failed
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

      - name: Cleanup
        if: always()
        run: docker stop myapp && docker rm myapp || true

================================================
FILE: .github/workflows/helm.yml
================================================
name: Publish Helm Chart
on:
  workflow_call:
    inputs:
      version:
        required: true
        type: string
    secrets:
      GH_TOKEN:
        required: true

jobs:
  publish-helm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Helm
        uses: azure/setup-helm@v4

      - name: Package Helm chart
        run: |
          mkdir -p ./helm-packages
          helm package helm \
            --version ${{ inputs.version }} \
            --app-version ${{ inputs.version }} \
            --destination ./helm-packages

      - name: Authenticate to GitHub Packages
        run: |
          echo "${{ secrets.GH_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin

      - name: Push Helm chart to GitHub Packages (OCI)
        run: |
          helm push ./helm-packages/portabase-${{ inputs.version }}.tgz oci://ghcr.io/portabase/charts

================================================
FILE: .github/workflows/release-candidate.yml
================================================
name: Publish Docker image for release candidate

on:
  push:
    tags:
      - "*.*.*-rc*"
      - "!*-*-rc*"

permissions:
  contents: read
  packages: write

jobs:
  docker_publish:
    uses: ./.github/workflows/docker.yml
    with:
      add_latest: true
    secrets:
      DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }}
      DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }}

  discord_notification:
    needs: docker_publish
    uses: ./.github/workflows/discord.yml
    with:
      discord_title: "New Release Candidate: ${{ github.ref_name }}"
      discord_color: 2044800
      discord_footer: "Portabase"
    secrets:
      DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/release.yml
================================================
name: Auto Release & Publish

on:
  pull_request_target:
    types: [ closed ]
    branches:
      - main

permissions:
  contents: write
  packages: write

jobs:

  check-skip:
    runs-on: ubuntu-latest
    outputs:
      skip: ${{ steps.set-skip.outputs.skip }}
    steps:
      - name: Determine if release should be skipped
        id: set-skip
        run: |
          TITLE="${{ github.event.pull_request.title }}"
          echo "PR title: $TITLE"

          if [[ "$TITLE" == *"[skip-release]"* ]]; then
            echo "PR title contains [skip-release], skipping release jobs."
            echo "skip=true" >> $GITHUB_OUTPUT
          else
            echo "skip=false" >> $GITHUB_OUTPUT
          fi

  create-release:
    needs: check-skip
    if: ${{ needs.check-skip.outputs.skip == 'false' }}
    runs-on: ubuntu-latest
    outputs:
      draft_tag: ${{ steps.release_step.outputs.draft_tag }}
      version: ${{ steps.release_step.outputs.version }}
    steps:
      - uses: actions/create-github-app-token@v1
        id: app-token
        with:
          app-id: ${{ vars.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ steps.app-token.outputs.token }}
          ref: main

      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "lts/*"
          cache: "pnpm"

      - run: pnpm install --frozen-lockfile

      - run: |
          git config --global user.name 'github-actions[bot]'
          git config --global user.email 'github-actions[bot]@users.noreply.github.com'


      - name: Run release-it
        id: release_step
        run: |
          git pull origin main
          
          VERSION=$(pnpm exec release-it --ci --release-version)
          echo $VERSION
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          
          OUTPUT=$(pnpm exec release-it --ci)
          echo "$OUTPUT"
          
          DRAFT_TAG=$(echo "$OUTPUT" | grep -oE 'untagged-[a-z0-9]+')
          echo $DRAFT_TAG
          echo "draft_tag=$DRAFT_TAG" >> $GITHUB_OUTPUT
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}


  publish-docker:
    needs: create-release
    if: ${{ needs.create-release.result == 'success' }}
    uses: ./.github/workflows/docker.yml
    with:
      version: ${{ needs.create-release.outputs.version }}
      ref: ${{ needs.create-release.outputs.version }}
      add_latest: true
    secrets:
      DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }}
      DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }}

  publish-helm:
    needs: create-release
    if: ${{ needs.create-release.result == 'success' }}
    uses: ./.github/workflows/helm.yml
    with:
      version: ${{ needs.create-release.outputs.version }}
    secrets:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  finalize-release:
    needs:
      - create-release
      - publish-docker
      - publish-helm
    runs-on: ubuntu-latest
    outputs:
      release_tag: ${{ steps.publish_release_step.outputs.release_tag }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Publish GitHub Release
        id: publish_release_step
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          OUTPUT=$(gh release edit ${{ needs.create-release.outputs.draft_tag }} --draft=false)
          echo "$OUTPUT"
          RELEASE_TAG=$(echo "$OUTPUT" | sed -E 's|.*/releases/tag/||')
          echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT

  notify-discord:
    needs:
      - publish-docker
      - publish-helm
      - create-release
      - finalize-release
    uses: ./.github/workflows/discord.yml
    with:
      release_tag: ${{ needs.create-release.outputs.version }}
      discord_title: "New Release"
      discord_color: 3066993
      discord_footer: "Portabase"
    secrets:
      DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .github/workflows/security.yml
================================================
name: Security Checks
on:
  pull_request:
  push:
    branches: [ main ]

jobs:
  sca-deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/trivy-action@v0.35.0
        with:
          scan-type: 'fs'
          format: 'table'
          severity: 'CRITICAL,HIGH'
          ignore-unfixed: true

  secrets-gitleaks:
    if: github.event.pull_request.head.repo.fork == false
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
        with:
          config-path: .gitleaks.toml

================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.idea
.vscode
# dependencies
/node_modules
/.pnp
.pnp.js
#.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

public/uploads/*
!public/uploads/

private/uploads/*
private/keys/*

/.env

seeds/pocket-id/*.db

certificates

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
/e2e/local-storage.json


================================================
FILE: .gitleaks.toml
================================================
title = "Custom gitleaks config"

[allowlist]
# Global allowlist patterns (won’t be flagged)
regexes = []
paths = [
    "src/utils/init.ts"
]


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.2
    hooks:
      - id: gitleaks

================================================
FILE: .release-it.json
================================================
{
  "github": {
    "release": true,
    "draft": true,
    "tokenRef": "GITHUB_TOKEN"
  },
  "git": {
    "commit": true,
    "commitMessage": "chore: release ${version}",
    "requireCleanWorkingDir": true,
    "tag": true,
    "tagName": "${version}",
    "push": true
  },
  "plugins": {
    "@release-it/conventional-changelog": {
      "preset": {
        "name": "conventionalcommits",
        "types": [
          {
            "type": "feat",
            "section": "✨ Features"
          },
          {
            "type": "fix",
            "section": "🐛 Bug Fixes"
          },
          {
            "type": "perf",
            "section": "⚡️ Performance Improvements"
          },
          {
            "type": "revert",
            "section": "⏪️ Reverts"
          },
          {
            "type": "docs",
            "section": "📝 Documentation",
            "hidden": true
          },
          {
            "type": "chore",
            "section": "🔧 Chores",
            "hidden": true
          }
        ]
      }
    },
    "@release-it/bumper": {
      "out": {
        "file": "CITATION.cff",
        "path": "version",
        "type": "text/yaml"
      }
    }
  }
}


================================================
FILE: CITATION.cff
================================================
cff-version: 1.2.0
title: Portabase
message: If you use this software, please cite it as below.
type: software
authors:
  - family-names: Gauthereau
    given-names: Charles
  - family-names: Larcher
    given-names: Killian
repository-code: https://github.com/Portabase/portabase
url: https://portabase.io
abstract: >-
  Portabase is a free, open-source, self-hosted solution for database
  administration, providing backup and restore capabilities, scheduling,
  retention policies, notifications, and support for multiple storage backends.
  Its headless agent architecture enables connection to multiple database
  instances securely and efficiently.
keywords:
  - docker
  - kubernetes
  - backups
  - postgresql
  - mysql
  - mariadb
  - devops
  - database
  - monitoring
  - s3
  - self-hosted
  - system-administration
  - web-ui
  - agent
license: Apache-2.0
version: 1.13.0
date-released: '2026-03-02'


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2024 Portabase

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: Makefile
================================================
CLUSTER_SCRIPT=docker/entrypoints/app-dev-entrypoint.sh

up:
	@bash $(CLUSTER_SCRIPT)

seed-keycloak:
	@[ $$(ls -1 seeds/keycloak/*.json 2>/dev/null | wc -l) -gt 0 ] || (echo "No realm export found in seeds/keycloak. Add an export from Keycloak first."; exit 1)
	@docker compose -f docker-compose.func.yml stop keycloak >/dev/null 2>&1 || true
	@docker compose -f docker-compose.func.yml rm -f -s keycloak >/dev/null 2>&1 || true
	@docker volume rm portabase-dev-func_keycloak-data >/dev/null 2>&1 || true
	@docker compose -f docker-compose.func.yml up -d keycloak
	@echo "Keycloak seed import triggered from seeds/keycloak/*.json"

seed-pocket:
	@[ -f seeds/pocket-id/portabase.zip ] || (echo "No export found in seeds/pocket-id. Add an export from Pocket ID first."; exit 1)
	@docker compose -f docker-compose.func.yml stop pocket-id >/dev/null 2>&1 || true
	@docker compose -f docker-compose.func.yml rm -f -s pocket-id >/dev/null 2>&1 || true
	@docker volume rm portabase-dev-func_pocket-id-data >/dev/null 2>&1 || true
	@docker compose -f docker-compose.func.yml run --rm -v $$(pwd)/seeds/pocket-id/portabase.zip:/tmp/portabase.zip pocket-id ./pocket-id import --yes --path /tmp/portabase.zip >/dev/null
	@docker compose -f docker-compose.func.yml up -d pocket-id
	@sleep 2
	@docker compose -f docker-compose.func.yml exec pocket-id ./pocket-id one-time-access-token admin
	@echo "Pocket ID data restored from seeds/pocket-id/portabase.zip"

seed-auth: seed-keycloak seed-pocket

pocket-token:
	@docker compose -f docker-compose.func.yml exec pocket-id ./pocket-id one-time-access-token admin

export-pocket:
	@echo "Exporting Pocket ID data to seeds/pocket-id/portabase.zip..."
	@mkdir -p ./seeds/pocket-id
	@docker compose -f docker-compose.func.yml exec pocket-id ./pocket-id export --path /tmp/portabase-export.zip
	@docker compose -f docker-compose.func.yml cp pocket-id:/tmp/portabase-export.zip ./seeds/pocket-id/portabase.zip
	@docker compose -f docker-compose.func.yml exec pocket-id rm /tmp/portabase-export.zip
	@echo "Pocket ID data exported to seeds/pocket-id/portabase.zip"

export-keycloak:
	@echo "Exporting Keycloak configuration and users to seeds/keycloak/*.json..."
	@mkdir -p ./seeds/keycloak
	@rm -f ./seeds/keycloak/*.json
	@docker compose -f docker-compose.func.yml stop keycloak >/dev/null 2>&1
	@docker rm -f kc-exporter >/dev/null 2>&1 || true
	@docker compose -f docker-compose.func.yml run --name kc-exporter keycloak export --dir /tmp/kc-export --users realm_file
	@docker cp kc-exporter:/tmp/kc-export/. ./seeds/keycloak/
	@docker rm -f kc-exporter >/dev/null 2>&1
	@docker compose -f docker-compose.func.yml start keycloak >/dev/null 2>&1
	@echo "Keycloak configuration and users exported to seeds/keycloak/*.json"

end-to-end:
	@echo "Starting E2E testing..."
	@docker compose -f docker-compose.e2e.yml up -d
	@CI=true PROJECT_URL=http://localhost:8887 pnpm playwright test --project=chromium || (docker compose -f docker-compose.e2e.yml down --volumes && exit 1)
	@docker compose -f docker-compose.e2e.yml down --volumes
	@echo "Finished E2E testing successfully."

e2e-manual:
	@echo "Starting E2E testing..."
	@docker compose -f docker-compose.e2e.yml up -d
	@pnpm playwright test --ui || (docker compose -f docker-compose.e2e.yml down --volumes && exit 1)
	@docker compose -f docker-compose.e2e.yml down --volumes
	@echo "Finished E2E testing successfully."

================================================
FILE: README.md
================================================
<br />
<div align="center">
  <a href="https://portabase.io">
    <img src="/.github/assets/logo.png" alt="Logo" width="80" height="80">
  </a>

<h3 align="center">Portabase</h3>

  <p align="center" style="margin-top: 20px; font-style: italic;">
  <i>Portabase is a tool designed to simplify the backup and restoration of your database instances. It integrates seamlessly with <a href="https://github.com/Portabase/agent-rust">Portabase agents</a> for managing operations securely and efficiently.</i>
  </p>


[![License: Apache](https://img.shields.io/badge/License-apache-yellow.svg)](LICENSE)
[![Docker Pulls](https://img.shields.io/docker/pulls/portabase/portabase?color=brightgreen)](https://hub.docker.com/r/portabase/portabase)
[![Helm Chart](https://img.shields.io/badge/Helm-Kubernetes-326ce5?logo=helm&logoColor=white)](https://github.com/Portabase/portabase/pkgs/container/charts%2Fportabase)
[![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey)](https://github.com/Portabase/portabase)
[![Support Portabase](https://img.shields.io/badge/Support-Portabase-orange)](https://www.buymeacoffee.com/portabase)

[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-336791?logo=postgresql&logoColor=white)](https://www.postgresql.org/)
[![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=white)](https://www.mysql.com/)
[![MariaDB](https://img.shields.io/badge/MariaDB-003545?logo=mariadb&logoColor=white)](https://mariadb.org/)
[![SQLite](https://img.shields.io/badge/-SQLite-blue?logo=sqlite&logoColor=white)](https://sqlite.org/)
[![Redis](https://img.shields.io/badge/Redis-DC382D?style=flat&logo=Redis&logoColor=white)](https://redis.io/)
[![MongoDB](https://img.shields.io/badge/-MongoDB-13aa52?logo=mongodb&logoColor=white)](https://www.mongodb.com/)
[![Valkey](https://img.shields.io/badge/Valkey-6284fc?style=flat&logo=Valkey&logoColor=white)](https://valkey.io/)
[![Firebird](https://img.shields.io/badge/Firebird-f55b14?style=flat&logo=Firebird&logoColor=white)](https://firebirdsql.org/)

[![Self Hosted](https://img.shields.io/badge/self--hosted-yes-brightgreen)](https://github.com/Portabase/portabase)
[![Open Source](https://img.shields.io/badge/open%20source-❤️-red)](https://github.com/Portabase/portabase)

[![NextJS][NextJS]][NextJS-url]
[![BetterAuth][BetterAuth]][BetterAuth-url]
[![Drizzle][Drizzle]][Drizzle-url]
[![ShadcnUI][ShadcnUI]][ShadcnUI-url]
[![Docker][Docker]][Docker-url]

  <p>
    <strong>
        <a href="https://portabase.io">Website</a> •
        <a href="https://portabase.io/docs">Documentation</a> •
        <a href="https://www.youtube.com/watch?v=nSTzT27GgAg">Demo</a> •
        <a href="https://portabase.io/docs/dashboard/setup">Installation</a> •
        <a href="https://github.com/Portabase/portabase/issues/new?labels=bug&template=bug-report---.md">Report Bug</a> •
        <a href="https://github.com/Portabase/portabase/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
    </strong>
  </p>

![portabase-dashboard](https://github.com/user-attachments/assets/8f2c69d6-f1f9-4b80-b51c-01f6f13b9b62)


</div>

## Installation

You have 4 ways to install Portabase:

- Automated CLI (recommended) - [details](https://portabase.io/docs/dashboard/setup#cli)
- Docker Run - [details](https://portabase.io/docs/dashboard/setup#docker)
- Docker Compose setup - [details](https://portabase.io/docs/dashboard/setup#docker-compose)
- Kubernetes with Helm [details](https://portabase.io/docs/dashboard/setup#helm)
- Development setup - [details](https://portabase.io/docs/dashboard/setup#development)

**Ensure Docker is installed on your machine before getting started.**

## Supported databases

| Engine             | Support    | Supported Versions            | Restore |
|:-------------------|:-----------|:------------------------------|:--------|
| **PostgreSQL**     | ✅ Stable   | 12, 13, 14, 15, 16, 17 and 18 | Yes     |
| **MySQL**          | ✅ Stable   | 5.7, 8 and 9                  | Yes     |
| **MariaDB**        | ✅ Stable   | 10 and 11                     | Yes     |
| **MongoDB**        | ✅ Stable   | 4, 5, 6, 7 and 8              | Yes     |
| **SQLite**         | ✅ Stable   | 3.x                           | Yes     |
| **Redis**          | ✅ Stable   | 2.8+                          | No      |
| **Valkey**         | ✅ Stable   | 7.2+                          | No      |
| **Firebird**       | ✅ Stable   | 3.0, 4.0, 5.0                 | Yes     |
| **MSSQL Server**   | ❌ Ongoing  | -                             | Yes     |

See the [Database Servers documentation](https://portabase.io/docs/agent/db) for version-specific backup and restore details.

## Contributors

[![Contributors](https://contrib.rocks/image?repo=Portabase/portabase)](https://github.com/Portabase/portabase/graphs/contributors)

[!["Support Portabase"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/portabase)

## License

Distributed under the Apache License. See `LICENSE.txt` for more details.


[Docker]: https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=fff&style=for-the-badge

[NextJS]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white

[BetterAuth]: https://img.shields.io/badge/Better%20Auth-FFF?logo=betterauth&logoColor=000&style=for-the-badge

[Drizzle]: https://img.shields.io/badge/Drizzle-111?style=for-the-badge&logo=Drizzle&logoColor=c5f74f

[ShadcnUI]: https://img.shields.io/badge/shadcn/ui-000000?style=for-the-badge&logo=shadcn/ui&logoColor=white

[NextJS-url]: https://nextjs.org/

[BetterAuth-url]: https://www.better-auth.com/

[Drizzle-url]: https://orm.drizzle.team/

[ShadcnUI-url]: https://ui.shadcn.com/

[Docker-url]: https://www.docker.com/


================================================
FILE: app/(auth)/forgot-password/page.tsx
================================================
import { CardContent, CardHeader } from "@/components/ui/card";

import { TooltipProvider } from "@/components/ui/tooltip";
import { ForgotPasswordForm } from "@/components/wrappers/auth/login/forgot-password-form/forgot-password-form";
import { env } from "@/env.mjs";
import { CardAuth } from "@/features/layout/card-auth";
import { redirect } from "next/navigation";

export default async function RoutePage(props: { searchParams: Promise<{ callbackUrl: string | undefined }> }) {
    if (env.AUTH_EMAIL_PASSWORD_ENABLED !== "true") {
        redirect("/login");
    }

    return (
        <TooltipProvider>
            <CardAuth className="w-full">
                <CardHeader>
                    <div className="grid gap-2 text-center mb-2">
                        <h1 className="text-3xl font-bold">Reset password</h1>
                        <p className="text-balance text-muted-foreground">Enter your email address and we'll send you a link to reset your password.</p>
                    </div>
                </CardHeader>
                <CardContent>
                    <ForgotPasswordForm />
                </CardContent>
            </CardAuth>
        </TooltipProvider>
    );
}


================================================
FILE: app/(auth)/guard/page.tsx
================================================
import {CardContent, CardHeader} from "@/components/ui/card";
import {TooltipProvider} from "@/components/ui/tooltip";
import {GuardForm} from "@/components/wrappers/auth/guard/guard-form";
import {cookies} from "next/headers";
import {redirect} from "next/navigation";
import {CardAuth} from "@/features/layout/card-auth";

export default async function GuardPage() {

    const cookieStore = await cookies();
    const token = cookieStore.get("better-auth.two_factor")?.value || cookieStore.get("__Secure-better-auth.two_factor")?.value;
    
    if (!token) {
        redirect("/login");
    }

    return (
        <TooltipProvider>
            <CardAuth className="w-full">
                <CardHeader>
                    <div className="grid gap-2 text-center mb-2">
                        <h1 className="text-3xl font-bold">Two-factor verification</h1>
                        <p className="text-balance text-muted-foreground">Please enter the verification code generated
                            by your authentication app.</p>
                    </div>
                </CardHeader>
                <CardContent>
                    <GuardForm/>
                </CardContent>
            </CardAuth>
        </TooltipProvider>
    );
}


================================================
FILE: app/(auth)/layout.tsx
================================================
import React from "react";
import { redirect } from "next/navigation";
import { currentUser } from "@/lib/auth/current-user";
import { AuthLogoSection } from "@/components/wrappers/auth/auth-logo-section";
import { Heart } from "lucide-react";

export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const user = await currentUser();

  if (user && !user.banned && user.role !== "pending") {
    redirect("/dashboard/home");
  }

  return (
    <div className="flex min-h-screen flex-col justify-between py-10 sm:px-6 lg:px-8 ">
      <div className="flex flex-col items-center justify-center flex-1">
        <div className="mx-auto w-full max-w-md">
          <AuthLogoSection />
          <div className="mt-4">{children}</div>
        </div>
      </div>
      <footer className="mt-8 text-center text-xs text-muted-foreground flex flex-col gap-1">
        <p className="flex items-center justify-center gap-1">
          Made with{" "}
          <Heart className="size-3 fill-red-500 text-red-500 animate-pulse" />{" "}
          by <span className="font-medium text-foreground">Portabase</span>
        </p>
      </footer>
    </div>
  );
}


================================================
FILE: app/(auth)/login/page.tsx
================================================
import { LoginForm } from "@/components/wrappers/auth/login/login-form/login-form";
import { Metadata } from "next";
import { SUPPORTED_PROVIDERS } from "@/lib/auth/config";
import { SocialAuthButtons } from "@/components/wrappers/auth/social-buttons";
import { TooltipProvider } from "@/components/ui/tooltip";
import { CardContent, CardHeader } from "@/components/ui/card";
import Link from "next/link";
import { Separator } from "@/components/ui/separator";
import { CardAuth } from "@/features/layout/card-auth";
import { env } from "@/env.mjs";

export const metadata: Metadata = {
  title: "Login",
};

export default async function SignInPage() {
  return (
    <TooltipProvider>
      <CardAuth className="w-full">
        <CardHeader>
          <div className="grid gap-2 text-center mb-2">
            <h1 className="text-3xl font-bold">Login</h1>
            <p className="text-balance text-muted-foreground">
              Fill your login informations
            </p>
          </div>
        </CardHeader>
        <CardContent className="space-y-4">
          {env.AUTH_EMAIL_PASSWORD_ENABLED === "true" && (
            <LoginForm isPasskeyEnabled={env.AUTH_PASSKEY_ENABLED === "true"} />
          )}

          {env.AUTH_EMAIL_PASSWORD_ENABLED === "true" &&
            SUPPORTED_PROVIDERS.filter((p) => !p.isManual && p.isActive)
              .length > 0 && (
              <div className="relative my-4 flex items-center justify-center overflow-hidden">
                <Separator />
                <div className="px-2 text-center  text-sm">OR</div>
                <Separator />
              </div>
            )}

          {SUPPORTED_PROVIDERS.filter((p) => !p.isManual && p.isActive).length >
            0 && <SocialAuthButtons providers={SUPPORTED_PROVIDERS} />}

          {env.AUTH_SIGNUP_ENABLED !== "true" ||
            (env.AUTH_EMAIL_PASSWORD_ENABLED === "true" && (
              <div className="mt-4 text-center text-sm">
                Don&apos;t have an account ?{" "}
                <Link href="/register" className="underline">
                  Sign up
                </Link>
              </div>
            ))}
        </CardContent>
      </CardAuth>
    </TooltipProvider>
  );
}


================================================
FILE: app/(auth)/register/page.tsx
================================================
import { PageParams } from "@/types/next";
import { RegisterForm } from "@/components/wrappers/auth/register/register-form/register-form";
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { env } from "@/env.mjs";

export const metadata: Metadata = {
  title: "Register",
};

export default async function RoutePage(props: PageParams<{}>) {
  if (
    env.AUTH_SIGNUP_ENABLED !== "true" ||
    env.AUTH_EMAIL_PASSWORD_ENABLED !== "true"
  ) {
    redirect("/login");
  }

  return (
    <div className="mx-auto grid w-full gap-6">
      <RegisterForm />
    </div>
  );
}


================================================
FILE: app/(auth)/reset-password/page.tsx
================================================
import { CardContent, CardHeader } from "@/components/ui/card";
import { TooltipProvider } from "@/components/ui/tooltip";
import { ResetPasswordForm } from "@/components/wrappers/auth/login/reset-password-form/reset-password-form";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth/auth";
import { Avatar, AvatarImage, AvatarFallback } from "@radix-ui/react-avatar";
import { CardAuth } from "@/features/layout/card-auth";
import { env } from "@/env.mjs";

export default async function RoutePage(props: { searchParams: Promise<{ token: string | undefined }> }) {
    if (env.AUTH_EMAIL_PASSWORD_ENABLED !== "true") {
        return redirect("/login");
    }

    const { token } = await props.searchParams;

    if (!token) {
        return redirect(`/login?error=invalid_or_expired_token`);
    }

    const verification = await (await auth.$context).internalAdapter.findVerificationValue(`reset-password:${token}`);

    if (!verification || verification.expiresAt < new Date()) {
        return redirect(`/login?error=invalid_or_expired_token`);
    }

    const user = await (await auth.$context).internalAdapter.findUserById(verification.value);

    return (
        <TooltipProvider>
            <CardAuth className="w-full">
                <CardHeader className="space-y-4">
                    <div className="space-y-1 text-center">
                        <h1 className="text-2xl font-bold tracking-tight">Set a new password</h1>
                        <p className="text-sm text-muted-foreground text-balance">Please enter your new password below.</p>
                    </div>

                    <div className="flex flex-col items-center space-y-2 text-center">
                        <Avatar className="relative flex h-16 w-16 shrink-0 overflow-hidden rounded-full border">
                            <AvatarImage src={user!.image ?? ""} alt={user!.name} className="aspect-square h-full w-full object-cover" />
                            <AvatarFallback className="flex h-full w-full items-center justify-center rounded-full bg-muted text-2xl font-medium text-muted-foreground">
                                {user!.name
                                    .split(" ")
                                    .map((n) => n[0])
                                    .join("")
                                    .toUpperCase()
                                    .slice(0, 2)}
                            </AvatarFallback>
                        </Avatar>

                        <div className="flex flex-col items-center">
                            <div className="font-semibold text-lg">{user!.name}</div>
                            <div className="text-sm text-muted-foreground">{user!.email}</div>
                        </div>
                    </div>
                </CardHeader>

                <CardContent>
                    <ResetPasswordForm />
                </CardContent>
            </CardAuth>
        </TooltipProvider>
    );
}


================================================
FILE: app/(customer)/dashboard/(admin)/admin/organizations/[organizationId]/page.tsx
================================================
import {notFound} from "next/navigation";
import {eq} from "drizzle-orm";
import {db} from "@/db";
import * as drizzleDb from "@/db";
import {PageParams} from "@/types/next";
import {Page} from "@/features/layout/page";
import {OrganizationManagement} from "@/components/wrappers/dashboard/admin/organizations/organization/organization-management";
import {buildOrganizationWithMembers} from "@/utils/common";
import {isUUID} from "@/utils/text";
import {user} from "@/db/schema/02_user";
import {invitation} from "@/db/schema/05_invitation";
import {member} from "@/db/schema/04_member";
import {organization} from "@/db/schema/03_organization";
import {user as drizzleUser} from "@/db/schema/02_user";


export default async function RoutePage(props: PageParams<{ organizationId: string }>) {
    const {organizationId} = await props.params;

    if (!organizationId) {
        return notFound();
    }

    if (!isUUID(organizationId)) {
        return notFound();
    }

    const users = await db.select().from(drizzleUser);

    const organizationData = await db
        .select({organization, member, user, invitation})
        .from(organization)
        .leftJoin(member, eq(drizzleDb.schemas.organization.id, member.organizationId))
        .leftJoin(invitation, eq(drizzleDb.schemas.invitation.id, invitation.organizationId))
        .leftJoin(user, eq(drizzleDb.schemas.member.userId, user.id))
        .where(eq(organization.id, organizationId));

    const formattedData = buildOrganizationWithMembers(organizationData);

    if (!formattedData) return notFound();

    return (
        <Page>
            <OrganizationManagement organization={formattedData} users={users}/>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(admin)/admin/organizations/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {db} from "@/db";
import {
    AdminOrganizationAddModal
} from "@/components/wrappers/dashboard/admin/organizations/organization/admin-organization-add-modal";
import {AdminOrganizationList} from "@/components/wrappers/dashboard/admin/organizations/organization/admin-orgnization-list";
import {isNull} from "drizzle-orm";

export default async function RoutePage(props: PageParams<{}>) {

    const organizations = await db.query.organization.findMany({
        where: (fields) => isNull(fields.deletedAt),
        with: {
            members: true,
        },
    });


    return (
        <Page>
            <PageHeader className="flex flex-col">
                <div className="flex justify-between">
                    <PageTitle className="mb-3">Active organizations</PageTitle>
                    <PageActions>
                        <AdminOrganizationAddModal/>
                    </PageActions>
                </div>
            </PageHeader>
            <PageContent className="flex flex-col gap-5">
                <AdminOrganizationList organizations={organizations}/>
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(admin)/admin/settings/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {db} from "@/db";
import {notFound} from "next/navigation";
import {SettingsTabs} from "@/components/wrappers/dashboard/admin/settings/settings-tabs";
import {desc, isNull} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
import {NotificationChannelWith} from "@/db/schema/09_notification-channel";

export default async function RoutePage(props: PageParams<{}>) {

    const settings = await db.query.setting.findFirst({
        where: (fields, {eq}) => eq(fields.name, "system"),
    });

    console.log(settings)

    const storageChannels = await db.query.storageChannel.findMany({
        with: {
            organizations: true
        },
        where: isNull(drizzleDb.schemas.storageChannel.organizationId),
        orderBy: desc(drizzleDb.schemas.storageChannel.createdAt)
    }) as StorageChannelWith[]

    const notificationChannels = await db.query.notificationChannel.findMany({
        with: {
            organizations: true
        },
        where: isNull(drizzleDb.schemas.notificationChannel.organizationId),
        orderBy: desc(drizzleDb.schemas.notificationChannel.createdAt)
    }) as NotificationChannelWith[]


    if (!settings || !storageChannels || !notificationChannels ) {
        notFound()
    }

    return (
        <Page>
            <PageHeader className="flex flex-col">
                <div className="flex justify-between">
                    <PageTitle className="mb-3">System settings</PageTitle>
                </div>
            </PageHeader>
            <PageContent className="flex flex-col gap-5">
                <SettingsTabs storageChannels={storageChannels} notificationChannels={notificationChannels} settings={settings} />
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(admin)/admin/users/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {db} from "@/db";
import {desc, isNull} from "drizzle-orm";
import {AdminUserList} from "@/components/wrappers/dashboard/admin/users/admin-user-list";
import {AdminUserAddModal} from "@/components/wrappers/dashboard/admin/users/admin-user-add-modal";
import {SUPPORTED_PROVIDERS} from "@/lib/auth/config";

export default async function RoutePage(props: PageParams<{}>) {

    const users = await db.query.user.findMany({
        where: (fields) => isNull(fields.deletedAt),
        with: {
            accounts: true
        },
        orderBy: (fields) => desc(fields.createdAt),

    });
    const organizations = await db.query.organization.findMany({
        with: {
            members: true,
        },
    });

    const credentialProvider = SUPPORTED_PROVIDERS.find(p => p.id === 'credential');
    const isPasswordAuthEnabled = credentialProvider?.isActive || false;

    return (
        <Page>
            <PageHeader className="flex flex-col">
                <div className="flex justify-between">
                    <PageTitle className="mb-3">Active users</PageTitle>
                    <PageActions>
                        <AdminUserAddModal organizations={organizations}/>
                    </PageActions>
                </div>
            </PageHeader>
            <PageContent className="flex flex-col gap-5">
                <AdminUserList users={users} isPasswordAuthEnabled={isPasswordAuthEnabled}/>
            </PageContent>
        </Page>
    );
}




================================================
FILE: app/(customer)/dashboard/(admin)/agents/[agentId]/page.tsx
================================================
import { PageParams } from "@/types/next";
import {
  Page,
  PageContent,
  PageDescription,
  PageTitle,
} from "@/features/layout/page";
import { db } from "@/db";
import * as drizzleDb from "@/db";
import {eq, isNull} from "drizzle-orm";
import { notFound } from "next/navigation";
import { ButtonDeleteAgent } from "@/components/wrappers/dashboard/agent/button-delete-agent/button-delete-agent";
import { capitalizeFirstLetter } from "@/utils/text";
import { generateEdgeKey } from "@/utils/edge_key";
import { getServerUrl } from "@/utils/get-server-url";
import { AgentContentPage } from "@/components/wrappers/dashboard/agent/agent-content";
import { AgentDialog } from "@/features/agents/components/agent.dialog";


export default async function RoutePage(
  props: PageParams<{ agentId: string }>,
) {
  const { agentId } = await props.params;

  const agent = await db.query.agent.findFirst({
    where: eq(drizzleDb.schemas.agent.id, agentId),
    with: {
      databases: true,
      organizations: true,
    },
  });

  const organizations = await db.query.organization.findMany({
    where: (fields) => isNull(fields.deletedAt),
    with: {
      members: true,
    },
  });


  if (!agent) {
    notFound();
  }

  const isOwnerByAnOrganization = agent.organizationId

  if (isOwnerByAnOrganization){
    notFound();
  }

  const organizationIds = agent.organizations.map(org => org.organizationId)

  const edgeKey = await generateEdgeKey(getServerUrl(), agent.id);

  return (
    <Page>
      <div className="justify-between gap-2 sm:flex">
        <PageTitle className="flex flex-col md:flex-row items-center justify-between w-full ">
          <div className="min-w-full md:min-w-fit ">
            {capitalizeFirstLetter(agent.name)}
          </div>
          <div className="flex items-center gap-2 md:justify-between w-full ">
            <div className="flex items-center gap-2">
              <AgentDialog
                agent={agent}
                typeTrigger={"edit"}
                adminView={true}
                organizations={organizations}
              />
            </div>
            <div className="flex items-center gap-2">
              <ButtonDeleteAgent organizationIds={organizationIds} agentId={agentId} text={"Delete Agent"} />
            </div>
          </div>
        </PageTitle>
      </div>

      {agent.description && (
        <PageDescription className="mt-5 sm:mt-0">
          {agent.description}
        </PageDescription>
      )}
      <PageContent className="flex flex-col w-full h-full justify-between gap-6">
        <AgentContentPage agent={agent} edgeKey={edgeKey} />
      </PageContent>
    </Page>
  );
}


================================================
FILE: app/(customer)/dashboard/(admin)/agents/page.tsx
================================================
import {PageParams} from "@/types/next";
import {AgentCard} from "@/components/wrappers/dashboard/agent/agent-card/agent-card";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {notFound} from "next/navigation";
import {db} from "@/db";
import * as drizzleDb from "@/db";
import {and, desc, eq, isNull, not} from "drizzle-orm";
import {Metadata} from "next";
import {AgentDialog} from "@/features/agents/components/agent.dialog";

export const metadata: Metadata = {
    title: "Agents",
};

export default async function RoutePage(props: PageParams<{}>) {

    const agents = await db.query.agent.findMany({
        where: and(not(eq(drizzleDb.schemas.agent.isArchived, true)),isNull(drizzleDb.schemas.agent.organizationId)),
        with: {
            databases: true
        },
        orderBy: (fields) => desc(fields.lastContact),
    });
    
    if (!agents) {
        notFound();
    }

    return (
        <Page>
            <PageHeader>
                <PageTitle>Agents</PageTitle>
                {agents.length > 0 && (
                    <PageActions>
                         <AgentDialog typeTrigger={"create"} />
                    </PageActions>
                )}
            </PageHeader>
            <PageContent>
                {agents.length > 0 ? (
                    <CardsWithPagination data={agents} cardItem={AgentCard} cardsPerPage={4} numberOfColumns={1}/>
                ) : (
                     <AgentDialog typeTrigger="empty"/>
                )}
            </PageContent>
        </Page>
    );
}

================================================
FILE: app/(customer)/dashboard/(admin)/layout.tsx
================================================
import React from "react";
import { notFound } from "next/navigation";
import { currentUser } from "@/lib/auth/current-user";

export default async function Layout({ children }: { children: React.ReactNode }) {
    const user = await currentUser();

    if (user!.role !== "superadmin" && user!.role !== "admin") {
        notFound();
    }

    return <>{children}</>;
}


================================================
FILE: app/(customer)/dashboard/(admin)/notifications/channels/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {Metadata} from "next";
import {db} from "@/db";
import {notificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {desc, isNull} from "drizzle-orm";
import {ChannelsSection} from "@/components/wrappers/dashboard/admin/channels/channels-section";
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
import * as drizzleDb from "@/db";

export const metadata: Metadata = {
    title: "Notification Channels",
};

export default async function RoutePage(props: PageParams<{}>) {

    const notificationChannels = await db.query.notificationChannel.findMany({
        with: {
            organizations: true
        },
        where: isNull(drizzleDb.schemas.notificationChannel.organizationId),
        orderBy: desc(notificationChannel.createdAt)
    }) as NotificationChannelWith[]

    const organizations = await db.query.organization.findMany({
        where: (fields) => isNull(fields.deletedAt),
        with: {
            members: true,
        },
    });


    return (
        <Page>
            <PageHeader>
                <PageTitle>Notification channels</PageTitle>
                <PageActions>
                    <ChannelAddEditModal kind="notification" adminView={false}/>
                </PageActions>
            </PageHeader>
            <PageContent>
                <ChannelsSection kind="notification" organizations={organizations}
                                 channels={notificationChannels}/>
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(admin)/notifications/logs/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {Metadata} from "next";
import {NotificationLogsList} from "@/components/wrappers/dashboard/admin/notifications/logs/notification-logs-list";
import {notFound} from "next/navigation";
import {getNotificationHistory} from "@/db/services/notification-log";

export const metadata: Metadata = {
    title: "Activity logs",
};

export default async function RoutePage(props: PageParams<{}>) {

    const notificationLogs = await getNotificationHistory()

    if (!notificationLogs) {
        notFound();
    }

    return (
        <Page>
            <PageHeader>
                <PageTitle>Activity logs</PageTitle>
            </PageHeader>
            <PageContent>
                <NotificationLogsList notificationLogs={notificationLogs} />
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(admin)/storages/channels/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {Metadata} from "next";
import {ChannelsSection} from "@/components/wrappers/dashboard/admin/channels/channels-section";
import {db} from "@/db";
import {desc, eq, isNull} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";

export const metadata: Metadata = {
    title: "Storage Channels",
};

export default async function RoutePage(props: PageParams<{}>) {

    const storageChannels = await db.query.storageChannel.findMany({
        with: {
            organizations: true
        },
        where: isNull(drizzleDb.schemas.storageChannel.organizationId),
        orderBy: desc(drizzleDb.schemas.storageChannel.createdAt)
    }) as StorageChannelWith[]

    const organizations = await db.query.organization.findMany({
        where: (fields) => isNull(fields.deletedAt),
        with: {
            members: true,
        },
    });

    const settings = await db.query.setting.findFirst({
        where: eq(drizzleDb.schemas.setting.name, "system"),
    });


    return (
        <Page>
            <PageHeader>
                <PageTitle>Storage channels</PageTitle>
                <PageActions>
                    <ChannelAddEditModal kind={"storage"} adminView={false}/>
                </PageActions>
            </PageHeader>
            <PageContent>
                <ChannelsSection defaultStorageChannelId={settings?.defaultStorageChannelId} kind={"storage"} organizations={organizations} channels={storageChannels}/>
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(organization)/migration/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {notFound} from "next/navigation";
import {getOrganization} from "@/lib/auth/auth";
import {Metadata} from "next";
import {db} from "@/db";
import {MigrationTool} from "@/components/wrappers/dashboard/organization/migration/migration-tool";

export const metadata: Metadata = {
    title: "Projects",
};

export default async function RoutePage(props: PageParams<{}>) {

    const organization = await getOrganization({});

    if (!organization) {
        notFound();
    }

    const projects = await db.query.project.findMany({
        where: (project, {eq, and, not}) =>
            and(
                eq(project.organizationId, organization.id),
                not(eq(project.isArchived, true))
            ),
        with: {
            organization: true,
            databases: {
                where: (database, { isNull, not, inArray, and }) =>
                    and(
                        isNull(database.deletedAt),
                        not(inArray(database.dbms, ["valkey", "redis"]))
                    ),

                with: {
                    backups: {
                        where: (backup, { isNull, eq, and }) =>
                            and(
                                isNull(backup.deletedAt),
                                eq(backup.status, "success")
                            ),
                        orderBy: (backup, {desc}) => [desc(backup.createdAt)],
                        limit: 15,
                    }
                }
            },
        },
    });
    return (
        <Page>
            <PageHeader className="flex flex-col items-start justify-between mb-6">
                <PageTitle className="mb-2">
                    Database Migration
                </PageTitle>
                <p className="text-sm text-muted-foreground">
                    Import backups from a source project into your target database
                </p>
            </PageHeader>
            <PageContent>
                <MigrationTool projects={projects}/>
            </PageContent>
        </Page>
    );
}

================================================
FILE: app/(customer)/dashboard/(organization)/projects/[projectId]/database/[databaseId]/page.tsx
================================================
import {PageParams} from "@/types/next";
import {notFound, redirect} from "next/navigation";
import {Page} from "@/features/layout/page";
import {db} from "@/db";
import {eq, and, inArray} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {getOrganizationProjectDatabases} from "@/lib/services";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {BackupModalProvider} from "@/components/wrappers/dashboard/database/backup/backup-modal-context";
import {DatabaseContent} from "@/components/wrappers/dashboard/projects/database/database-content";
import {getHealthLast12hLogs} from "@/db/services/healthcheck";

export default async function RoutePage(props: PageParams<{
    projectId: string;
    databaseId: string
}>) {
    const {projectId, databaseId} = await props.params;

    const organization = await getOrganization({});
    const activeMember = await getActiveMember()

    if (!organization || !activeMember) {
        notFound();
    }

    const databasesProject = await getOrganizationProjectDatabases({
        organizationSlug: organization.slug,
        projectId: projectId
    })

    const dbItem = await db.query.database.findFirst({
        where: and(inArray(drizzleDb.schemas.backup.id, databasesProject.ids ?? []), eq(drizzleDb.schemas.database.id, databaseId), eq(drizzleDb.schemas.database.projectId, projectId)),
        with: {
            project: true,
            retentionPolicy: true,
            alertPolicies: true,
            storagePolicies: true
        }
    });

    if (!dbItem) {
        redirect("/dashboard/projects");
    }

    const backups = await db.query.backup.findMany({
        where: eq(drizzleDb.schemas.backup.databaseId, dbItem.id),
        with: {
            restorations: true,
            storages: {
                with: {
                    storageChannel: true
                }
            }
        },
        orderBy: (b, {desc}) => [desc(b.createdAt)],
    });

    const restorations = await db.query.restoration.findMany({
        where: eq(drizzleDb.schemas.restoration.databaseId, dbItem.id),
        orderBy: (r, {desc}) => [desc(r.createdAt)],
    });

    const isAlreadyBackup = backups.some((b) => b.status === "waiting" || b.status === "ongoing");
    const isAlreadyRestore = restorations.some((r) => r.status === "waiting");

    const totalBackups = await db.select({count: drizzleDb.schemas.backup.id})
        .from(drizzleDb.schemas.backup)
        .where(eq(drizzleDb.schemas.backup.databaseId, dbItem.id))
        .then(rows => rows.length);

    const availableBackups = backups.filter(b => !b.deletedAt).length;

    const successfulBackups = await db.select({count: drizzleDb.schemas.backup.id})
        .from(drizzleDb.schemas.backup)
        .where(and(
            eq(drizzleDb.schemas.backup.databaseId, dbItem.id),
            eq(drizzleDb.schemas.backup.status, "success")
        ))
        .then(rows => rows.length);


    const [settings] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
    if (!settings) {
        notFound();
    }

    const databaseHealthLogs = dbItem ? await getHealthLast12hLogs({ id: dbItem.id }) : []


    const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null;

    const isMember = activeMember?.role === "member";

    return (
        <Page>
            <BackupModalProvider>
                <DatabaseContent
                    activeMember={activeMember}
                    settings={settings}
                    database={dbItem}
                    databaseHealthLogs={databaseHealthLogs}
                    isAlreadyRestore={isAlreadyRestore}
                    restorations={restorations}
                    backups={backups}
                    totalBackups={totalBackups}
                    availableBackups={availableBackups}
                    successRate={successRate}
                    organizationId={organization.id}
                    activeOrganizationChannels={[]}
                    activeOrganizationStorageChannels={[]}
                />
            </BackupModalProvider>
        </Page>
    );
}

================================================
FILE: app/(customer)/dashboard/(organization)/projects/[projectId]/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageContent, PageTitle} from "@/features/layout/page";
import {
    ButtonDeleteProject
} from "@/components/wrappers/dashboard/projects/button-delete-project/button-delete-project";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {ProjectDatabaseCard} from "@/components/wrappers/dashboard/projects/project-card/project-database-card";
import {notFound, redirect} from "next/navigation";
import {db} from "@/db";
import {eq} from "drizzle-orm";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import * as drizzleDb from "@/db";
import {capitalizeFirstLetter} from "@/utils/text";
import {ProjectDialog} from "@/features/projects/components/project.dialog";
import {ProjectWith} from "@/db/schema/06_project";
import {isUuidv4} from "@/utils/verify-uuid";
import {getOrganizationAvailableDatabases} from "@/db/services/database";


export default async function RoutePage(props: PageParams<{
    projectId: string
}>) {
    const {
        projectId
    } = await props.params;

    if (!isUuidv4(projectId)) {
        notFound()
    }

    const organization = await getOrganization({});
    const activeMember = await getActiveMember()

    if (!organization) {
        notFound();
    }
    const org = await db.query.organization.findFirst({
        where: eq(drizzleDb.schemas.organization.slug, organization.slug),
    });

    if (!org) notFound();

    const proj = await db.query.project.findFirst({
        where: (proj, {
            and,
            eq,
            not
        }) => and(eq(proj.id, projectId), eq(proj.organizationId, org.id), not(eq(proj.isArchived, true))),
        with: {
            databases: true,
        },
    });

    if (!proj) {
        redirect("/dashboard/projects");
    }

    const availableDatabases = await getOrganizationAvailableDatabases(organization.id, proj.id)
    const isMember = activeMember?.role === "member";

    return (
        <Page>
            <div className="justify-between gap-2 sm:flex">
                <PageTitle className="flex flex-col md:flex-row items-center justify-between w-full ">
                    <div className="min-w-full md:min-w-fit ">
                        {capitalizeFirstLetter(proj.name)}
                    </div>
                    {!isMember && (
                        <div className="flex items-center gap-2 md:justify-between w-full ">
                            <div className="flex items-center gap-2">
                                <ProjectDialog
                                    databases={availableDatabases}
                                    organization={org}
                                    project={proj as ProjectWith}
                                    isEdit={true}
                                />
                            </div>
                            <div className="flex items-center gap-2">
                                <ButtonDeleteProject projectId={projectId} text={"Delete Project"}/>
                            </div>
                        </div>
                    )}
                </PageTitle>
            </div>
            <PageContent className="flex flex-col w-full h-full">
                {proj.databases.length > 0 ? (
                    <CardsWithPagination
                        data={proj.databases}
                        organizationSlug={organization.slug}
                        // @ts-ignore
                        cardItem={ProjectDatabaseCard}
                        cardsPerPage={20}
                        numberOfColumns={3}
                        pageSizeOptions={[10, 20, 50]}
                        extendedProps={proj}
                    />
                ) : (
                    <div className="flex flex-col items-center justify-center h-full text-muted-foreground py-20">
                        <p className="text-lg font-medium">No databases found</p>
                        <p className="text-sm mt-2">You haven’t added any databases to this project yet.</p>
                    </div>
                )}
            </PageContent>
        </Page>
    );
}

================================================
FILE: app/(customer)/dashboard/(organization)/projects/page.tsx
================================================
import {PageParams} from "@/types/next";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {ProjectCard} from "@/components/wrappers/dashboard/projects/project-card/project-card";
import {db} from "@/db";
import {notFound} from "next/navigation";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
import {Metadata} from "next";
import {ProjectDialog} from "@/features/projects/components/project.dialog";
import {DatabaseWith} from "@/db/schema/07_database";
import {getOrganizationAvailableDatabases} from "@/db/services/database";

export const metadata: Metadata = {
    title: "Projects",
};

export default async function RoutePage(props: PageParams<{}>) {

    const organization = await getOrganization({});
    const activeMember = await getActiveMember()

    if (!organization) {
        notFound();
    }

    const projects = await db.query.project.findMany({
        where: (project, {
            eq,
            and,
            not
        }) => and(eq(project.organizationId, organization.id), not(eq(project.isArchived, true))),
        with: {
            databases: true,
        },
    });
    const isMember = activeMember?.role === "member";

    const availableDatabases = await getOrganizationAvailableDatabases(organization.id)


    return (
        <Page>
            <PageHeader>
                <PageTitle>Projects</PageTitle>
                {(projects.length > 0 && !isMember) && (
                    <PageActions>
                        <ProjectDialog databases={availableDatabases} organization={organization}/>
                    </PageActions>
                )}
            </PageHeader>

            <PageContent>
                {projects.length > 0 ? (
                    <CardsWithPagination
                        organizationSlug={organization.slug}
                        data={projects}
                        cardItem={ProjectCard}
                        cardsPerPage={12}
                        numberOfColumns={3}
                        pageSizeOptions={[12, 24, 48]}
                    />
                ) : isMember ? (
                    <EmptyStatePlaceholder state={"empty"} text="No project available"/>
                ) : (
                    <ProjectDialog databases={availableDatabases} organization={organization} isEmpty={true}/>
                )}
            </PageContent>
        </Page>
    );
}

================================================
FILE: app/(customer)/dashboard/(organization)/settings/agents/[agentId]/page.tsx
================================================
import {PageParams} from "@/types/next";
import {
    Page,
    PageContent,
    PageDescription,
    PageTitle,
} from "@/features/layout/page";
import {db} from "@/db";
import * as drizzleDb from "@/db";
import {eq} from "drizzle-orm";
import {notFound} from "next/navigation";
import {ButtonDeleteAgent} from "@/components/wrappers/dashboard/agent/button-delete-agent/button-delete-agent";
import {capitalizeFirstLetter} from "@/utils/text";
import {generateEdgeKey} from "@/utils/edge_key";
import {getServerUrl} from "@/utils/get-server-url";
import {AgentContentPage} from "@/components/wrappers/dashboard/agent/agent-content";
import {AgentDialog} from "@/features/agents/components/agent.dialog";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {currentUser} from "@/lib/auth/current-user";
import {computeOrganizationPermissions} from "@/lib/acl/organization-acl";


export default async function RoutePage(
    props: PageParams<{ agentId: string }>,
) {
    const {agentId} = await props.params;

    const organization = await getOrganization({});
    const user = await currentUser();
    const activeMember = await getActiveMember();

    if (!organization || !activeMember || !user) {
        notFound();
    }



    const {canManageAgents} = computeOrganizationPermissions(activeMember);

    if (!canManageAgents){
        notFound();
    }

    const agent = await db.query.agent.findFirst({
        where: eq(drizzleDb.schemas.agent.id, agentId),
        with: {
            databases: true,
            organizations: true,
        },
    });


    if (!agent) {
        notFound();
    }

    const hasAccess =
        agent.organizationId === organization.id ||
        agent.organizations.some(org => org.organizationId === organization.id);

    if (!hasAccess) {
        notFound();
    }

    const isOwned = agent.organizationId
    const edgeKey = await generateEdgeKey(getServerUrl(), agent.id);

    return (
        <Page>
            <div className="justify-between gap-2 sm:flex">
                <PageTitle className="flex flex-col md:flex-row items-center justify-between w-full ">
                    <div className="min-w-full md:min-w-fit ">
                        {capitalizeFirstLetter(agent.name)}
                    </div>
                    {isOwned && (
                        <div className="flex items-center gap-2 md:justify-between w-full ">
                            <div className="flex items-center gap-2">
                                <AgentDialog
                                    agent={agent}
                                    typeTrigger={"edit"}
                                />
                            </div>
                            <div className="flex items-center gap-2">
                                <ButtonDeleteAgent organizationId={organization.id ?? null} agentId={agentId} text={"Delete Agent"}/>
                            </div>
                        </div>
                    )}
                </PageTitle>
            </div>

            {agent.description && (
                <PageDescription className="mt-5 sm:mt-0">
                    {agent.description}
                </PageDescription>
            )}
            <PageContent className="flex flex-col w-full h-full justify-between gap-6">
                <AgentContentPage agent={agent} edgeKey={edgeKey}/>
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(organization)/settings/agents/page.tsx
================================================
import { redirect } from "next/navigation";

export default async function RoutePage() {
    redirect("/dashboard/settings?tab=agents");
}


================================================
FILE: app/(customer)/dashboard/(organization)/settings/page.tsx
================================================
import {PageParams} from "@/types/next";
import {
    Page,
    PageContent,
    PageHeader,
    PageTitle,
} from "@/features/layout/page";
import {currentUser} from "@/lib/auth/current-user";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {notFound} from "next/navigation";
import {Metadata} from "next";
import {OrganizationTabs} from "@/components/wrappers/dashboard/organization/tabs/organization-tabs";
import {getOrganizationChannels} from "@/db/services/notification-channel";
import {computeOrganizationPermissions} from "@/lib/acl/organization-acl";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel";
import {DeleteOrganizationButton} from "@/components/wrappers/dashboard/organization/delete-organization-button";
import {EditOrganizationDialog} from "@/features/organization/components/edit-organization.dialog";
import {db} from "@/db";
import {isNull} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {eq} from "drizzle-orm";
import {getOrganizationAgents} from "@/db/services/agent";
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";

export const metadata: Metadata = {
    title: "Settings",
};

export default async function RoutePage(props: PageParams<{ slug: string }>) {
    const organization = await getOrganization({});
    const user = await currentUser();
    const activeMember = await getActiveMember();

    if (!organization || !activeMember || !user) {
        notFound();
    }

    const notificationChannels = await getOrganizationChannels(organization.id);
    const storageChannels = await getOrganizationStorageChannels(organization.id);
    const agents = await getOrganizationAgents(organization.id);
    const permissions = computeOrganizationPermissions(activeMember);

    const users = await db.query.user.findMany({
        where: (fields) => isNull(fields.deletedAt),
    });

    const organizationWithMembers = await db.query.organization.findFirst({
        where: eq(drizzleDb.schemas.organization.id, organization.id),
        with: {
            projects: true,
            members: {
                with: {
                    user: true,
                },
            },
        },
    });

    if (!organizationWithMembers) notFound();

    return (
        <Page>
            <PageHeader>
                <PageTitle className="flex flex-col md:flex-row items-center justify-between w-full ">
                    <div className="min-w-full md:min-w-fit ">Organization settings</div>
                    <div className="flex items-center gap-2 md:justify-between w-full ">
                        <div className="flex items-center gap-2">
                            {permissions.canManageSettings &&
                                organization.slug !== "default" && (
                                    <EditOrganizationDialog
                                        organization={organizationWithMembers}
                                        users={users}
                                        currentUser={user}
                                    />
                                )}
                        </div>
                        <div className="flex items-center gap-2">
                            {permissions.canManageDangerZone &&
                                organization.slug !== "default" && (

                                    <Tooltip>
                                        <TooltipTrigger asChild>
                                            <div>
                                                <DeleteOrganizationButton
                                                    disabled={organizationWithMembers.projects.length > 0}
                                                    organizationSlug={organization.slug}
                                                />
                                            </div>

                                        </TooltipTrigger>
                                        {organizationWithMembers.projects.length > 0 && (
                                            <TooltipContent>
                                                <p>Your organization has some projects associated with it. Please delete them before deleting the organization.</p>
                                            </TooltipContent>
                                        )}
                                    </Tooltip>

                                )}
                        </div>
                    </div>
                </PageTitle>
            </PageHeader>
            <PageContent>
                <OrganizationTabs
                    activeMember={activeMember}
                    organization={organization}
                    notificationChannels={notificationChannels}
                    storageChannels={storageChannels}
                    agents={agents}
                />
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/(organization)/statistics/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
import {EvolutionLineChart} from "@/components/wrappers/dashboard/statistics/charts/evolution-line-chart";
import {PercentageLineChart} from "@/components/wrappers/dashboard/statistics/charts/percentage-line-chart";
import {notFound} from "next/navigation";
import {db} from "@/db";
import {and, asc, count, eq, inArray} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {getOrganization} from "@/lib/auth/auth";
import {Building2, DatabaseBackup, Folder, RefreshCcw} from "lucide-react";
import {Metadata} from "next";

export const metadata: Metadata = {
    title: "Statistics",
};

export default async function RoutePage(props: PageParams<{}>) {
    const organization = await getOrganization({});

    if (!organization) {
        notFound();
    }

    const org = await db.query.organization.findFirst({
        where: eq(drizzleDb.schemas.organization.slug, organization.slug),
    });

    if (!org) notFound();

    const projects = await db.query.project.findMany({
        where: eq(drizzleDb.schemas.project.organizationId, org.id),
    });

    const projectIds = projects.map(project => project.id);

    const databasesOfAllProjects = await db.query.database.findMany({
        where: inArray(drizzleDb.schemas.database.projectId, projectIds),
    })
    const databaseIds = databasesOfAllProjects.map((database) => database.id);


    const backupsEvolution = await db.query.backup.findMany({
        columns: {
            id: true,
            createdAt: true,
        },
        orderBy: [asc(drizzleDb.schemas.backup.id)],
        where: inArray(drizzleDb.schemas.backup.databaseId, databaseIds),
    });


    const backupsRate = await db
        .select({
            createdAt: drizzleDb.schemas.backup.createdAt,
            status: drizzleDb.schemas.backup.status,
            _count: count(),
        })
        .from(drizzleDb.schemas.backup)
        .where(and(inArray(drizzleDb.schemas.backup.status, ["success", "failed"]), inArray(drizzleDb.schemas.backup.databaseId, databaseIds)))
        .groupBy(drizzleDb.schemas.backup.createdAt, drizzleDb.schemas.backup.status)
        .orderBy(drizzleDb.schemas.backup.createdAt);


    const restorationsCountResult = await db
        .select({
            count: count(),
        })
        .from(drizzleDb.schemas.restoration)
        .where(inArray(drizzleDb.schemas.restoration.databaseId, databaseIds));


    const restorationsCount = restorationsCountResult[0]?.count ?? 0;
    const projectsCount = projects.length;
    const backupsEvolutionCount = backupsEvolution.length;

    const sortedBackupsEvolution = backupsEvolution.sort(
        (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
    );


    return (
        <Page>
            <PageHeader>
                <PageTitle>Statistics Overview</PageTitle>
            </PageHeader>

            <PageContent className="flex flex-col gap-y-4">
                <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between pb-2">
                            <CardTitle className="text-sm font-medium">Projects</CardTitle>
                            <Building2 className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{projectsCount}</div>
                            <p className="text-xs text-muted-foreground">Active projects in this organization</p>
                        </CardContent>
                    </Card>

                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between pb-2">
                            <CardTitle className="text-sm font-medium">Backups</CardTitle>
                            <DatabaseBackup className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{backupsEvolutionCount}</div>
                            <p className="text-xs text-muted-foreground">Total backups executed across all databases</p>
                        </CardContent>
                    </Card>

                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between pb-2">
                            <CardTitle className="text-sm font-medium">Restorations</CardTitle>
                            <RefreshCcw className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{restorationsCount}</div>
                            <p className="text-xs text-muted-foreground">Total restoration operations performed</p>
                        </CardContent>
                    </Card>
                </div>

                <div className="flex flex-col md:flex-row gap-4">
                    <EvolutionLineChart
                        data={sortedBackupsEvolution}
                    />

                    <PercentageLineChart
                        data={backupsRate}/>
                </div>
            </PageContent>
        </Page>
    );
}


================================================
FILE: app/(customer)/dashboard/home/page.tsx
================================================
import {PageParams} from "@/types/next";
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
import {Building2, Database, DatabaseBackup, Folder, RefreshCcw, Server, Workflow} from "lucide-react";
import {currentUser} from "@/lib/auth/current-user";
import {notFound} from "next/navigation";
import {db} from "@/db";
import {and, asc, eq, inArray} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {listOrganizations} from "@/lib/auth/auth";
import {Metadata} from "next";

export const metadata: Metadata = {
    title: "Home",
};

export default async function RoutePage(props: PageParams<{}>) {

    const user = await currentUser();
    const organizations = await listOrganizations()

    if (!user || !organizations) notFound();

    const organizationIds = organizations.map(project => project.id);

    const agents = await db.query.agent.findMany({});

    const projects = await db.query.project.findMany({
        where: and(inArray(drizzleDb.schemas.project.organizationId, organizationIds), eq(drizzleDb.schemas.project.isArchived, false)),
    });

    const projectIds = projects.map(project => project.id);


    const databasesOfAllProjects = await db.query.database.findMany({
        where: inArray(drizzleDb.schemas.database.projectId, projectIds),
    })
    const databaseIds = databasesOfAllProjects.map((database) => database.id);


    const backupsEvolution = await db.query.backup.findMany({
        columns: {
            id: true,
            createdAt: true,
            deletedAt: true,
        },
        orderBy: [asc(drizzleDb.schemas.backup.id)],
        where: inArray(drizzleDb.schemas.backup.databaseId, databaseIds),
    });

    const availableBackups = backupsEvolution.filter(backup => backup.deletedAt == null);


    return (
        <Page>
            <PageHeader>
                <PageTitle>Dashboard</PageTitle>
            </PageHeader>
            <PageContent className="flex flex-col gap-y-4">
                <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-sm font-medium">Organizations</CardTitle>
                            <Building2 className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{organizations.length}</div>
                            <p className="text-xs text-muted-foreground">Number of organizations</p>
                        </CardContent>
                    </Card>

                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-sm font-medium">Projects</CardTitle>
                            <Folder className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{projects.length}</div>
                            <p className="text-xs text-muted-foreground">Number of projects</p>
                        </CardContent>
                    </Card>

                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-sm font-medium">Databases</CardTitle>
                            <Database className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{databasesOfAllProjects.length}</div>
                            <p className="text-xs text-muted-foreground">Databases across all projects</p>
                        </CardContent>
                    </Card>

                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-sm font-medium">Agents</CardTitle>
                            <Workflow className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{agents.length}</div>
                            <p className="text-xs text-muted-foreground">Registered agents</p>
                        </CardContent>
                    </Card>

                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-sm font-medium">Total Backups</CardTitle>
                            <DatabaseBackup className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{backupsEvolution.length}</div>
                            <p className="text-xs text-muted-foreground">All backups recorded</p>
                        </CardContent>
                    </Card>

                    <Card className="w-full">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-sm font-medium">Available Backups</CardTitle>
                            <RefreshCcw className="h-4 w-4 text-muted-foreground"/>
                        </CardHeader>
                        <CardContent>
                            <div className="text-2xl font-bold">{availableBackups.length}</div>
                            <p className="text-xs text-muted-foreground">Currently active backups</p>
                        </CardContent>
                    </Card>
                </div>
                {/*Do not delete*/}
                {/*<div className="flex flex-1 flex-col gap-4">*/}
                {/*    <div className="grid auto-rows-min gap-4 md:grid-cols-3">*/}
                {/*        <div className="aspect-video rounded-xl bg-muted/50"/>*/}
                {/*        <div className="aspect-video rounded-xl bg-muted/50"/>*/}
                {/*        <div className="aspect-video rounded-xl bg-muted/50"/>*/}
                {/*    </div>*/}
                {/*</div>*/}
            </PageContent>
        </Page>

    );
}


================================================
FILE: app/(customer)/dashboard/layout.tsx
================================================
import { ReactNode } from "react";
import { redirect } from "next/navigation";

import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/wrappers/dashboard/common/sidebar/app-sidebar";
import { Header } from "@/features/layout/Header";
import { currentUser } from "@/lib/auth/current-user";
import { ThemeMetaUpdater } from "@/features/browser/theme-meta-updater";

export default async function Layout({ children }: { children: ReactNode }) {
  const user = await currentUser();
  if (!user) redirect("/login");

  return (
    <SidebarProvider>
      <div className="flex flex-col lg:flex-row w-full">
        <ThemeMetaUpdater />
        <AppSidebar />
        <SidebarInset>
          <Header />
          <main className="h-full">{children}</main>
        </SidebarInset>
      </div>
    </SidebarProvider>
  );
}


================================================
FILE: app/(customer)/dashboard/loading.tsx
================================================
import { LoadingSpinner } from "@/components/wrappers/common/loading/loading-spinner";

export default function Loading() {
    return (
        <div className="w-full h-full flex flex-col items-center justify-center">
            <LoadingSpinner size={50} />
        </div>
    );
}


================================================
FILE: app/(landing)/home/page.tsx
================================================
export default function Home() {
    return (
        <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
            <h1 className="font-bold text-4xl leading-tight">Home landing page</h1>
        </div>
    );
}


================================================
FILE: app/(landing)/page.tsx
================================================
import { redirect } from "next/navigation";
import { getCurrentOrganizationSlug } from "@/features/dashboard/organization-cookie";
import { currentUser } from "@/lib/auth/current-user";

export default async function Index() {
    const user = await currentUser();
    if (user) {
        const currentOrganizationSlug = await getCurrentOrganizationSlug();
        redirect(`/dashboard/home`);
    }
    redirect("/login");
    //Do not delete
    // return (
    //     <Home/>
    // );
}


================================================
FILE: app/api/agent/[agentId]/backup/helpers.ts
================================================
import {NextResponse} from "next/server";
import {and, eq} from "drizzle-orm";
import {db} from "@/db";
import * as drizzleDb from "@/db";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/agent/backup/helpers"});

export function withAgentCheck(handler: Function) {
    return async (request: Request, context: { params: Promise<{ agentId: string }> }) => {
        try {
            const agentId = (await context.params).agentId;

            const agent = await db.query.agent.findFirst({
                where: and(eq(drizzleDb.schemas.agent.id, agentId), eq(drizzleDb.schemas.agent.isArchived, false)),
            });

            if (!agent) {
                return NextResponse.json(
                    {error: "Agent not found"},
                    {status: 404}
                );
            }

            return handler(request, {...context, agent});
        } catch (err) {
            log.error({error: err, name: "withAgentCheck"}, "Error in agent middleware");
            return NextResponse.json(
                {error: "Internal server error"},
                {status: 500}
            );
        }
    };
}

export async function getDatabaseOrThrow(generatedId: string) {
    const database = await db.query.database.findFirst({
        where: eq(drizzleDb.schemas.database.agentDatabaseId, generatedId),
        with: {
            project: true,
            alertPolicies: true,
            storagePolicies: true
        }
    });

    if (!database) {
        throw NextResponse.json(
            {error: "Database associated with generatedId not found"},
            {status: 404}
        );
    }

    return database;
}

================================================
FILE: app/api/agent/[agentId]/backup/route.ts
================================================
import {NextResponse} from "next/server";
import {and, eq} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {db as dbClient, db} from "@/db";
import {getDatabaseOrThrow, withAgentCheck} from "./helpers";
import {Backup} from "@/db/schema/07_database";
import {withUpdatedAt} from "@/db/utils";
import {eventEmitter} from "@/features/shared/event";
import {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
import {EventKind} from "@/features/notifications/types";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/agent/backup/route"});

export type BodyPost = {
    method: "manual" | "automatic"
    generatedId: string
}

export type BodyPatch = {
    backupId: string
    status: "success" | "failed"
    size: number
    generatedId: string
}

export const POST = withAgentCheck(async (request: Request, {params, agent}: {
    params: Promise<{ agentId: string }>,
    agent: any
}) => {
    try {
        const body: BodyPost = await request.json();
        const method = body.method
        const database = await getDatabaseOrThrow(body.generatedId);

        let backup: Backup | null | undefined = null;

        if (method === "automatic") {

            const ongoingBackup = await db.query.backup.findFirst({
                where: and(
                    eq(drizzleDb.schemas.backup.status, 'ongoing'),
                    eq(drizzleDb.schemas.backup.databaseId, database.id),
                ),
            });

            if (!ongoingBackup) {
                [backup] = await db
                    .insert(drizzleDb.schemas.backup)
                    .values({
                        status: 'ongoing',
                        databaseId: database.id,
                    })
                    .returning();
                if (!backup) {
                    return NextResponse.json(
                        {error: "Unable to create an automatic backup"},
                        {status: 500}
                    );
                }
            } else {
                return NextResponse.json(
                    {error: "A backup is already ongoing"},
                    {status: 500}
                );
            }
        } else {
            backup = await db.query.backup.findFirst({
                where: and(
                    eq(drizzleDb.schemas.backup.status, 'ongoing'),
                    eq(drizzleDb.schemas.backup.databaseId, database.id),
                ),
            });

            if (!backup) {
                return NextResponse.json(
                    {error: "Unable to find the corresponding backup"},
                    {status: 404}
                );
            }
        }

        eventEmitter.emit('modification', {update: true});

        return NextResponse.json(
            {
                message: "Init backup success",
                backup: backup,
            },
            {status: 200}
        );
    } catch (error) {
        log.error({error: error}, "Error in POST for INIT backup");
        return NextResponse.json(
            {error: "Internal server error"},
            {status: 500}
        );
    }
});

export const PATCH = withAgentCheck(async (request: Request, {params, agent}: {
    params: Promise<{ agentId: string }>,
    agent: any
}) => {
    try {
        const body: BodyPatch = await request.json();
        log.info({data: body}, "Body from PATH in backup route");

        const status = body.status
        const backupId = body.backupId
        const backupSize = body.size

        const database = await getDatabaseOrThrow(body.generatedId);

        const backup = await db.query.backup.findFirst({
            where: eq(drizzleDb.schemas.backup.id, backupId),
        });

        if (!backup) {
            return NextResponse.json(
                {error: "No backup found"},
                {status: 500}
            );
        }

        const [backupUpdated] = await dbClient
            .update(drizzleDb.schemas.backup)
            .set(withUpdatedAt({
                status: status,
                fileSize: backupSize
            }))
            .where(eq(drizzleDb.schemas.backup.id, backup.id))
            .returning();


        eventEmitter.emit('modification', {update: true});
        await sendNotificationsBackupRestore(database, status == "failed" ? "error_backup" : "success_backup" as EventKind);

        return NextResponse.json(
            {
                message: "Backup successfully updated",
                backup: backupUpdated,
            },
            {status: 200}
        );
    } catch (error) {
        log.error({error: error}, "Error in PATCH backup")
        return NextResponse.json(
            {error: "Internal server error"},
            {status: 500}
        );
    }
});



================================================
FILE: app/api/agent/[agentId]/backup/upload/init/route.ts
================================================
import {NextResponse} from "next/server";
import {and, eq} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {db} from "@/db";
import {getDatabaseOrThrow, withAgentCheck} from "../../helpers";
import {isUuidv4} from "@/utils/verify-uuid";
import {eventEmitter} from "@/features/shared/event";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/agent/backup/upload/init"});

export type Body = {
    generatedId: string
    storageChannelId: string
    backupId: string
}
export const POST = withAgentCheck(async (request: Request, {params, agent}: {
    params: Promise<{ agentId: string }>,
    agent: any
}) => {
    try {
        const body: Body = await request.json();

        log.info({data: body}, "Body for backup upload init");

        const generatedId = body.generatedId;
        const storageChannelId = body.storageChannelId;
        const backupId = body.backupId;

        if (!generatedId || !isUuidv4(generatedId)) {
            return NextResponse.json(
                {error: "generatedId is not a valid UUID"},
                {status: 400}
            );
        }

        const database = await getDatabaseOrThrow(generatedId);

        const backup = await db.query.backup.findFirst({
            where: and(
                eq(drizzleDb.schemas.backup.id, backupId),
                eq(drizzleDb.schemas.backup.databaseId, database.id),
            ),
        });

        if (!backup) {
            return NextResponse.json(
                {error: "Unable to find the corresponding backup"},
                {status: 404}
            );
        }

        const [backupStorage] = await db
            .insert(drizzleDb.schemas.backupStorage)
            .values({
                backupId: backup.id,
                storageChannelId: storageChannelId,
                status: "pending",
            })
            .returning();

        eventEmitter.emit('modification', {update: true});

        return NextResponse.json(
            {
                message: "Backup storage successfully created",
                backupStorage: backupStorage
            },
            {status: 200}
        );
    } catch (error) {
        log.error({error: error}, "Error in POST for INIT backup");
        return NextResponse.json(
            {error: "Internal server error"},
            {status: 500}
        );
    }
});



================================================
FILE: app/api/agent/[agentId]/backup/upload/status/route.ts
================================================
import {NextResponse} from "next/server";
import {and, eq} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {db as dbClient, db} from "@/db";
import {withUpdatedAt} from "@/db/utils";
import {getDatabaseOrThrow, withAgentCheck} from "../../helpers";
import {eventEmitter} from "@/features/shared/event";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/agent/backup/upload/status"});

export type Body = {
    generatedId: string
    status: "success" | "failed"
    backupStorageId: string
    path: string
    size: number
    backupId: string
}
export const PATCH = withAgentCheck(async (request: Request, {params, agent}: {
    params: Promise<{ agentId: string }>,
    agent: any
}) => {
    try {
        const body: Body = await request.json();
        const generatedId = body.generatedId;
        const status = body.status;
        const filePath = body.path;
        const fileSize = body.size;
        const backupStorageId = body.backupStorageId;
        const backupId = body.backupId;

        log.info({data: body}, "Body for backup upload status");

        const database = await getDatabaseOrThrow(generatedId);

        const backup = await db.query.backup.findFirst({
            where: and(
                eq(drizzleDb.schemas.backup.id, backupId),
                eq(drizzleDb.schemas.backup.databaseId, database.id),
            ),
            with: {
                storages: true
            }
        });

        if (!backup) {
            return NextResponse.json(
                {error: "Unable to find the corresponding backup"},
                {status: 404}
            );
        }

        const [backupStorage] = await dbClient
            .update(drizzleDb.schemas.backupStorage)
            .set(withUpdatedAt({
                status: status,
                path: filePath,
                size: fileSize
            }))
            .where(eq(drizzleDb.schemas.backupStorage.id, backupStorageId))
            .returning();


        if (backup.storages.length > 0) {
            const hasSuccessfulStorage = backup.storages.some(
                (storage) => storage.status === "success"
            );

            if (hasSuccessfulStorage && backup.status !== "success") {
                await db
                    .update(drizzleDb.schemas.backup)
                    .set(withUpdatedAt({
                        status: "success",
                        fileSize: fileSize,
                    }))
                    .where(eq(drizzleDb.schemas.backup.id, backup.id));
            }
        }

        eventEmitter.emit('modification', {update: true});

        return NextResponse.json({
                message: "Backup status successfully updated",
                backupStorage: backupStorage
            },
            {status: 200}
        );
    } catch (error) {
        log.error({error: error},"Error in POST for INIT backup");
        return NextResponse.json(
            {error: "Internal server error"},
            {status: 500}
        );
    }
});



================================================
FILE: app/api/agent/[agentId]/restore/route.ts
================================================
import {NextResponse} from "next/server";
import {isUuidv4} from "@/utils/verify-uuid";
import * as drizzleDb from "@/db";
import {db} from "@/db";
import {and, eq} from "drizzle-orm";
import {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
import {logger} from "@/lib/logger";
import {withUpdatedAt} from "@/db/utils";

const log = logger.child({module: "api/agent/restore"});

export type BodyResultRestore = {
    generatedId: string
    status: string
}
type RestorationStatus = 'waiting' | 'ongoing' | 'failed' | 'success';


export async function POST(
    request: Request,
    {params}: { params: Promise<{ agentId: string }> }
) {

    try {

        const agentId = (await params).agentId
        const body: BodyResultRestore = await request.json();


        if (!isUuidv4(body.generatedId)) {
            return NextResponse.json(
                {error: "generatedId is not a valid uuid"},
                {status: 500}
            );
        }

        const agent = await db.query.agent.findFirst({
            where: and(eq(drizzleDb.schemas.agent.id, agentId), eq(drizzleDb.schemas.agent.isArchived, false)),
        })
        if (!agent) {
            return NextResponse.json({error: "Agent not found"}, {status: 404})
        }

        const database = await db.query.database.findFirst({
            where: eq(drizzleDb.schemas.database.agentDatabaseId, body.generatedId),
            with: {
                alertPolicies: true
            }
        })

        if (!database) {
            return NextResponse.json({error: "Database associated with generatedId provided not found"}, {status: 404})
        }

        const restoration = await db.query.restoration.findFirst({
            where: and(eq(drizzleDb.schemas.restoration.status, "ongoing"), eq(drizzleDb.schemas.restoration.databaseId, database.id),)
        })

        if (!restoration) {
            return NextResponse.json({error: "Unable to fin the corresponding restoration"}, {status: 404})
        }


        await db
            .update(drizzleDb.schemas.restoration)
            .set(withUpdatedAt({status: body.status as RestorationStatus}))
            .where(eq(drizzleDb.schemas.restoration.id, restoration.id));

        await sendNotificationsBackupRestore(database, body.status == "failed" ? "error_restore" : "success_restore");

        const response = {
            status: true,
            message: "Restoration successfully updated"
        }

        return Response.json(response, {status: 200})
    } catch (error) {
        log.error({error: error}, "Error in POST handler")
        return NextResponse.json(
            {error: 'Internal server error'},
            {status: 500}
        );
    }
}

================================================
FILE: app/api/agent/[agentId]/status/helpers.ts
================================================
import {NextResponse} from "next/server";
import {Body} from "./route";
import {isUuidv4} from "@/utils/verify-uuid";
import {Agent} from "@/db/schema/08_agent";
import {DatabaseWith} from "@/db/schema/07_database";
import * as drizzleDb from "@/db";
import {db, db as dbClient} from "@/db";
import {and, eq, inArray} from "drizzle-orm";
import {dbmsEnumSchema, EDbmsSchema} from "@/db/schema/types";
import {withUpdatedAt} from "@/db/utils";
import type {StorageInput} from "@/features/storages/types";
import {dispatchStorage} from "@/features/storages/dispatch";
import {Setting} from "@/db/schema/01_setting";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/agent/status/helpers"});

export async function handleDatabases(body: Body, agent: Agent, lastContact: Date, settings: Setting) {
    const databasesResponse = [];

    const formatDatabase = (database: DatabaseWith, backupAction: boolean, restoreAction: boolean, UrlBackup: string | null, storages: PingDatabaseStorageChannels[], urlMeta: string | null) => ({
        generatedId: database.agentDatabaseId,
        dbms: database.dbms,
        storages: storages,
        encrypt: settings.encryption,
        data: {
            backup: {
                action: backupAction,
                cron: database.backupPolicy,
            },
            restore: {
                action: restoreAction,
                file: UrlBackup,
                metaFile: urlMeta
            },
        },
    });

    for (const db of body.databases) {

        const existingDatabase = await dbClient.query.database.findFirst({
            where: eq(drizzleDb.schemas.database.agentDatabaseId, db.generatedId),
            with: {
                project: true
            }
        });

        let backupAction: boolean = false
        let restoreAction: boolean = false
        let urlBackup: string | null = null;
        let urlMeta: string | null = null

        if (!existingDatabase) {
            if (!isUuidv4(db.generatedId)) {
                return NextResponse.json(
                    {error: "generatedId is not a valid uuid"},
                    {status: 500}
                );
            }

            if (!dbmsEnumSchema.safeParse(db.dbms).success) {
                log.error({name: "handleDatabases"},`Database type not available: ${db.dbms}`);
                continue;
            }

            const [databaseCreated] = await dbClient
                .insert(drizzleDb.schemas.database)
                .values({
                    agentId: agent.id,
                    name: db.name,
                    dbms: db.dbms as EDbmsSchema,
                    agentDatabaseId: db.generatedId,
                    lastContact: db.pingStatus ? lastContact : null,
                    healthErrorCount: null
                })
                .returning();


            if (databaseCreated) {


                await dbClient
                    .insert(drizzleDb.schemas.healthcheckLog)
                    .values({
                        kind: "database",
                        status: db.pingStatus ? "success" : "failed",
                        objectId: databaseCreated.id,
                        date: lastContact
                    })

                const storages = await getDatabaseStorageChannels(databaseCreated.id)

                databasesResponse.push(formatDatabase(databaseCreated, backupAction, restoreAction, urlBackup, storages, null));
            }
        } else {

            const [databaseUpdated] = await dbClient
                .update(drizzleDb.schemas.database)
                .set(withUpdatedAt({
                    name: db.name,
                    agentId: agent.id,
                    dbms: db.dbms as EDbmsSchema,
                    lastContact: db.pingStatus ? lastContact : existingDatabase.lastContact,
                    healthErrorCount: db.pingStatus ? null : existingDatabase.healthErrorCount,
                }))
                .where(eq(drizzleDb.schemas.database.id, existingDatabase.id))
                .returning();


            await dbClient
                .insert(drizzleDb.schemas.healthcheckLog)
                .values({
                    kind: "database",
                    status: db.pingStatus ? "success" : "failed",
                    objectId: databaseUpdated.id,
                    date: lastContact
                })


            const activeBackup = await dbClient.query.backup.findFirst({
                where: and(
                    eq(drizzleDb.schemas.backup.databaseId, databaseUpdated.id),
                    inArray(drizzleDb.schemas.backup.status, ["waiting", "ongoing"])
                )
            })

            const restoration = await dbClient.query.restoration.findFirst({
                where: and(eq(drizzleDb.schemas.restoration.databaseId, databaseUpdated.id), eq(drizzleDb.schemas.restoration.status, "waiting")),
                with: {
                    backupStorage: true
                }
            })

            if (activeBackup && activeBackup.status == "waiting") {
                backupAction = true

                await dbClient
                    .update(drizzleDb.schemas.backup)
                    .set(withUpdatedAt({status: "ongoing"}))
                    .where(eq(drizzleDb.schemas.backup.id, activeBackup.id));
            }

            if (restoration) {
                restoreAction = true

                if (!restoration.backupStorage || restoration.backupStorage.status != "success" || !restoration.backupStorage.path) {
                    restoreAction = false
                    continue;
                }

                const input: StorageInput = {
                    action: "get",
                    data: {
                        path: restoration.backupStorage.path,
                        signedUrl: true,
                    },
                    metadata: {
                        storageId: restoration.backupStorage.storageChannelId,
                        fileKind: "backups"
                    }
                };

                const inputMeta: StorageInput = {
                    action: "get",
                    data: {
                        path: `${restoration.backupStorage.path}.meta`,
                        signedUrl: true,
                    },
                    metadata: {
                        storageId: restoration.backupStorage.storageChannelId,
                        fileKind: "backups"
                    }
                };


                try {
                    const result = await dispatchStorage(input, undefined, restoration.backupStorage.storageChannelId);
                    const resultMeta = await dispatchStorage(inputMeta, undefined, restoration.backupStorage.storageChannelId);

                    if (result.success) {
                        urlBackup = result.url ?? null;
                        urlMeta = resultMeta.url ?? null
                    } else {
                        await dbClient
                            .update(drizzleDb.schemas.restoration)
                            .set(withUpdatedAt({status: "failed"}))
                            .where(eq(drizzleDb.schemas.restoration.id, restoration.id));

                        const errorMessage = "Failed to get backup URL";
                        log.error({error: errorMessage, name: "handleDatabases"}, "Restoration failed");
                        continue;
                    }
                } catch (err) {
                    log.error({error: err, name: "handleDatabases"}, "Restoration crashed unexpectedly");
                    await dbClient
                        .update(drizzleDb.schemas.restoration)
                        .set(withUpdatedAt({status: "failed"}))
                        .where(eq(drizzleDb.schemas.restoration.id, restoration.id));
                    continue;
                }

                await dbClient
                    .update(drizzleDb.schemas.restoration)
                    .set(withUpdatedAt({status: "ongoing"}))
                    .where(eq(drizzleDb.schemas.restoration.id, restoration.id));
            }
            const storages = await getDatabaseStorageChannels(databaseUpdated.id)
            databasesResponse.push(formatDatabase(databaseUpdated, backupAction, restoreAction, urlBackup, storages, urlMeta));
        }
    }
    return databasesResponse;
}


type PingDatabaseStorageChannels = {
    id: string;
    config: any
    provider: string
}

async function getDatabaseStorageChannels(databaseId: string): Promise<PingDatabaseStorageChannels[]> {

    const database = await db.query.database.findFirst({
        where: eq(drizzleDb.schemas.database.id, databaseId),
        with: {
            project: true,
            retentionPolicy: true,
            alertPolicies: true,
            storagePolicies: true
        }
    });

    if (!database) {
        return []
    }

    const settings = await db.query.setting.findFirst({
        where: eq(drizzleDb.schemas.setting.name, "system"),
        with: {storageChannel: true},
    });

    const defaultStorageChannel: PingDatabaseStorageChannels[] = settings?.storageChannel
        ? [{
            id: settings.storageChannel.id,
            provider: settings.storageChannel.provider,
            config: settings.storageChannel.config,
        }]
        : [];


    const enabledDatabaseStorageChannels = await Promise.all(
        (database.storagePolicies ?? [])
            .filter(p => p.enabled)
            .map(async policy => {
                const storageChannel = await db.query.storageChannel.findFirst({
                    where: eq(drizzleDb.schemas.storageChannel.id, policy.storageChannelId),
                });

                if (!storageChannel) return null;

                return {
                    id: storageChannel.id,
                    config: storageChannel.config,
                    provider: storageChannel.provider,
                } as PingDatabaseStorageChannels;
            })
    );

    const filteredChannels: PingDatabaseStorageChannels[] = enabledDatabaseStorageChannels.filter(
        (c): c is PingDatabaseStorageChannels => c !== null
    );

    return filteredChannels.length > 0 ? filteredChannels : defaultStorageChannel;
}



================================================
FILE: app/api/agent/[agentId]/status/route.ts
================================================
import {NextResponse} from "next/server";
import {handleDatabases} from "./helpers";
import * as drizzleDb from "@/db";
import {db} from "@/db";
import {EDbmsSchema} from "@/db/schema/types";
import {and, eq} from "drizzle-orm";
import {isUuidv4} from "@/utils/verify-uuid";
import {withUpdatedAt} from "@/db/utils";
import {logger} from "@/lib/logger";


const log = logger.child({module: "api/agent/status/route"});


export type databaseAgent = {
    name: string,
    dbms: EDbmsSchema,
    generatedId: string
    pingStatus: boolean
}

export type Body = {
    version: string,
    databases: databaseAgent[]
}


export async function POST(
    request: Request,
    {params}: { params: Promise<{ agentId: string }> }
) {
    try {
        const agentId = (await params).agentId
        log.info(`Agent ID: ${agentId}`)
        const body: Body = await request.json();
        const lastContact = new Date();
        let message: string

        if (!isUuidv4(agentId)) {
            message = "agentId is not a valid uuid"
            log.error({error: message}, "An error occurred")
            return NextResponse.json(
                {error: "agentId is not a valid uuid"},
                {status: 500}
            );
        }

        const agent = await db.query.agent.findFirst({
            where: and(eq(drizzleDb.schemas.agent.id, agentId), eq(drizzleDb.schemas.agent.isArchived, false)),
        })

        if (!agent) {
            message = "Agent not found"
            return NextResponse.json({error: message}, {status: 404})
        }

        const [settings] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
        if (!settings) {
            return NextResponse.json({error: "An error occured"}, {status: 404})
        }

        const databasesResponse = await handleDatabases(body, agent, lastContact, settings)

        await db
            .update(drizzleDb.schemas.agent)
            .set(withUpdatedAt({
                version: body.version,
                lastContact: lastContact,
                healthErrorCount: null
            }))
            .where(eq(drizzleDb.schemas.agent.id, agentId));

        await db
            .insert(drizzleDb.schemas.healthcheckLog)
            .values({
                kind: "agent",
                status: "success",
                objectId: agentId,
                date: lastContact
            })

        const response = {
            agent: {
                id: agentId,
                lastContact: lastContact
            },
            databases: databasesResponse
        }

        return Response.json(response)
    } catch (error) {
        log.error({error: error}, "Error in POST handler")
        return NextResponse.json(
            {error: 'Internal server error'},
            {status: 500}
        );
    }
}



================================================
FILE: app/api/auth/[...all]/route.ts
================================================
import { auth } from "@/lib/auth/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { GET, POST } = toNextJsHandler(auth.handler);


================================================
FILE: app/api/config/route.ts
================================================
import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({
    PROJECT_URL: process.env.PROJECT_URL,
    PROJECT_NAME: process.env.PROJECT_NAME,
    PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION,
  });
}


================================================
FILE: app/api/events/route.ts
================================================
import {auth} from "@/lib/auth/auth";
import {headers} from "next/headers";
import {NextResponse} from "next/server";
import {eventEmitter} from "@/features/shared/event";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/events"});

export async function GET(request: Request) {

    const session = await auth.api.getSession({
        headers: await headers(),
    });

    if (!session) {
        return NextResponse.json({error: "Unauthorized"}, {status: 403});
    }

    return new Response(
        new ReadableStream({
            start(controller) {
                log.info("Stream started");
                const handleModification = (data: any) => {
                    log.info({data: data},"Modification event triggered");
                    controller.enqueue(`event: modification\n`);
                    controller.enqueue(`data: ${JSON.stringify(data)}\n\n`);
                };

                eventEmitter.on('modification', handleModification);

                request.signal.addEventListener('abort', () => {
                    log.info("Client disconnected");
                    controller.close();
                    eventEmitter.off('modification', handleModification);
                });
            },
        }),
        {
            status: 200,
            headers: {
                'Content-Type': 'text/event-stream',
                'Cache-Control': 'no-cache',
                Connection: 'keep-alive',
            },
        }
    );
}

================================================
FILE: app/api/files/backups/route.ts
================================================
import {NextResponse} from "next/server";
import path from "path";
import type {StorageInput} from "@/features/storages/types";
import {dispatchStorage} from "@/features/storages/dispatch";
import {Readable} from "node:stream";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/files/backups"});

export async function GET(
    request: Request,
) {

    const {searchParams} = new URL(request.url);
    const token = searchParams.get('token');
    const expires = searchParams.get('expires');
    const pathFromUrl = searchParams.get('path');
    const storageId = searchParams.get('storageId');

    if (!pathFromUrl || !storageId) {
        return NextResponse.json({error: "Missing search params"}, {status: 404})
    }

    const input: StorageInput = {
        action: "get",
        data: {
            path: pathFromUrl,
            signedUrl: true,
        },
        metadata: {
            storageId: storageId,
            fileKind: "backups",
        }
    };

    log.info({input: input}, "Dispatch Storage");

    const result = await dispatchStorage(input, undefined, storageId);

    if (!result.success) {
        return NextResponse.json({error: "Enable to get file from provided storage channel, an error occurred !"})
    }

    const fileName = path.basename(pathFromUrl);

    const crypto = require('crypto');
    const expectedToken = crypto.createHash('sha256').update(`${fileName}${expires}`).digest('hex');
    if (token !== expectedToken) {
        return NextResponse.json(
            {error: 'Invalid signed token'},
            {status: 403}
        );
    }

    const expiresAt = parseInt(expires!, 10);
    if (Date.now() > expiresAt) {
        return NextResponse.json(
            {error: 'Signed token expired'},
            {status: 403}
        );
    }

    if (!result.file || !(result.file instanceof Readable)) {
        return NextResponse.json(
            {error: "Invalid file payload"},
            {status: 500}
        );
    }

    const fileStream = Readable.from(result.file as Readable);

    const stream = new ReadableStream({
        start(controller) {
            fileStream.on('data', (chunk) => controller.enqueue(chunk));
            fileStream.on('end', () => controller.close());
            fileStream.on('error', (err) => controller.error(err));
        },
    });

    return new NextResponse(stream, {
        headers: {
            'Content-Disposition': `attachment; filename="${fileName}"`,
            'Content-Type': 'application/octet-stream',
        },
    });
}




================================================
FILE: app/api/files/images/[fileName]/route.ts
================================================
import {NextResponse} from "next/server";
import {auth} from "@/lib/auth/auth";
import {headers} from "next/headers";
import {StorageInput} from "@/features/storages/types";
import {dispatchStorage} from "@/features/storages/dispatch";
import {Readable} from "node:stream";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/files/images"});

export async function GET(
    req: Request,
    {params}: { params: Promise<{ fileName: string }> }
) {
    const {searchParams} = new URL(req.url);
    const fileName = (await params).fileName;
    const storageId = searchParams.get('storageId');

    if (!fileName) return NextResponse.json({error: "Missing file parameter"}, {status: 400});

    const session = await auth.api.getSession({headers: await headers()});
    if (!session) return NextResponse.json({error: "Unauthorized"}, {status: 403});


    if (!storageId) {
        return NextResponse.json({error: "Missing storageId in search params"}, {status: 404})
    }

    const ext = fileName.split(".").pop()?.toLowerCase();
    const contentType =
        ext === "png"
            ? "image/png"
            : ext === "jpg" || ext === "jpeg"
                ? "image/jpeg"
                : ext === "gif"
                    ? "image/gif"
                    : ext === "webp"
                        ? "image/webp"
                        : "application/octet-stream";

    try {

        const path = `images/${fileName}`;

        const input: StorageInput = {
            action: "get",
            data: {
                path: path,
            },
            metadata: {
                storageId: storageId,
                fileKind: "images"
            }
        }

        const result = await dispatchStorage(input, undefined, storageId);

        if (!result.file || !(result.file instanceof Readable)) {
            log.error({error: result}, `An error occurred while getting file`);
            return NextResponse.json(
                {error: "Invalid file payload"},
                {status: 500}
            );
        }

        const fileStream = Readable.from(result.file as Readable);

        const stream = new ReadableStream({
            start(controller) {
                fileStream.on('data', (chunk) => controller.enqueue(chunk));
                fileStream.on('end', () => controller.close());
                fileStream.on('error', (err) => controller.error(err));
            },
        });

        return new NextResponse(stream, {
            headers: {
                'Content-Disposition': `inline; filename="${fileName}"`,
                "Cache-Control": "no-store",
                "Content-Type": contentType,
            },
        });

    } catch (err) {
        log.error({error: err}, `Error streaming image`);
        return NextResponse.json({error: "Error fetching file"}, {status: 500});
    }
}

================================================
FILE: app/api/google/drive/callback/route.ts
================================================
export async function GET(request: Request) {
    const url = new URL(request.url);
    const code = url.searchParams.get("code");

    const success = Boolean(code);

    return new Response(
        `
        <!DOCTYPE html>
        <html>
            <body>
                <h1>${success ? "Success" : "Failed"}</h1>
            </body>
        </html>
        `,
        {
            status: success ? 200 : 400,
            headers: {
                "Content-Type": "text/html; charset=utf-8",
            },
        }
    );
}


================================================
FILE: app/api/tus/hooks/route.ts
================================================
import {NextResponse} from "next/server";
import fs from "fs";
import path from "path";
import {env} from "@/env.mjs";
import {logger} from "@/lib/logger";

const log = logger.child({module: "api/tus/hooks"});

export async function POST(request: Request) {
    try {
        const body = await request.json();
        const event = body.Event
        const headers = event.HTTPRequest.Header
        const uploadLength = headers["X-File-Size"]?.[0];
        const uploadOffset = headers["Upload-Offset"]?.[0];
        const status = headers["X-Status"]?.[0];

        log.info(`Upload ID : ${event.Upload.ID} (${uploadOffset}/${uploadLength})`);

        if (status === "success") {
            if (
                body.Type === "post-receive" &&
                event.Upload.SizeIsDeferred === false &&
                event.Upload.Offset === event.Upload.Size
            ) {
                const id = event.Upload.ID;
                const fileName = headers["X-File-Name"]?.[0];
                const filePath = headers["X-File-Path"]?.[0];

                if (!filePath) {
                    return NextResponse.json({error: "Missing X-File-Path"}, {status: 500});
                }

                const uploadDir = path.join(env.PRIVATE_PATH!, "/uploads/");

                const oldFilePath = path.join(uploadDir, "tmp", id);
                const newFilePath = path.join(uploadDir, filePath);

                fs.mkdirSync(path.dirname(newFilePath), {recursive: true});

                let retries = 10;
                while (!fs.existsSync(oldFilePath)) {
                    if (retries-- === 0) {
                        return NextResponse.json({error: `Upload file not found: ${oldFilePath}`}, {status: 500});
                    }
                    await new Promise(r => setTimeout(r, 200));
                }

                fs.renameSync(oldFilePath, newFilePath);

                const infoFilePath = `${oldFilePath}.info`;
                if (fs.existsSync(infoFilePath)) {
                    fs.unlinkSync(infoFilePath);
                }

                const metadataHeaderB64 = headers["Upload-Metadata"]?.[0];

                if (metadataHeaderB64) {
                    const metadataHeader = Buffer.from(metadataHeaderB64, "base64").toString("utf-8");
                    if (metadataHeader) {
                        const tomlContent = metadataHeader
                            .split(",")
                            .map((pair) => {
                                const [key, value] = pair.split(" ");
                                const escapedValue = value.replace(/"/g, '\\"');
                                return `${key} = "${escapedValue}"`;
                            })
                            .join("\n");
                        const metaFilePath = `${newFilePath}.meta`;
                        fs.writeFileSync(metaFilePath, tomlContent, "utf-8");
                    }
                }
            }
        }
        return NextResponse.json({});
    } catch (error) {
        log.error({error: error},"TUS Hook error");
        return NextResponse.json({error: "Internal server error"}, {status: 500});
    }
}

================================================
FILE: app/error/page.tsx
================================================
"use client";

import { Loader2 } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { useSession } from "@/lib/auth/auth-client";

export default function ErrorPage() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const { data: session, isPending } = useSession();

  useEffect(() => {
    if (isPending) {
      return;
    }

    const error = searchParams.get("error");
    const params = new URLSearchParams();
    if (error) {
      params.set("error", error);
    }

    const destination = session ? "/dashboard/home" : "/login";
    router.replace(`${destination}?${params.toString()}`);
  }, [isPending, session, router, searchParams]);

  return (
    <div className="flex h-screen w-full items-center justify-center">
      <Loader2 className="h-8 w-8 animate-spin" />
    </div>
  );
}


================================================
FILE: app/globals.css
================================================
@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

@theme inline {
    --color-background: var(--background);
    --color-foreground: var(--foreground);
    --font-sans: var(--font-poppins), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    --font-title: var(--font-poppins), var(--font-poppins), sans-serif;
    --font-mono: var(--font-geist-mono);
    --font-author: var(--font-author);
    --font-poppins: var(--font-poppins);
    --color-sidebar-ring: var(--sidebar-ring);
    --color-sidebar-border: var(--sidebar-border);
    --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
    --color-sidebar-accent: var(--sidebar-accent);
    --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
    --color-sidebar-primary: var(--sidebar-primary);
    --color-sidebar-foreground: var(--sidebar-foreground);
    --color-sidebar: var(--sidebar);
    --color-chart-5: var(--chart-5);
    --color-chart-4: var(--chart-4);
    --color-chart-3: var(--chart-3);
    --color-chart-2: var(--chart-2);
    --color-chart-1: var(--chart-1);
    --color-ring: var(--ring);
    --color-input: var(--input);
    --color-border: var(--border);
    --color-destructive: var(--destructive);
    --color-accent-foreground: var(--accent-foreground);
    --color-accent: var(--accent);
    --color-muted-foreground: var(--muted-foreground);
    --color-muted: var(--muted);
    --color-secondary-foreground: var(--secondary-foreground);
    --color-secondary: var(--secondary);
    --color-primary-foreground: var(--primary-foreground);
    --color-primary: var(--primary);
    --color-popover-foreground: var(--popover-foreground);
    --color-popover: var(--popover);
    --color-card-foreground: var(--card-foreground);
    --color-card: var(--card);
    --radius-sm: calc(var(--radius) - 4px);
    --radius-md: calc(var(--radius) - 2px);
    --radius-lg: var(--radius);
    --radius-xl: calc(var(--radius) + 4px);
    --animate-accordion-down: accordion-down 0.2s ease-out;
    --animate-accordion-up: accordion-up 0.2s ease-out;

    @keyframes accordion-down {
        from {
            height: 0;
        }
        to {
            height: var(--radix-accordion-content-height);
        }
    }

    @keyframes accordion-up {
        from {
            height: var(--radix-accordion-content-height);
        }
        to {
            height: 0;
        }
    }
}

:root {
    --radius: 0.625rem;
    --background: oklch(1 0 0);
    --foreground: oklch(0.145 0 0);
    --card: oklch(1 0 0);
    --card-foreground: oklch(0.145 0 0);
    --popover: oklch(1 0 0);
    --popover-foreground: oklch(0.145 0 0);
    --primary: oklch(0.611 0.204 42.149);
    --primary-foreground: oklch(0.985 0 0);
    --secondary: oklch(0.97 0 0);
    --secondary-foreground: oklch(0.205 0 0);
    --muted: oklch(0.97 0 0);
    --muted-foreground: oklch(0.556 0 0);
    --accent: oklch(0.97 0 0);
    --accent-foreground: oklch(0.205 0 0);
    --destructive: oklch(0.577 0.245 27.325);
    --border: oklch(0.922 0 0);
    --input: oklch(0.922 0 0);
    --ring: oklch(0.611 0.204 42.149);
    --chart-1: oklch(0.646 0.222 41.116);
    --chart-2: oklch(0.6 0.118 184.704);
    --chart-3: oklch(0.398 0.07 227.392);
    --chart-4: oklch(0.828 0.189 84.429);
    --chart-5: oklch(0.769 0.188 70.08);
    --sidebar: oklch(0.985 0 0);
    --sidebar-foreground: oklch(0.145 0 0);
    --sidebar-primary: oklch(0.611 0.204 42.149);
    --sidebar-primary-foreground: oklch(0.985 0 0);
    --sidebar-accent: oklch(0.97 0 0);
    --sidebar-accent-foreground: oklch(0.205 0 0);
    --sidebar-border: oklch(0.922 0 0);
    --sidebar-ring: oklch(0.611 0.204 42.149);
}

.dark {
    --background: oklch(0.145 0 0);
    --foreground: oklch(0.985 0 0);
    --card: oklch(0.205 0 0);
    --card-foreground: oklch(0.985 0 0);
    --popover: oklch(0.205 0 0);
    --popover-foreground: oklch(0.985 0 0);
    --primary: oklch(0.704 0.191 47.132);
    --primary-foreground: oklch(0.985 0 0);
    --secondary: oklch(0.269 0 0);
    --secondary-foreground: oklch(0.985 0 0);
    --muted: oklch(0.269 0 0);
    --muted-foreground: oklch(0.708 0 0);
    --accent: oklch(0.269 0 0);
    --accent-foreground: oklch(0.985 0 0);
    --destructive: oklch(0.704 0.191 22.216);
    --border: oklch(1 0 0 / 10%);
    --input: oklch(1 0 0 / 15%);
    --ring: oklch(0.704 0.191 47.132);
    --chart-1: oklch(0.488 0.243 264.376);
    --chart-2: oklch(0.696 0.17 162.48);
    --chart-3: oklch(0.769 0.188 70.08);
    --chart-4: oklch(0.627 0.265 303.9);
    --chart-5: oklch(0.645 0.246 16.439);
    --sidebar: oklch(0.205 0 0);
    --sidebar-foreground: oklch(0.985 0 0);
    --sidebar-primary: oklch(0.704 0.191 47.132);
    --sidebar-primary-foreground: oklch(0.985 0 0);
    --sidebar-accent: oklch(0.269 0 0);
    --sidebar-accent-foreground: oklch(0.985 0 0);
    --sidebar-border: oklch(1 0 0 / 10%);
    --sidebar-ring: oklch(0.704 0.191 47.132);
}

@layer base {
    * {
        @apply border-border outline-ring/50;
    }
    body {
        @apply bg-background text-foreground font-sans;
    }
    h1, h2, h3, h4, h5, h6 {
        @apply font-title;
    }

    .scrollbar-hide {
        -ms-overflow-style: none;
        scrollbar-width: none;
    }

    .scrollbar-hide::-webkit-scrollbar {
        display: none;
    }
}


================================================
FILE: app/layout.tsx
================================================
import type { Metadata } from "next";
import type React from "react";
import "./globals.css";
import { ConsoleSilencer } from "@/components/wrappers/common/console-silencer";
import { author, geistMono, poppins } from "@/fonts/fonts";
import { cn } from "@/lib/utils";
import { Providers } from "./providers";

const title = process.env.PROJECT_NAME ?? "Portabase";

export const metadata: Metadata = {
  title: {
    default: title,
    template: `%s - ${title}`,
  },
  description: process.env.PROJECT_DESCRIPTION ?? undefined,
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <meta name="apple-mobile-web-app-title" content={title} />
      </head>
      <body
        className={cn(
          poppins.variable,
          author.variable,
          geistMono.variable,
          "font-sans h-full",
        )}
      >
        <ConsoleSilencer />
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}


================================================
FILE: app/manifest.json
================================================
{
  "name": "Portabase",
  "short_name": "Portabase",
  "icons": [
    {
      "src": "/web-app-manifest-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "/web-app-manifest-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "display": "standalone"
}

================================================
FILE: app/not-found.tsx
================================================
import BackButton from "@/components/wrappers/common/button/back-button";

export default async function NotFound() {
  return (
    <div className="flex items-center min-h-screen px-4 py-12 sm:px-6 md:px-8 lg:px-12 xl:px-16">
      <div className="w-full space-y-6 text-center">
        <div className="space-y-3">
          <h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
            Not found
          </h1>
          <p className="leading-7 not-first:mt-6">
            The content you are trying to view is not available.
          </p>
        </div>
        <BackButton>Go home</BackButton>
      </div>
    </div>
  );
}


================================================
FILE: app/providers.tsx
================================================
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { type PropsWithChildren, Suspense } from "react";

import { Toaster } from "@/components/ui/sonner";
import { ErrorLayout } from "@/components/wrappers/common/error-layout";
import { ThemeMetaUpdaterRoot } from "@/features/browser/theme-meta-updater-root";
import { ThemeProvider } from "@/features/theme/theme-provider";

export type ProviderProps = PropsWithChildren<{}>;
const queryClient = new QueryClient();

export const Providers = (props: ProviderProps) => {
  return (
    <Suspense fallback={null}>
      <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
        <ThemeMetaUpdaterRoot />
        <QueryClientProvider client={queryClient}>
          <ErrorLayout>
            <Toaster />
            {props.children}
          </ErrorLayout>
        </QueryClientProvider>
      </ThemeProvider>
    </Suspense>
  );
};


================================================
FILE: components.json
================================================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "radix",
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "registries": {
    "@reui": "https://reui.io/r/{name}.json"
  }
}


================================================
FILE: docker/dockerfile/Dockerfile
================================================
FROM --platform=$BUILDPLATFORM node:22-bullseye AS base

RUN apt-get update && apt-get install -y \
    curl \
    ca-certificates \
    bash \
    tzdata \
    libc6 \
    build-essential \
    g++ \
    make \
    autoconf \
    automake \
    libtool \
    git \
    nginx \
    && rm -rf /var/lib/apt/lists/*


RUN mkdir -p /etc/apt/keyrings \
    && curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
    | gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg \
    && echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main" \
    > /etc/apt/sources.list.d/pgdg.list

RUN apt-get update && apt-get install -y \
    postgresql-18 \
    postgresql-client-18 \
    postgresql-contrib-18 \
    && rm -rf /var/lib/apt/lists/*

ENV PATH="/usr/lib/postgresql/18/bin:${PATH}"


FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS tusd-base

ARG TUSD_VERSION=2.8.0

RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /build

RUN git clone https://github.com/tus/tusd.git . \
    && git checkout v${TUSD_VERSION}

RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \
    go build -ldflags="-s -w" -o /tusddist/tusd ./cmd/tusd

FROM scratch AS tusd-dist
COPY --from=tusd-base /tusddist/tusd /tusd


FROM base AS build-env

RUN corepack enable && corepack prepare pnpm@latest --activate

FROM build-env AS deps

WORKDIR /app

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm i --frozen-lockfile

FROM build-env AS dev

COPY --from=deps /app/node_modules ./node_modules
COPY --from=tusd-base /usr/local/bin/tusd /usr/local/bin/tusd
WORKDIR /app
COPY . .

USER root
RUN chmod +x /app/docker/entrypoints/app-dev-entrypoint.sh

ENTRYPOINT ["sh","/app/docker/entrypoints/app-dev-entrypoint.sh"]

FROM build-env AS builder

WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1
RUN pnpm run build

FROM base AS prod

WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=80
ENV HOSTNAME="0.0.0.0"
ENV PGDATA=/data/postgres
ENV PRIVATE_PATH=/data/private


RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.ts ./
COPY --from=builder /app/portabase.config.ts ./
COPY --from=builder /app/drizzle.config.ts ./
COPY --from=builder --chown=1001:1001 /app/.next/standalone ./
COPY --from=builder --chown=1001:1001 /app/.next/static ./.next/static
COPY --chown=1001:1001 src/db ./src/db
COPY --from=deps /app/node_modules ./node_modules

COPY --from=tusd-dist /tusd /usr/local/bin/tusd
RUN chmod +x /usr/local/bin/tusd

COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf


RUN mkdir -p .next /data/private/uploads \
    && chown -R nextjs:nodejs .next /data/private /app/public


USER root
COPY ./docker/entrypoints/app-prod-entrypoint.sh /app/app-prod-entrypoint.sh
RUN chmod +x /app/app-prod-entrypoint.sh

EXPOSE 80
ENTRYPOINT ["sh","/app/app-prod-entrypoint.sh"]


================================================
FILE: docker/entrypoints/app-dev-entrypoint.sh
================================================
#!/bin/bash

set -euo pipefail

echo "▶ Running Drizzle codegen..."
pnpm drizzle-kit generate

echo "▶ Applying migrations..."
pnpm drizzle-kit migrate

echo "▶ Starting Next.js dev server..."
exec pnpm dev

================================================
FILE: docker/entrypoints/app-prod-entrypoint.sh
================================================
#!/bin/bash

if [ -n "$TZ" ]; then
    echo "[INFO] Application timezone set to $TZ (environment only)"
    export TZ="$TZ"
else
    echo "[WARN] No TZ provided, using default container timezone"
fi

POSTGRES_BIN=$(ls -d /usr/lib/postgresql/*/bin | head -n 1)

if [ -z "$POSTGRES_BIN" ]; then
    echo "PostgreSQL binaries not found"
    exit 1
fi

export PATH="$POSTGRES_BIN:$PATH"

if [ -z "$DATABASE_URL" ]; then
    echo "[INFO] No DATABASE_URL provided, starting internal Postgres..."

    mkdir -p "$PGDATA"
    chown -R postgres:postgres "$PGDATA"

    if [ ! -f "$PGDATA/PG_VERSION" ]; then
        echo "[INFO] Initializing database cluster..."
        if ! su postgres -c "initdb -D '$PGDATA'" > /dev/null 2>&1; then
            echo "[ERROR] initdb failed"
            exit 1
        fi
    fi

    if ! su postgres -c "pg_ctl -D '$PGDATA' \
        -o \"-c listen_addresses='localhost' -c logging_collector=on\" \
        -l $PGDATA/postgres.log -w start" > /dev/null 2>&1; then
        echo "[ERROR] PostgreSQL failed to start"
        exit 1
    fi

    until su postgres -c "pg_isready -h 127.0.0.1 -p 5432" > /dev/null 2>&1; do
        sleep 1
    done

    echo "[INFO] PostgreSQL server is up and accepting connections"

    DB_USER="${POSTGRES_USER:-portabase_user}"
    DB_PASS="${POSTGRES_PASSWORD:-JaB6b1SUtIWYvt7srnOt}"
    DB_NAME="${POSTGRES_DB:-portabase_db}"

    USER_EXISTS=$(su postgres -c "psql -tAc \"SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'\"" 2>/dev/null)
    if [ "$USER_EXISTS" != "1" ]; then
        if ! su postgres -c "psql -c \"CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';\"" > /dev/null 2>&1; then
            echo "[ERROR] Failed creating user"
            exit 1
        fi
    fi

    DB_EXISTS=$(su postgres -c "psql -tAc \"SELECT 1 FROM pg_database WHERE datname='$DB_NAME'\"" 2>/dev/null)
    if [ "$DB_EXISTS" != "1" ]; then
        if ! su postgres -c "psql -c \"CREATE DATABASE $DB_NAME OWNER $DB_USER;\"" > /dev/null 2>&1; then
            echo "[ERROR] Failed creating database"
            exit 1
        fi
    fi

    export DATABASE_URL="postgres://$DB_USER:$DB_PASS@127.0.0.1:5432/$DB_NAME"

    echo "[SUCCESS] Internal PostgreSQL started successfully"
    echo "[SUCCESS] Database: $DB_NAME | User: $DB_USER | Host: 127.0.0.1:5432"
fi


mkdir -p /data/private/uploads/tmp
echo "▶ Starting tusd server..."
tusd --base-path /tus/files/ --upload-dir /data/private/uploads/tmp --hooks-http http://127.0.0.1:3000/api/tus/hooks --port 1080 --max-size 21474836480 &

echo "▶ Starting Next.js server..."
PORT=3000 node server.js &

echo "▶ Starting nginx..."
exec nginx -g "daemon off;"




================================================
FILE: docker/nginx/nginx.conf
================================================
events {}

http {
    client_max_body_size 20G;
    ignore_invalid_headers  off;

    server {
        listen 80;

       location /tus/ {
            proxy_pass http://127.0.0.1:1080/tus/;

            proxy_pass_request_headers on;

            proxy_request_buffering  off;
            proxy_buffering          off;
            proxy_http_version       1.1;

            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $http_host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_set_header         Upgrade $http_upgrade;
            proxy_set_header         Connection "upgrade";
        }

        location / {
            proxy_pass http://127.0.0.1:3000;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $http_host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}


================================================
FILE: docker-compose.e2e.yml
================================================
services:
  app:
    build:
      context: .
      dockerfile: docker/dockerfile/Dockerfile
      target: prod
    ports:
      - '8887:80'
    environment:
      TZ: "Europe/Paris"
    env_file:
      - .env
    volumes:
      - portabase-e2e-data:/data
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:18-alpine
    ports:
      - "5433:5432"
    volumes:
      - postgres-e2e-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=devdb
      - POSTGRES_USER=devuser
      - POSTGRES_PASSWORD=changeme
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser -d devdb"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  portabase-e2e-data:
  postgres-e2e-data:


================================================
FILE: docker-compose.func.yml
================================================
name: portabase-dev-func

services:
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    command: start-dev --import-realm
    # environment:
    #   KC_BOOTSTRAP_ADMIN_USERNAME: admin
    #   KC_BOOTSTRAP_ADMIN_PASSWORD: admin
    ports:
      - "3056:8080"
    volumes:
      - keycloak-data:/opt/keycloak/data
      - ./seeds/keycloak:/opt/keycloak/data/import:ro
      - ./export:/tmp/export
  pocket-id:
    image: ghcr.io/pocket-id/pocket-id
    restart: unless-stopped
    environment:
      - APP_URL=http://localhost:3055
      - ENCRYPTION_KEY=QwHyjbZvSsDUAcjpdmSPsuYxaH6vET6OeBaeLwXccCb43L6Om3W1AoU5pKIJTzYr
    ports:
      - 3055:1411
    volumes:
      - pocket-id-data:/app/data
      - ./seeds/pocket-id:/seed:ro
    healthcheck:
      test: "curl -f http://localhost:1411/healthz"
      interval: 1m30s
      timeout: 5s
      retries: 2
      start_period: 10s
volumes:
  keycloak-data:
  pocket-id-data:


================================================
FILE: docker-compose.prod.yml
================================================
services:
  app:
#        build:
#          context: .
#          dockerfile: docker/dockerfile/Dockerfile
#          target: prod
    image: portabase/portabase:1.7.1
    ports:
      - '8887:80'
    environment:
      TZ: "Europe/Paris"
    env_file:
      - .env
    volumes:
      - portabase-data:/data
    depends_on:
      db:
        condition: service_healthy
    container_name: portabase-app-prod

  db:
    image: postgres:16-alpine
    ports:
      - "5433:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=devdb
      - POSTGRES_USER=devuser
      - POSTGRES_PASSWORD=changeme
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U devuser -d devdb" ]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  portabase-data:
  postgres-data:


================================================
FILE: docker-compose.yml
================================================
name: portabase-dev

services:
  db:
    image: postgres:17-alpine
    ports:
      - "5433:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=devdb
      - POSTGRES_USER=devuser
      - POSTGRES_PASSWORD=changeme
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser -d devdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  tusd:
    image: tusproject/tusd:v2.8.0
    ports:
      - "1080:8080"
    command: >
      -upload-dir /data/uploads/tmp
      -hooks-http http://localhost:8887/api/tus/hooks
      -max-size 21474836480
      -base-path /tus/files/
    extra_hosts:
      - "localhost:host-gateway"
    volumes:
      - ./private/uploads/tmp:/data/uploads/tmp
    user: "1000:1000"

volumes:
  postgres-data:


================================================
FILE: drizzle.config.ts
================================================
import { defineConfig } from "drizzle-kit";

import dotenv from "dotenv";

dotenv.config({
    path: ".env",
});

export default defineConfig({
    out: "./src/db/migrations",
    schema: ["./src/db/schema", "./src/db/schema/types.ts"],
    dialect: "postgresql",
    dbCredentials: {
        url: process.env.DATABASE_URL!,
    },
});


================================================
FILE: e2e/access-management.spec.ts
================================================
import {expect, test} from "@playwright/test";
import {users} from "./helpers/auth";
import {changeUserRole, create, switchToDefault} from "./helpers/access-management";
import {LOCAL_STORAGE_PATH} from "./helpers/session";

test.use({storageState: LOCAL_STORAGE_PATH});

const firstOrganization = "Organization A";
const secondOrganization = "Organization B";

test.describe.serial(() => {
    test("Create an organization from organizations page", async ({page}) => {
        await page.goto("/dashboard/admin/organizations");
        await expect(page.getByRole("heading", {name: "Active organizations"})).toBeVisible();

        await create(page, "button", firstOrganization);

        await expect(page.getByText(/Organization has been successfully created\./i)).toBeVisible();
        await expect(page).toHaveURL("/dashboard/admin/organizations");
        await expect(page.getByRole("button", {name: firstOrganization, exact: true})).toBeVisible();

        await switchToDefault(page);
    });

    test("Create an organization from sidebar button", async ({page}) => {
        await page.goto("/dashboard/home");
        await expect(page.getByRole("heading", {name: "Dashboard"})).toBeVisible();

        await create(page, "sidebar", secondOrganization);

        await expect(page.getByText(/Organization has been successfully created\./i)).toBeVisible();
        await expect(page).toHaveURL("/dashboard/home");
        await expect(page.getByRole("button", {name: secondOrganization, exact: true})).toBeVisible();

        await switchToDefault(page);
    });

    test("Change John Doe's role from pending to user", async ({page}) => {
        await page.goto("/dashboard/admin/users");
        await expect(page.getByText(users.normal.email, {exact: true})).toBeVisible();

        const userRow = page.locator("tr").filter({hasText: users.normal.email}).first();
        await expect(userRow).toBeVisible();
        await userRow.locator("button").last().click();
        await page.getByRole('menuitem', {name: 'Role'}).click();

        await expect(page.getByRole("heading", {name: "Change the user's role"})).toBeVisible();

        await changeUserRole(page, "user")

        await expect(page.getByText("User role changed successfully.")).toBeVisible();
        await expect(page.locator("tr").filter({hasText: users.normal.email}).getByText("user", {exact: true})).toBeVisible();
    });

    test("Add John Doe to Organization A", async ({page}) => {
        await page.goto("/dashboard/admin/organizations");
        await expect(page.getByRole("heading", {name: "Active organizations"})).toBeVisible();

        const organizationRow = page.locator("tr").filter({hasText: firstOrganization}).first();
        await expect(organizationRow).toBeVisible();
        await organizationRow.locator('a[href*="/dashboard/admin/organizations/"], a[href*="organizations/"]').click();

        await expect(page.getByText(firstOrganization, {exact: true})).toBeVisible();
        await page.getByRole("button", {name: /Add member/i}).click();
        await expect(page.getByRole("heading", {name: "Add member to your organization"})).toBeVisible();

        await page.getByPlaceholder("Enter a user email").fill(users.normal.email);
        await page.getByRole("option", {name: new RegExp(users.normal.email, "i")}).click();
        await page.getByRole("button", {name: "Confirm"}).click();

        await expect(page.getByText("Member successfully added!")).toBeVisible();
        await expect(page.getByText(users.normal.email, {exact: true})).toBeVisible();
    });
});


================================================
FILE: e2e/agent.spec.ts
================================================
import {expect, test} from "@playwright/test";
import {execSync} from "child_process";
import fs from "fs";
import os from "os";
import path from "path";
import {createAgentWithDockerDatabases} from "./helpers/agent-cli";
import {create, edit, get, remove} from "./helpers/agent";
import {LOCAL_STORAGE_PATH} from "./helpers/session";

const agent = {
    aName: "Agent A",
    aUpdatedName: "Agent A Updated",
    bName: "Agent B",
    description: "Agent created by Playwright E2E",
    updatedDescription: "Agent updated by Playwright E2E",
};

test.use({storageState: LOCAL_STORAGE_PATH});

test.describe.serial(() => {
    let agentWorkspace: string | null = null;

    test("Create agent A from empty state", async ({page}) => {
        await page.goto("/dashboard/agents");
        await expect(page.getByRole("heading", {name: "Agents"})).toBeVisible();
        await expect(page.getByText("Create new Agent", {exact: true})).toBeVisible();
        await create(page, "emptyState", agent.aName, agent.description);

        await expect(page.getByText("Success creating agent")).toBeVisible();
        await expect(get(page, agent.aName)).toBeVisible();
        await expect(page.getByText("Create new Agent", {exact: true})).toHaveCount(0);
    });

    test("Edit agent A", async ({page}) => {
        await page.goto("/dashboard/agents");
        await expect(page.getByRole("heading", {name: "Agents"})).toBeVisible();
        await expect(get(page, agent.aName)).toBeVisible();

        await edit(page, agent.aName, agent.aUpdatedName, agent.updatedDescription);

        await expect(page.getByText("Success updating agent")).toBeVisible();
        await expect(page.getByText(agent.aUpdatedName, {exact: true})).toBeVisible();
        await expect(page.getByText(agent.updatedDescription, {exact: true})).toBeVisible();
    });

    test("Create agent B from classic button", async ({page}) => {
        await page.goto("/dashboard/agents");
        await expect(page.getByRole("heading", {name: "Agents"})).toBeVisible();
        await expect(page.getByRole("button", {name: /Create Agent/i})).toBeVisible();
        await create(page, "button", agent.bName, agent.description);

        await expect(page.getByText("Success creating agent")).toBeVisible();
        await expect(get(page, agent.bName)).toBeVisible();
    });

    test("Delete Agent B", async ({page}) => {
        await page.goto("/dashboard/agents");
        await expect(page.getByRole("heading", {name: "Agents"})).toBeVisible();
        await expect(get(page, agent.bName)).toBeVisible();
        await remove(page, agent.bName);

        await expect(page.getByText("Agent has been successfully deleted.")).toBeVisible();
        await expect(page).toHaveURL("/dashboard/agents");
        await expect(page.getByText(agent.bName)).toHaveCount(0);
    });

    // test("Launch the updated agent", async ({page}) => {
    //     await page.goto("/dashboard/agents");
    //     await expect(page.getByRole("heading", {name: "Agents"})).toBeVisible();
    //     await get(page, agent.aName).click();
    //
    //     await expect(page).toHaveURL(/\/dashboard\/agents\/.+/);
    //     await expect(page.getByText(agent.aName, {exact: true})).toBeVisible();
    //     await expect(page.getByText("Registration & Setup")).toBeVisible();
    //
    //     const commandInput = page.locator("input[readonly]").first();
    //     await page.locator("input[readonly]").first().locator("xpath=following-sibling::button[1]").click();
    //     const command = await commandInput.inputValue();
    //
    //     agentWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), "portabase-agent-"));
    //     await createAgentWithDockerDatabases(command, agentWorkspace);
    //     execSync(`portabase start "${agent.aName}"`, {
    //         cwd: agentWorkspace,
    //         stdio: "pipe",
    //         timeout: 120_000,
    //     });
    //
    //     await expect(page.getByText("Never connected.")).toHaveCount(0, {timeout: 120_000});
    //     await expect(page.getByText("Action Required")).toHaveCount(0);
    // });

    test.afterAll(async () => {
        if (agentWorkspace) {
            try {
                execSync(`portabase stop "${agent.aName}"`, {
                    cwd: agentWorkspace,
                    stdio: "pipe",
                    timeout: 30_000,
                });
            } catch {
            }

            try {
                execSync(`portabase uninstall --force "${agent.aName}"`, {
                    cwd: agentWorkspace,
                    stdio: "pipe",
                    timeout: 30_000,
                });
            } catch {
            }

            fs.rmSync(agentWorkspace, {recursive: true, force: true});
            agentWorkspace = null;
        }
    });
});

================================================
FILE: e2e/auth.spec.ts
================================================
import {test, expect} from '@playwright/test';
import {login, register, users} from "./helpers/auth";
import {LOCAL_STORAGE_PATH} from "./helpers/session";

const TIMEOUT = undefined
// const TIMEOUT = 5000

test.use({storageState: LOCAL_STORAGE_PATH})

test.describe.serial( () => {

    test('Redirect to login if not connected', async ({page}) => {
        await page.goto('/dashboard/projects');
        await expect(page).toHaveURL("login?redirect=%2Fdashboard%2Fprojects", {timeout: TIMEOUT});
    });

    test('Password too short', async ({page}) => {
        await page.goto('')
        await page.click('text=Sign up')
        await expect(page).toHaveURL('/register')

        let password = '123456'
        await register(page, users["admin"].username, users["admin"].email, password, password)

        const toast = page.locator('text=Must have at least 8 character')
        await expect(toast).toBeVisible()
    })

    test('Password too simple', async ({page}) => {
        await page.goto('')
        await page.click('text=Sign up')
        await expect(page).toHaveURL('/register')

        let password = '12345678'
        await register(page, users["admin"].username, users["admin"].email, password, password)

        const toast = page.locator('text=Your password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.')
        await expect(toast).toBeVisible()
    })

    test('Password and confirm password mismatch', async ({page}) => {
        await page.goto('/')
        await page.click('text=Sign up')
        await expect(page).toHaveURL('/register')

        let password = 'testPASS123456!'
        let confirmPassword = 'testPASS123456!!'
        await register(page, users["admin"].username, users["admin"].email, password, confirmPassword)

        const toast = page.locator('text=The passwords did not match')
        await expect(toast).toBeVisible()
    })

    test('Successful register for admin', async ({page}) => {
        await page.goto('/')

        await page.click('text=Sign up')
        await expect(page).toHaveURL('/register')

        await register(page, users["admin"].username, users["admin"].email, users["admin"].password, users["admin"].password)

        await expect(page).toHaveURL('/login', {timeout: TIMEOUT})
    })

    test('User already exists.', async ({page}) => {
        await page.goto('/')

        await page.click('text=Sign up')
        await expect(page).toHaveURL('/register')

        await register(page, users["admin"].username, users["admin"].email, users["admin"].password, users["admin"].password)

        const toast = page.locator('text=User already exists. Use another email.')
        await expect(toast).toBeVisible()
    })

    test('Successful register for normal', async ({page}) => {
        await page.goto('/')

        await page.click('text=Sign up')
        await expect(page).toHaveURL('/register')

        await register(page, users["normal"].username, users["normal"].email, users["normal"].password, users["normal"].password)

        await expect(page).toHaveURL('/login', {timeout: TIMEOUT})
    })

    test('Failed login because account not active', async ({page}) => {
        await page.goto('/login')
        await login(page, users["normal"].email, users["normal"].password)

        const toast = page.locator('text=Your account is not active.')
        await expect(toast).toBeVisible()
    })

    test('Successful login', async ({page}) => {
        await page.goto('/login')
        await login(page, users["admin"].email, users["admin"].password)

        await expect(page).toHaveURL('/dashboard/home', {timeout: TIMEOUT})
        await expect(page.getByRole('link', {name: 'Logo Portabase'})).toBeVisible()
        await page.context().storageState({path: LOCAL_STORAGE_PATH})
    })
})


================================================
FILE: e2e/cleanup.spec.ts
================================================
import {expect, test} from "@playwright/test";
import fs from "fs";
import {logout} from "./helpers/auth";
import {LOCAL_STORAGE_PATH} from "./helpers/session";


test.use({storageState: LOCAL_STORAGE_PATH});

test.describe( () => {
    test.afterAll(async () => {
        if (fs.existsSync(LOCAL_STORAGE_PATH)) {
            fs.unlinkSync(LOCAL_STORAGE_PATH);
        }
    });

    test("Remove shared storage state", async ({page}) => {
        await test.step("Logout shared authenticated session", async () => {
            if (!fs.existsSync(LOCAL_STORAGE_PATH)) {
                return;
            }

            const content = fs.readFileSync(LOCAL_STORAGE_PATH, "utf-8").trim();
            if (!content || content === "{}") {
                return;
            }

            await page.goto('/dashboard/ho
Download .txt
gitextract_jp3gfjy6/

├── .dockerignore
├── .eslintrc.json
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── SECURITY.md
│   └── workflows/
│       ├── discord.yml
│       ├── docker.yml
│       ├── e2e.yml
│       ├── helm.yml
│       ├── release-candidate.yml
│       ├── release.yml
│       └── security.yml
├── .gitignore
├── .gitleaks.toml
├── .pre-commit-config.yaml
├── .release-it.json
├── CITATION.cff
├── LICENSE
├── Makefile
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── forgot-password/
│   │   │   └── page.tsx
│   │   ├── guard/
│   │   │   └── page.tsx
│   │   ├── layout.tsx
│   │   ├── login/
│   │   │   └── page.tsx
│   │   ├── register/
│   │   │   └── page.tsx
│   │   └── reset-password/
│   │       └── page.tsx
│   ├── (customer)/
│   │   └── dashboard/
│   │       ├── (admin)/
│   │       │   ├── admin/
│   │       │   │   ├── organizations/
│   │       │   │   │   ├── [organizationId]/
│   │       │   │   │   │   └── page.tsx
│   │       │   │   │   └── page.tsx
│   │       │   │   ├── settings/
│   │       │   │   │   └── page.tsx
│   │       │   │   └── users/
│   │       │   │       └── page.tsx
│   │       │   ├── agents/
│   │       │   │   ├── [agentId]/
│   │       │   │   │   └── page.tsx
│   │       │   │   └── page.tsx
│   │       │   ├── layout.tsx
│   │       │   ├── notifications/
│   │       │   │   ├── channels/
│   │       │   │   │   └── page.tsx
│   │       │   │   └── logs/
│   │       │   │       └── page.tsx
│   │       │   └── storages/
│   │       │       └── channels/
│   │       │           └── page.tsx
│   │       ├── (organization)/
│   │       │   ├── migration/
│   │       │   │   └── page.tsx
│   │       │   ├── projects/
│   │       │   │   ├── [projectId]/
│   │       │   │   │   ├── database/
│   │       │   │   │   │   └── [databaseId]/
│   │       │   │   │   │       └── page.tsx
│   │       │   │   │   └── page.tsx
│   │       │   │   └── page.tsx
│   │       │   ├── settings/
│   │       │   │   ├── agents/
│   │       │   │   │   ├── [agentId]/
│   │       │   │   │   │   └── page.tsx
│   │       │   │   │   └── page.tsx
│   │       │   │   └── page.tsx
│   │       │   └── statistics/
│   │       │       └── page.tsx
│   │       ├── home/
│   │       │   └── page.tsx
│   │       ├── layout.tsx
│   │       └── loading.tsx
│   ├── (landing)/
│   │   ├── home/
│   │   │   └── page.tsx
│   │   └── page.tsx
│   ├── api/
│   │   ├── agent/
│   │   │   └── [agentId]/
│   │   │       ├── backup/
│   │   │       │   ├── helpers.ts
│   │   │       │   ├── route.ts
│   │   │       │   └── upload/
│   │   │       │       ├── init/
│   │   │       │       │   └── route.ts
│   │   │       │       └── status/
│   │   │       │           └── route.ts
│   │   │       ├── restore/
│   │   │       │   └── route.ts
│   │   │       └── status/
│   │   │           ├── helpers.ts
│   │   │           └── route.ts
│   │   ├── auth/
│   │   │   └── [...all]/
│   │   │       └── route.ts
│   │   ├── config/
│   │   │   └── route.ts
│   │   ├── events/
│   │   │   └── route.ts
│   │   ├── files/
│   │   │   ├── backups/
│   │   │   │   └── route.ts
│   │   │   └── images/
│   │   │       └── [fileName]/
│   │   │           └── route.ts
│   │   ├── google/
│   │   │   └── drive/
│   │   │       └── callback/
│   │   │           └── route.ts
│   │   └── tus/
│   │       └── hooks/
│   │           └── route.ts
│   ├── error/
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   ├── manifest.json
│   ├── not-found.tsx
│   └── providers.tsx
├── components.json
├── docker/
│   ├── dockerfile/
│   │   └── Dockerfile
│   ├── entrypoints/
│   │   ├── app-dev-entrypoint.sh
│   │   └── app-prod-entrypoint.sh
│   └── nginx/
│       └── nginx.conf
├── docker-compose.e2e.yml
├── docker-compose.func.yml
├── docker-compose.prod.yml
├── docker-compose.yml
├── drizzle.config.ts
├── e2e/
│   ├── access-management.spec.ts
│   ├── agent.spec.ts
│   ├── auth.spec.ts
│   ├── cleanup.spec.ts
│   ├── helpers/
│   │   ├── access-management.ts
│   │   ├── agent-cli.ts
│   │   ├── agent.ts
│   │   ├── auth.ts
│   │   ├── env.ts
│   │   ├── notification.ts
│   │   ├── project.ts
│   │   ├── session.ts
│   │   └── storage.ts
│   ├── notification/
│   │   ├── discord.spec.ts
│   │   ├── gotify.spec.ts
│   │   ├── ntfy.spec.ts
│   │   ├── slack.spec.ts
│   │   ├── smtp.spec.ts
│   │   ├── teams.spec.ts
│   │   ├── telegram.spec.ts
│   │   └── webhook.spec.ts
│   ├── project.spec.ts
│   ├── setup.spec.ts
│   └── storage/
│       ├── azure.spec.ts
│       ├── gcs.spec.ts
│       ├── google-drive.spec.ts
│       └── s3.spec.ts
├── eslint.config.mjs
├── helm/
│   ├── .helmignore
│   ├── Chart.yaml
│   ├── README.md
│   ├── templates/
│   │   ├── deployment.yaml
│   │   ├── pvc.yaml
│   │   └── service.yaml
│   └── values.yaml
├── instrumentation.ts
├── next.config.ts
├── package.json
├── playwright.config.ts
├── pnpm-workspace.yaml
├── portabase.config.ts
├── postcss.config.mjs
├── proxy.ts
├── seeds/
│   └── keycloak/
│       └── master-realm.json
├── src/
│   ├── components/
│   │   ├── emails/
│   │   │   ├── auth/
│   │   │   │   ├── email-forgot-password.tsx
│   │   │   │   ├── email-new-login.tsx
│   │   │   │   └── email-verification.tsx
│   │   │   ├── email-create-user.tsx
│   │   │   ├── email-layout.tsx
│   │   │   ├── email-notification.tsx
│   │   │   ├── email-settings-test.tsx
│   │   │   └── email-text.tsx
│   │   ├── layout.tsx
│   │   ├── ui/
│   │   │   ├── accordion.tsx
│   │   │   ├── alert-dialog.tsx
│   │   │   ├── alert.tsx
│   │   │   ├── aspect-ratio.tsx
│   │   │   ├── avatar.tsx
│   │   │   ├── badge.tsx
│   │   │   ├── breadcrumb.tsx
│   │   │   ├── button.tsx
│   │   │   ├── calendar.tsx
│   │   │   ├── card.tsx
│   │   │   ├── carousel.tsx
│   │   │   ├── chart.tsx
│   │   │   ├── checkbox.tsx
│   │   │   ├── collapsible.tsx
│   │   │   ├── command.tsx
│   │   │   ├── context-menu.tsx
│   │   │   ├── dialog.tsx
│   │   │   ├── drawer.tsx
│   │   │   ├── dropdown-menu.tsx
│   │   │   ├── dropzone.tsx
│   │   │   ├── form.tsx
│   │   │   ├── github-button.tsx
│   │   │   ├── hover-card.tsx
│   │   │   ├── input-otp.tsx
│   │   │   ├── input.tsx
│   │   │   ├── label.tsx
│   │   │   ├── menubar.tsx
│   │   │   ├── navigation-menu.tsx
│   │   │   ├── pagination.tsx
│   │   │   ├── password-input-indicator.tsx
│   │   │   ├── password-input.tsx
│   │   │   ├── popover.tsx
│   │   │   ├── progress.tsx
│   │   │   ├── radio-group.tsx
│   │   │   ├── resizable.tsx
│   │   │   ├── scroll-area.tsx
│   │   │   ├── search-input.tsx
│   │   │   ├── select.tsx
│   │   │   ├── separator.tsx
│   │   │   ├── sheet.tsx
│   │   │   ├── sidebar.tsx
│   │   │   ├── skeleton.tsx
│   │   │   ├── slider.tsx
│   │   │   ├── sliding-number.tsx
│   │   │   ├── sonner.tsx
│   │   │   ├── switch.tsx
│   │   │   ├── table.tsx
│   │   │   ├── tabs.tsx
│   │   │   ├── textarea.tsx
│   │   │   ├── toast.tsx
│   │   │   ├── toaster.tsx
│   │   │   ├── toggle-group.tsx
│   │   │   ├── toggle.tsx
│   │   │   └── tooltip.tsx
│   │   └── wrappers/
│   │       ├── auth/
│   │       │   ├── auth-logo-section.tsx
│   │       │   ├── guard/
│   │       │   │   └── guard-form.tsx
│   │       │   ├── login/
│   │       │   │   ├── forgot-password-form/
│   │       │   │   │   ├── forgot-password-form.schema.ts
│   │       │   │   │   ├── forgot-password-form.tsx
│   │       │   │   │   └── forgot-password.actions.ts
│   │       │   │   ├── login-form/
│   │       │   │   │   ├── login-form.schema.ts
│   │       │   │   │   └── login-form.tsx
│   │       │   │   └── reset-password-form/
│   │       │   │       ├── reset-password-form.action.ts
│   │       │   │       ├── reset-password-form.schema.ts
│   │       │   │       └── reset-password-form.tsx
│   │       │   ├── register/
│   │       │   │   └── register-form/
│   │       │   │       ├── register-form.schema.ts
│   │       │   │       └── register-form.tsx
│   │       │   ├── reset-password/
│   │       │   │   ├── reset-password-form.tsx
│   │       │   │   ├── reset-password-schema.ts
│   │       │   │   └── reset-password-section.tsx
│   │       │   └── social-buttons.tsx
│   │       ├── common/
│   │       │   ├── bread-crumbs/
│   │       │   │   └── bread-crumbs.tsx
│   │       │   ├── button/
│   │       │   │   ├── back-button.tsx
│   │       │   │   ├── button-with-confirm.tsx
│   │       │   │   ├── button-with-loading.tsx
│   │       │   │   └── copy-button.tsx
│   │       │   ├── cards-with-pagination.tsx
│   │       │   ├── code-snippet.tsx
│   │       │   ├── combobox.tsx
│   │       │   ├── connection-indicator.tsx
│   │       │   ├── console-silencer.tsx
│   │       │   ├── day-time-picker.tsx
│   │       │   ├── dialog.tsx
│   │       │   ├── dropzone/
│   │       │   │   └── dropzone-file.tsx
│   │       │   ├── empty-state-placeholder.tsx
│   │       │   ├── error-layout.tsx
│   │       │   ├── file-uploader.tsx
│   │       │   ├── github/
│   │       │   │   └── github-button.tsx
│   │       │   ├── loading/
│   │       │   │   └── loading-spinner.tsx
│   │       │   ├── multiselect/
│   │       │   │   └── multi-select.tsx
│   │       │   ├── pagination/
│   │       │   │   ├── pagination-indexes.tsx
│   │       │   │   ├── pagination-navigation.tsx
│   │       │   │   └── pagination-size.tsx
│   │       │   ├── provider-switch.tsx
│   │       │   ├── status-badge.tsx
│   │       │   ├── table/
│   │       │   │   ├── data-table.tsx
│   │       │   │   ├── filters.tsx
│   │       │   │   ├── table-pagination-navigation.tsx
│   │       │   │   ├── table-pagination-size.tsx
│   │       │   │   ├── table-pagination.tsx
│   │       │   │   └── table-sort-button.tsx
│   │       │   └── tooltip-custom.tsx
│   │       └── dashboard/
│   │           ├── admin/
│   │           │   ├── channels/
│   │           │   │   ├── channel/
│   │           │   │   │   ├── channel-add-edit-modal.tsx
│   │           │   │   │   ├── channel-card/
│   │           │   │   │   │   ├── button-delete-channel.tsx
│   │           │   │   │   │   ├── button-edit-channel.tsx
│   │           │   │   │   │   └── channel-card.tsx
│   │           │   │   │   └── channel-form/
│   │           │   │   │       ├── channel-form.schema.ts
│   │           │   │   │       ├── channel-form.tsx
│   │           │   │   │       ├── channel-test-button.tsx
│   │           │   │   │       └── providers/
│   │           │   │   │           ├── notifications/
│   │           │   │   │           │   ├── action.ts
│   │           │   │   │           │   └── forms/
│   │           │   │   │           │       ├── discord.form.tsx
│   │           │   │   │           │       ├── discord.schema.ts
│   │           │   │   │           │       ├── gotify.form.tsx
│   │           │   │   │           │       ├── gotify.schema.ts
│   │           │   │   │           │       ├── ntfy.form.tsx
│   │           │   │   │           │       ├── ntfy.schema.ts
│   │           │   │   │           │       ├── slack.form.tsx
│   │           │   │   │           │       ├── slack.schema.ts
│   │           │   │   │           │       ├── smtp.form.tsx
│   │           │   │   │           │       ├── smtp.schema.ts
│   │           │   │   │           │       ├── telegram.form.tsx
│   │           │   │   │           │       ├── telegram.schema.ts
│   │           │   │   │           │       ├── webhook.form.tsx
│   │           │   │   │           │       └── webhook.schema.ts
│   │           │   │   │           └── storages/
│   │           │   │   │               ├── action.ts
│   │           │   │   │               └── forms/
│   │           │   │   │                   ├── google-drive/
│   │           │   │   │                   │   └── helpers.ts
│   │           │   │   │                   ├── google-drive.form.tsx
│   │           │   │   │                   ├── google-drive.schema.ts
│   │           │   │   │                   ├── local.schema.ts
│   │           │   │   │                   ├── s3.form.tsx
│   │           │   │   │                   └── s3.schema.ts
│   │           │   │   ├── channels-section.tsx
│   │           │   │   ├── helpers/
│   │           │   │   │   ├── common.tsx
│   │           │   │   │   ├── notification.tsx
│   │           │   │   │   └── storage.tsx
│   │           │   │   └── organization/
│   │           │   │       ├── channels-organization-form.tsx
│   │           │   │       ├── channels-organization.action.ts
│   │           │   │       └── channels-organization.schema.ts
│   │           │   ├── notifications/
│   │           │   │   └── logs/
│   │           │   │       ├── columns.tsx
│   │           │   │       ├── notification-log-modal.tsx
│   │           │   │       └── notification-logs-list.tsx
│   │           │   ├── organizations/
│   │           │   │   ├── admin-organizations-table.tsx
│   │           │   │   ├── columns-organizations.tsx
│   │           │   │   └── organization/
│   │           │   │       ├── admin-organization-add-modal.tsx
│   │           │   │       ├── admin-organization-form.tsx
│   │           │   │       ├── admin-organization-section.tsx
│   │           │   │       ├── admin-orgnization-list.tsx
│   │           │   │       ├── button-delete-organization.tsx
│   │           │   │       ├── details/
│   │           │   │       │   ├── add-member.action.ts
│   │           │   │       │   ├── organization-add-member-form.tsx
│   │           │   │       │   ├── organization-add-member-modal.tsx
│   │           │   │       │   ├── organization-delete-member-modal.tsx
│   │           │   │       │   ├── organization-member-card.tsx
│   │           │   │       │   ├── organization-member-change-role.tsx
│   │           │   │       │   ├── role-member.action.ts
│   │           │   │       │   └── update-organization-form.tsx
│   │           │   │       ├── organization-management.tsx
│   │           │   │       ├── organization.schema.ts
│   │           │   │       └── table-colums.tsx
│   │           │   ├── settings/
│   │           │   │   ├── email/
│   │           │   │   │   ├── email-form/
│   │           │   │   │   │   ├── email-form.action.ts
│   │           │   │   │   │   ├── email-form.schema.ts
│   │           │   │   │   │   └── email-form.tsx
│   │           │   │   │   └── settings-email-section.tsx
│   │           │   │   ├── notification/
│   │           │   │   │   ├── settings-notification-section.tsx
│   │           │   │   │   ├── settings-notification.action.ts
│   │           │   │   │   └── settings-notification.schema.ts
│   │           │   │   ├── settings-tabs.tsx
│   │           │   │   └── storage/
│   │           │   │       ├── settings-storage-section.tsx
│   │           │   │       ├── settings-storage.action.ts
│   │           │   │       ├── settings-storage.schema.ts
│   │           │   │       └── storage-s3/
│   │           │   │           ├── s3-form.action.ts
│   │           │   │           ├── s3-form.schema.ts
│   │           │   │           └── storage-s3-form.tsx
│   │           │   └── users/
│   │           │       ├── admin-user-add-modal.tsx
│   │           │       ├── admin-user-change-password-modal.tsx
│   │           │       ├── admin-user-change-role-modal.tsx
│   │           │       ├── admin-user-delete-modal.tsx
│   │           │       ├── admin-user-edit-form.tsx
│   │           │       ├── admin-user-edit-modal.tsx
│   │           │       ├── admin-user-form.tsx
│   │           │       ├── admin-user-list.tsx
│   │           │       ├── table-colums.tsx
│   │           │       ├── user-actions-cell.tsx
│   │           │       ├── user.action.ts
│   │           │       └── user.schema.ts
│   │           ├── agent/
│   │           │   ├── agent-card/
│   │           │   │   └── agent-card.tsx
│   │           │   ├── agent-card-key/
│   │           │   │   └── agent-card-key.tsx
│   │           │   ├── agent-content.tsx
│   │           │   ├── agent-database-card.tsx
│   │           │   ├── agent-database-columns.tsx
│   │           │   ├── agent-modal-key/
│   │           │   │   └── agent-modal-key.tsx
│   │           │   └── button-delete-agent/
│   │           │       ├── button-delete-agent.tsx
│   │           │       └── delete-agent.action.ts
│   │           ├── backup/
│   │           │   └── backup-button/
│   │           │       ├── backup-button.action.ts
│   │           │       └── backup-button.tsx
│   │           ├── common/
│   │           │   ├── logged-in/
│   │           │   │   ├── logged-in-button.server.tsx
│   │           │   │   ├── logged-in-button.tsx
│   │           │   │   └── logged-in-dropdown.tsx
│   │           │   ├── profile/
│   │           │   │   ├── profile-modal.tsx
│   │           │   │   └── profile-sidebar.tsx
│   │           │   └── sidebar/
│   │           │       ├── app-sidebar.tsx
│   │           │       ├── logo-sidebar.tsx
│   │           │       ├── menu-sidebar-main.tsx
│   │           │       ├── menu-sidebar.tsx
│   │           │       ├── side-bar-footer-credit.tsx
│   │           │       └── side-bar-logo.tsx
│   │           ├── database/
│   │           │   ├── backup/
│   │           │   │   ├── actions/
│   │           │   │   │   ├── backup-actions-cell.tsx
│   │           │   │   │   ├── backup-actions-form.tsx
│   │           │   │   │   ├── backup-actions-modal.tsx
│   │           │   │   │   ├── backup-actions.action.ts
│   │           │   │   │   ├── backup-actions.schema.ts
│   │           │   │   │   └── get-data.action.ts
│   │           │   │   └── backup-modal-context.tsx
│   │           │   ├── channels-policy/
│   │           │   │   ├── policy-form.tsx
│   │           │   │   ├── policy-modal.tsx
│   │           │   │   ├── policy.action.ts
│   │           │   │   └── policy.schema.ts
│   │           │   ├── cron-button/
│   │           │   │   ├── advanced-cron-select.tsx
│   │           │   │   ├── cron-button.tsx
│   │           │   │   ├── cron-input.tsx
│   │           │   │   └── cron.action.ts
│   │           │   ├── database-form/
│   │           │   │   ├── database-form.tsx
│   │           │   │   ├── form-database.action.ts
│   │           │   │   └── form-database.schema.ts
│   │           │   ├── health/
│   │           │   │   └── health-modal.tsx
│   │           │   ├── import/
│   │           │   │   ├── import-modal.tsx
│   │           │   │   ├── upload-backup-zone.tsx
│   │           │   │   └── upload-backup.action.ts
│   │           │   ├── restore-form.schema.ts
│   │           │   ├── restore-form.tsx
│   │           │   └── retention-policy/
│   │           │       ├── backup-retention-settings-form.tsx
│   │           │       ├── backup-retention-settings.action.tsx
│   │           │       ├── backup-retention-settings.schema.ts
│   │           │       ├── backup-retention-settings.tsx
│   │           │       └── retention-policy-sheet.tsx
│   │           ├── health/
│   │           │   └── heath-grid.tsx
│   │           ├── organization/
│   │           │   ├── create-organisation-modal.tsx
│   │           │   ├── delete-organization-button.tsx
│   │           │   ├── migration/
│   │           │   │   ├── migration-flow.tsx
│   │           │   │   ├── migration-tool.tsx
│   │           │   │   ├── migration.action.ts
│   │           │   │   ├── source-panel.tsx
│   │           │   │   └── target-panel.tsx
│   │           │   ├── organization-combobox.tsx
│   │           │   ├── settings/
│   │           │   │   ├── columns-organization-members.tsx
│   │           │   │   ├── member.schema.ts
│   │           │   │   ├── settings-organization-members-table.tsx
│   │           │   │   └── update-member.action.ts
│   │           │   └── tabs/
│   │           │       ├── organization-channels-tab/
│   │           │       │   ├── organization-agents-tab.tsx
│   │           │       │   ├── organization-notifiers-tab.tsx
│   │           │       │   └── organization-storages-tab.tsx
│   │           │       └── organization-tabs.tsx
│   │           ├── profile/
│   │           │   ├── actions/
│   │           │   │   ├── avatar.action.ts
│   │           │   │   ├── profile.action.ts
│   │           │   │   ├── provider.action.ts
│   │           │   │   └── security.action.ts
│   │           │   ├── components/
│   │           │   │   ├── avatar-with-upload.tsx
│   │           │   │   └── backup-codes-list.tsx
│   │           │   ├── form/
│   │           │   │   ├── 2fa-form.tsx
│   │           │   │   ├── 2fa.schema.ts
│   │           │   │   ├── reset-password-form.tsx
│   │           │   │   └── set-password-form.tsx
│   │           │   ├── modal/
│   │           │   │   ├── disable-2fa-modal.tsx
│   │           │   │   ├── reset-password-modal.tsx
│   │           │   │   ├── set-password-modal.tsx
│   │           │   │   ├── setup-2fa-modal.tsx
│   │           │   │   └── view-backup-codes-modal.tsx
│   │           │   ├── profile-account.tsx
│   │           │   ├── profile-apperance.tsx
│   │           │   ├── profile-general.tsx
│   │           │   ├── profile-providers.tsx
│   │           │   ├── profile-security.tsx
│   │           │   └── schemas/
│   │           │       ├── account.schema.ts
│   │           │       ├── general.schema.ts
│   │           │       ├── provider.schema.ts
│   │           │       └── security.schema.ts
│   │           ├── projects/
│   │           │   ├── button-delete-project/
│   │           │   │   ├── button-delete-project.tsx
│   │           │   │   └── delete-project.action.ts
│   │           │   ├── database/
│   │           │   │   ├── database-backup-list.tsx
│   │           │   │   ├── database-content.tsx
│   │           │   │   ├── database-kpi.tsx
│   │           │   │   ├── database-restore-list.tsx
│   │           │   │   └── database-tabs.tsx
│   │           │   └── project-card/
│   │           │       ├── project-card.tsx
│   │           │       └── project-database-card.tsx
│   │           └── statistics/
│   │               └── charts/
│   │                   ├── evolution-line-chart.tsx
│   │                   ├── fake-data.ts
│   │                   ├── line-chart.tsx
│   │                   ├── percentage-line-chart.tsx
│   │                   └── utils/
│   │                       └── placeholder.tsx
│   ├── db/
│   │   ├── index.ts
│   │   ├── migrations/
│   │   │   ├── 0000_awesome_nomad.sql
│   │   │   ├── 0001_wealthy_leo.sql
│   │   │   ├── 0002_pink_groot.sql
│   │   │   ├── 0003_absent_maestro.sql
│   │   │   ├── 0004_dazzling_hawkeye.sql
│   │   │   ├── 0005_old_swarm.sql
│   │   │   ├── 0006_moaning_pete_wisdom.sql
│   │   │   ├── 0007_last_umar.sql
│   │   │   ├── 0008_aberrant_scorpion.sql
│   │   │   ├── 0009_lucky_edwin_jarvis.sql
│   │   │   ├── 0010_past_trauma.sql
│   │   │   ├── 0011_outgoing_blob.sql
│   │   │   ├── 0012_peaceful_leopardon.sql
│   │   │   ├── 0013_past_logan.sql
│   │   │   ├── 0014_strong_galactus.sql
│   │   │   ├── 0015_absurd_next_avengers.sql
│   │   │   ├── 0016_broken_morgan_stark.sql
│   │   │   ├── 0017_wild_purple_man.sql
│   │   │   ├── 0018_smiling_mole_man.sql
│   │   │   ├── 0019_overjoyed_butterfly.sql
│   │   │   ├── 0020_low_giant_girl.sql
│   │   │   ├── 0020_thankful_sunspot.sql
│   │   │   ├── 0021_soft_blockbuster.sql
│   │   │   ├── 0022_purple_retro_girl.sql
│   │   │   ├── 0023_common_the_captain.sql
│   │   │   ├── 0024_lush_blindfold.sql
│   │   │   ├── 0025_past_franklin_richards.sql
│   │   │   ├── 0026_storage-backend.sql
│   │   │   ├── 0027_special_the_santerians.sql
│   │   │   ├── 0028_graceful_ben_parker.sql
│   │   │   ├── 0029_lowly_white_tiger.sql
│   │   │   ├── 0030_dizzy_morlocks.sql
│   │   │   ├── 0031_chemical_edwin_jarvis.sql
│   │   │   ├── 0032_sparkling_thunderbolt_ross.sql
│   │   │   ├── 0033_handy_valeria_richards.sql
│   │   │   ├── 0034_lush_speed.sql
│   │   │   ├── 0035_late_young_avengers.sql
│   │   │   ├── 0036_chief_night_thrasher.sql
│   │   │   ├── 0037_neat_talon.sql
│   │   │   ├── 0038_misty_red_hulk.sql
│   │   │   ├── 0039_conscious_solo.sql
│   │   │   ├── 0040_quick_lester.sql
│   │   │   ├── 0041_spooky_radioactive_man.sql
│   │   │   ├── 0042_breezy_namora.sql
│   │   │   ├── 0043_peaceful_chat.sql
│   │   │   ├── 0044_steep_wiccan.sql
│   │   │   ├── 0045_needy_martin_li.sql
│   │   │   ├── 0046_mysterious_menace.sql
│   │   │   ├── 0047_wet_carnage.sql
│   │   │   ├── 0048_yellow_eddie_brock.sql
│   │   │   ├── 0049_chief_terrax.sql
│   │   │   ├── 0050_dark_saracen.sql
│   │   │   ├── 0051_young_senator_kelly.sql
│   │   │   ├── 0052_cute_punisher.sql
│   │   │   ├── 0053_lyrical_union_jack.sql
│   │   │   └── meta/
│   │   │       ├── 0000_snapshot.json
│   │   │       ├── 0001_snapshot.json
│   │   │       ├── 0002_snapshot.json
│   │   │       ├── 0003_snapshot.json
│   │   │       ├── 0004_snapshot.json
│   │   │       ├── 0005_snapshot.json
│   │   │       ├── 0006_snapshot.json
│   │   │       ├── 0007_snapshot.json
│   │   │       ├── 0008_snapshot.json
│   │   │       ├── 0009_snapshot.json
│   │   │       ├── 0010_snapshot.json
│   │   │       ├── 0011_snapshot.json
│   │   │       ├── 0012_snapshot.json
│   │   │       ├── 0013_snapshot.json
│   │   │       ├── 0014_snapshot.json
│   │   │       ├── 0015_snapshot.json
│   │   │       ├── 0016_snapshot.json
│   │   │       ├── 0017_snapshot.json
│   │   │       ├── 0018_snapshot.json
│   │   │       ├── 0019_snapshot.json
│   │   │       ├── 0020_snapshot.json
│   │   │       ├── 0021_snapshot.json
│   │   │       ├── 0022_snapshot.json
│   │   │       ├── 0023_snapshot.json
│   │   │       ├── 0024_snapshot.json
│   │   │       ├── 0025_snapshot.json
│   │   │       ├── 0026_snapshot.json
│   │   │       ├── 0027_snapshot.json
│   │   │       ├── 0028_snapshot.json
│   │   │       ├── 0029_snapshot.json
│   │   │       ├── 0030_snapshot.json
│   │   │       ├── 0031_snapshot.json
│   │   │       ├── 0032_snapshot.json
│   │   │       ├── 0033_snapshot.json
│   │   │       ├── 0034_snapshot.json
│   │   │       ├── 0035_snapshot.json
│   │   │       ├── 0036_snapshot.json
│   │   │       ├── 0037_snapshot.json
│   │   │       ├── 0038_snapshot.json
│   │   │       ├── 0039_snapshot.json
│   │   │       ├── 0040_snapshot.json
│   │   │       ├── 0041_snapshot.json
│   │   │       ├── 0042_snapshot.json
│   │   │       ├── 0043_snapshot.json
│   │   │       ├── 0044_snapshot.json
│   │   │       ├── 0045_snapshot.json
│   │   │       ├── 0046_snapshot.json
│   │   │       ├── 0047_snapshot.json
│   │   │       ├── 0048_snapshot.json
│   │   │       ├── 0049_snapshot.json
│   │   │       ├── 0050_snapshot.json
│   │   │       ├── 0051_snapshot.json
│   │   │       ├── 0052_snapshot.json
│   │   │       ├── 0053_snapshot.json
│   │   │       └── _journal.json
│   │   ├── schema/
│   │   │   ├── 00_common.ts
│   │   │   ├── 01_setting.ts
│   │   │   ├── 02_user.ts
│   │   │   ├── 03_organization.ts
│   │   │   ├── 04_member.ts
│   │   │   ├── 05_invitation.ts
│   │   │   ├── 06_project.ts
│   │   │   ├── 07_database.ts
│   │   │   ├── 08_agent.ts
│   │   │   ├── 09_notification-channel.ts
│   │   │   ├── 10_alert-policy.ts
│   │   │   ├── 11_notification-log.ts
│   │   │   ├── 12_storage-channel.ts
│   │   │   ├── 13_storage-policy.ts
│   │   │   ├── 14_storage-backup.ts
│   │   │   ├── 15_healthcheck-log.ts
│   │   │   └── types.ts
│   │   ├── services/
│   │   │   ├── agent.ts
│   │   │   ├── backup.ts
│   │   │   ├── database.ts
│   │   │   ├── healthcheck.ts
│   │   │   ├── notification-channel.ts
│   │   │   ├── notification-log.ts
│   │   │   ├── storage-channel.ts
│   │   │   └── user.ts
│   │   └── utils/
│   │       └── index.ts
│   ├── env.mjs
│   ├── features/
│   │   ├── agents/
│   │   │   ├── agents.action.ts
│   │   │   ├── agents.schema.ts
│   │   │   ├── components/
│   │   │   │   ├── agent-organizations.action.ts
│   │   │   │   ├── agent-organizations.form.tsx
│   │   │   │   ├── agent-organizations.schema.ts
│   │   │   │   ├── agent.dialog.tsx
│   │   │   │   └── agent.form.tsx
│   │   │   └── hooks/
│   │   │       └── use-agent-update-check.ts
│   │   ├── browser/
│   │   │   ├── theme-meta-updater-root.tsx
│   │   │   └── theme-meta-updater.tsx
│   │   ├── dashboard/
│   │   │   ├── backup/
│   │   │   │   └── columns.tsx
│   │   │   ├── organization-cookie.ts
│   │   │   └── restore/
│   │   │       ├── columns.tsx
│   │   │       └── restore.action.ts
│   │   ├── keys/
│   │   │   └── keys.action.ts
│   │   ├── layout/
│   │   │   ├── Header.tsx
│   │   │   ├── card-auth.tsx
│   │   │   └── page.tsx
│   │   ├── notifications/
│   │   │   ├── dispatch.ts
│   │   │   ├── helpers.ts
│   │   │   ├── providers/
│   │   │   │   ├── discord.ts
│   │   │   │   ├── gotify.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── ntfy.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── smtp.ts
│   │   │   │   ├── telegram.ts
│   │   │   │   └── webhook.ts
│   │   │   └── types.ts
│   │   ├── organization/
│   │   │   ├── components/
│   │   │   │   ├── edit-organization.dialog.tsx
│   │   │   │   └── organization.form.tsx
│   │   │   ├── organization.action.ts
│   │   │   └── organization.schema.ts
│   │   ├── projects/
│   │   │   ├── components/
│   │   │   │   ├── project.dialog.tsx
│   │   │   │   └── project.form.tsx
│   │   │   ├── projects.action.ts
│   │   │   └── projects.schema.ts
│   │   ├── shared/
│   │   │   └── event.ts
│   │   ├── storages/
│   │   │   ├── dispatch.ts
│   │   │   ├── helpers.ts
│   │   │   ├── providers/
│   │   │   │   ├── google-drive/
│   │   │   │   │   ├── helpers.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── local.ts
│   │   │   │   └── s3.ts
│   │   │   └── types.ts
│   │   ├── theme/
│   │   │   ├── mode-toggle.tsx
│   │   │   └── theme-provider.tsx
│   │   ├── updates/
│   │   │   ├── components/
│   │   │   │   └── update-notification.tsx
│   │   │   ├── hooks/
│   │   │   │   └── use-update-check.ts
│   │   │   └── services/
│   │   │       └── github.ts
│   │   └── upload/
│   │       └── public/
│   │           └── upload.action.ts
│   ├── fonts/
│   │   └── fonts.ts
│   ├── hooks/
│   │   ├── use-mobile.ts
│   │   ├── use-mobile.tsx
│   │   └── use-organization-permissions.ts
│   ├── lib/
│   │   ├── acl/
│   │   │   └── organization-acl.ts
│   │   ├── auth/
│   │   │   ├── auth-client.ts
│   │   │   ├── auth.ts
│   │   │   ├── config.ts
│   │   │   ├── current-user.ts
│   │   │   ├── oauth.ts
│   │   │   ├── oidc.ts
│   │   │   └── permissions.ts
│   │   ├── email/
│   │   │   ├── helpers.ts
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   ├── logger.ts
│   │   ├── safe-actions/
│   │   │   └── actions.ts
│   │   ├── services.ts
│   │   ├── tasks/
│   │   │   ├── cleaning/
│   │   │   │   └── index.ts
│   │   │   ├── database/
│   │   │   │   ├── index.ts
│   │   │   │   ├── retention-count.ts
│   │   │   │   ├── retention-days.ts
│   │   │   │   ├── retention-gsf.ts
│   │   │   │   └── utils/
│   │   │   │       └── delete.ts
│   │   │   └── index.ts
│   │   ├── twx.tsx
│   │   ├── utils.ts
│   │   └── zod.ts
│   ├── middleware/
│   │   ├── errorHandler.ts
│   │   └── loggingMiddleware.ts
│   ├── types/
│   │   ├── action-type.ts
│   │   ├── auth.ts
│   │   ├── common.ts
│   │   └── next.ts
│   └── utils/
│       ├── common.ts
│       ├── cron.ts
│       ├── date-formatting.ts
│       ├── detection.ts
│       ├── edge_key.ts
│       ├── get-server-url.ts
│       ├── init.ts
│       ├── mock-data.ts
│       ├── name-from-email.ts
│       ├── os-parser.ts
│       ├── password.ts
│       ├── rsa-keys.ts
│       ├── slugify.ts
│       ├── text.ts
│       └── verify-uuid.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (920 symbols across 409 files)

FILE: app/(auth)/forgot-password/page.tsx
  function RoutePage (line 9) | async function RoutePage(props: { searchParams: Promise<{ callbackUrl: s...

FILE: app/(auth)/guard/page.tsx
  function GuardPage (line 8) | async function GuardPage() {

FILE: app/(auth)/layout.tsx
  function Layout (line 7) | async function Layout({

FILE: app/(auth)/login/page.tsx
  function SignInPage (line 16) | async function SignInPage() {

FILE: app/(auth)/register/page.tsx
  function RoutePage (line 11) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(auth)/reset-password/page.tsx
  function RoutePage (line 10) | async function RoutePage(props: { searchParams: Promise<{ token: string ...

FILE: app/(customer)/dashboard/(admin)/admin/organizations/[organizationId]/page.tsx
  function RoutePage (line 17) | async function RoutePage(props: PageParams<{ organizationId: string }>) {

FILE: app/(customer)/dashboard/(admin)/admin/organizations/page.tsx
  function RoutePage (line 10) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(admin)/admin/settings/page.tsx
  function RoutePage (line 11) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(admin)/admin/users/page.tsx
  function RoutePage (line 9) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(admin)/agents/[agentId]/page.tsx
  function RoutePage (line 20) | async function RoutePage(

FILE: app/(customer)/dashboard/(admin)/agents/page.tsx
  function RoutePage (line 16) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(admin)/layout.tsx
  function Layout (line 5) | async function Layout({ children }: { children: React.ReactNode }) {

FILE: app/(customer)/dashboard/(admin)/notifications/channels/page.tsx
  function RoutePage (line 15) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(admin)/notifications/logs/page.tsx
  function RoutePage (line 12) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(admin)/storages/channels/page.tsx
  function RoutePage (line 15) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(organization)/migration/page.tsx
  function RoutePage (line 13) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(organization)/projects/[projectId]/database/[databaseId]/page.tsx
  function RoutePage (line 13) | async function RoutePage(props: PageParams<{

FILE: app/(customer)/dashboard/(organization)/projects/[projectId]/page.tsx
  function RoutePage (line 20) | async function RoutePage(props: PageParams<{

FILE: app/(customer)/dashboard/(organization)/projects/page.tsx
  function RoutePage (line 18) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/(organization)/settings/agents/[agentId]/page.tsx
  function RoutePage (line 23) | async function RoutePage(

FILE: app/(customer)/dashboard/(organization)/settings/agents/page.tsx
  function RoutePage (line 3) | async function RoutePage() {

FILE: app/(customer)/dashboard/(organization)/settings/page.tsx
  function RoutePage (line 29) | async function RoutePage(props: PageParams<{ slug: string }>) {

FILE: app/(customer)/dashboard/(organization)/statistics/page.tsx
  function RoutePage (line 18) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/home/page.tsx
  function RoutePage (line 17) | async function RoutePage(props: PageParams<{}>) {

FILE: app/(customer)/dashboard/layout.tsx
  function Layout (line 10) | async function Layout({ children }: { children: ReactNode }) {

FILE: app/(customer)/dashboard/loading.tsx
  function Loading (line 3) | function Loading() {

FILE: app/(landing)/home/page.tsx
  function Home (line 1) | function Home() {

FILE: app/(landing)/page.tsx
  function Index (line 5) | async function Index() {

FILE: app/api/agent/[agentId]/backup/helpers.ts
  function withAgentCheck (line 9) | function withAgentCheck(handler: Function) {
  function getDatabaseOrThrow (line 36) | async function getDatabaseOrThrow(generatedId: string) {

FILE: app/api/agent/[agentId]/backup/route.ts
  type BodyPost (line 15) | type BodyPost = {
  type BodyPatch (line 20) | type BodyPatch = {
  constant POST (line 27) | const POST = withAgentCheck(async (request: Request, {params, agent}: {
  constant PATCH (line 101) | const PATCH = withAgentCheck(async (request: Request, {params, agent}: {

FILE: app/api/agent/[agentId]/backup/upload/init/route.ts
  type Body (line 12) | type Body = {
  constant POST (line 17) | const POST = withAgentCheck(async (request: Request, {params, agent}: {

FILE: app/api/agent/[agentId]/backup/upload/status/route.ts
  type Body (line 12) | type Body = {
  constant PATCH (line 20) | const PATCH = withAgentCheck(async (request: Request, {params, agent}: {

FILE: app/api/agent/[agentId]/restore/route.ts
  type BodyResultRestore (line 12) | type BodyResultRestore = {
  type RestorationStatus (line 16) | type RestorationStatus = 'waiting' | 'ongoing' | 'failed' | 'success';
  function POST (line 19) | async function POST(

FILE: app/api/agent/[agentId]/status/helpers.ts
  function handleDatabases (line 18) | async function handleDatabases(body: Body, agent: Agent, lastContact: Da...
  type PingDatabaseStorageChannels (line 215) | type PingDatabaseStorageChannels = {
  function getDatabaseStorageChannels (line 221) | async function getDatabaseStorageChannels(databaseId: string): Promise<P...

FILE: app/api/agent/[agentId]/status/route.ts
  type databaseAgent (line 15) | type databaseAgent = {
  type Body (line 22) | type Body = {
  function POST (line 28) | async function POST(

FILE: app/api/config/route.ts
  function GET (line 3) | async function GET() {

FILE: app/api/events/route.ts
  function GET (line 9) | async function GET(request: Request) {

FILE: app/api/files/backups/route.ts
  function GET (line 10) | async function GET(

FILE: app/api/files/images/[fileName]/route.ts
  function GET (line 11) | async function GET(

FILE: app/api/google/drive/callback/route.ts
  function GET (line 1) | async function GET(request: Request) {

FILE: app/api/tus/hooks/route.ts
  function POST (line 9) | async function POST(request: Request) {

FILE: app/error/page.tsx
  function ErrorPage (line 8) | function ErrorPage() {

FILE: app/layout.tsx
  function RootLayout (line 19) | async function RootLayout({

FILE: app/not-found.tsx
  function NotFound (line 3) | async function NotFound() {

FILE: app/providers.tsx
  type ProviderProps (line 11) | type ProviderProps = PropsWithChildren<{}>;

FILE: e2e/auth.spec.ts
  constant TIMEOUT (line 5) | const TIMEOUT = undefined

FILE: e2e/helpers/access-management.ts
  function getUserRow (line 3) | function getUserRow(page: Page, email: string) {
  function getOrganizationRow (line 7) | function getOrganizationRow(page: Page, organizationName: string) {
  function create (line 22) | async function create(page: Page, entrypoint: "button" | "sidebar", name...
  function switchTo (line 39) | async function switchTo(page: Page, name: string) {
  function switchToDefault (line 49) | async function switchToDefault(page: Page) {
  constant ROLE_LABELS (line 53) | const ROLE_LABELS = {
  function changeUserRole (line 64) | async function changeUserRole(page: Page, role: keyof typeof ROLE_LABELS) {

FILE: e2e/helpers/agent-cli.ts
  type InteractiveStep (line 3) | type InteractiveStep = {
  function normalizeOutput (line 8) | function normalizeOutput(output: string) {
  function runInteractiveCommand (line 21) | async function runInteractiveCommand(
  function createAgentWithDockerDatabases (line 76) | async function createAgentWithDockerDatabases(command: string, cwd: stri...

FILE: e2e/helpers/agent.ts
  function get (line 9) | function get(page: Page, name: string) {
  function create (line 23) | async function create(page: Page, entrypoint: "auto" | "emptyState" | "b...
  function edit (line 44) | async function edit(page: Page, currentName: string, updatedName: string...
  function remove (line 62) | async function remove(page: Page, name: string) {

FILE: e2e/helpers/auth.ts
  type UserCredentials (line 3) | type UserCredentials = {
  function register (line 19) | async function register(page: Page, name: string, email: string, passwor...
  function login (line 33) | async function login(page: Page, email: string, password: string) {
  function logout (line 45) | async function logout(page: Page) {

FILE: e2e/helpers/env.ts
  constant REQUIRED_E2E_ENV_VARS (line 1) | const REQUIRED_E2E_ENV_VARS = [
  function assertRequiredEnvVars (line 40) | function assertRequiredEnvVars() {
  function getEnv (line 55) | function getEnv(name: string): string {

FILE: e2e/helpers/notification.ts
  function get (line 9) | function get(page: Page, channelName: string) {
  function edit (line 20) | async function edit(page: Page, channelName: string) {
  function remove (line 30) | async function remove(page: Page, channelName: string) {
  function create (line 46) | async function create(
  function submit (line 73) | async function submit(page: Page) {
  function testFromEdit (line 82) | async function testFromEdit(page: Page, channelName: string) {
  function cancel (line 92) | async function cancel(page: Page) {

FILE: e2e/helpers/project.ts
  function get (line 9) | function get(page: Page, projectName: string) {
  function create (line 22) | async function create(page: Page, entrypoint: "emptyState" | "button", p...
  function edit (line 38) | async function edit(page: Page, currentName: string, updatedName: string) {
  function remove (line 55) | async function remove(page: Page, projectName: string) {

FILE: e2e/helpers/session.ts
  constant LOCAL_STORAGE_PATH (line 1) | const LOCAL_STORAGE_PATH = "./e2e/local-storage.json";

FILE: e2e/helpers/storage.ts
  function get (line 9) | function get(page: Page, channelName: string) {
  function create (line 25) | async function create(
  function edit (line 52) | async function edit(page: Page, channelName: string) {
  function remove (line 62) | async function remove(page: Page, channelName: string) {
  function testConnection (line 73) | async function testConnection(page: Page) {
  function submit (line 82) | async function submit(page: Page) {
  function testFromEdit (line 91) | async function testFromEdit(page: Page, channelName: string) {
  function cancel (line 101) | async function cancel(page: Page) {

FILE: instrumentation.ts
  function register (line 1) | async function register() {

FILE: next.config.ts
  function buildCSPHeader (line 7) | function buildCSPHeader(): string {
  function buildPermissionsPolicy (line 34) | function buildPermissionsPolicy(): string {
  method rewrites (line 55) | async rewrites() {
  method headers (line 66) | async headers() {

FILE: portabase.config.ts
  constant PORTABASE_DEFAULT_SETTINGS (line 3) | const PORTABASE_DEFAULT_SETTINGS = {

FILE: proxy.ts
  function proxy (line 7) | async function proxy(request: NextRequest) {
  function checkRouteExists (line 62) | function checkRouteExists(pathname: string) {

FILE: src/components/emails/auth/email-forgot-password.tsx
  type EmailCreateUserProps (line 6) | interface EmailCreateUserProps {

FILE: src/components/emails/auth/email-new-login.tsx
  type EmailCreateUserProps (line 5) | interface EmailCreateUserProps {

FILE: src/components/emails/auth/email-verification.tsx
  type EmailCreateUserProps (line 5) | interface EmailCreateUserProps {

FILE: src/components/emails/email-create-user.tsx
  type EmailCreateUserProps (line 7) | interface EmailCreateUserProps {

FILE: src/components/emails/email-notification.tsx
  type EmailNotificationProps (line 53) | interface EmailNotificationProps {

FILE: src/components/ui/accordion.tsx
  function Accordion (line 9) | function Accordion({
  function AccordionItem (line 15) | function AccordionItem({
  function AccordionTrigger (line 28) | function AccordionTrigger({
  function AccordionContent (line 50) | function AccordionContent({

FILE: src/components/ui/alert-dialog.tsx
  function AlertDialog (line 9) | function AlertDialog({
  function AlertDialogTrigger (line 15) | function AlertDialogTrigger({
  function AlertDialogPortal (line 23) | function AlertDialogPortal({
  function AlertDialogOverlay (line 31) | function AlertDialogOverlay({
  function AlertDialogContent (line 47) | function AlertDialogContent({
  function AlertDialogHeader (line 66) | function AlertDialogHeader({
  function AlertDialogFooter (line 79) | function AlertDialogFooter({
  function AlertDialogTitle (line 95) | function AlertDialogTitle({
  function AlertDialogDescription (line 108) | function AlertDialogDescription({
  function AlertDialogAction (line 121) | function AlertDialogAction({
  function AlertDialogCancel (line 133) | function AlertDialogCancel({

FILE: src/components/ui/alert.tsx
  function Alert (line 22) | function Alert({
  function AlertTitle (line 37) | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
  function AlertDescription (line 50) | function AlertDescription({

FILE: src/components/ui/aspect-ratio.tsx
  function AspectRatio (line 5) | function AspectRatio({

FILE: src/components/ui/avatar.tsx
  function Avatar (line 8) | function Avatar({
  function AvatarImage (line 24) | function AvatarImage({
  function AvatarFallback (line 37) | function AvatarFallback({

FILE: src/components/ui/badge.tsx
  function Badge (line 28) | function Badge({

FILE: src/components/ui/breadcrumb.tsx
  function Breadcrumb (line 6) | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
  function BreadcrumbList (line 10) | function BreadcrumbList({ className, ...props }: React.ComponentProps<"o...
  function BreadcrumbItem (line 23) | function BreadcrumbItem({ className, ...props }: React.ComponentProps<"l...
  function BreadcrumbLink (line 33) | function BreadcrumbLink({
  function BreadcrumbPage (line 51) | function BreadcrumbPage({ className, ...props }: React.ComponentProps<"s...
  function BreadcrumbSeparator (line 64) | function BreadcrumbSeparator({
  function BreadcrumbEllipsis (line 82) | function BreadcrumbEllipsis({

FILE: src/components/ui/button.tsx
  type ButtonVariantsProps (line 38) | type ButtonVariantsProps = VariantProps<typeof buttonVariants>;
  function Button (line 40) | function Button({

FILE: src/components/ui/calendar.tsx
  type CalendarProps (line 10) | type CalendarProps = React.ComponentProps<typeof DayPicker>;
  function Calendar (line 12) | function Calendar({ className, classNames, showOutsideDays = true, ...pr...

FILE: src/components/ui/card.tsx
  function Card (line 5) | function Card({ className, ...props }: React.ComponentProps<"div">) {
  function CardHeader (line 18) | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  function CardTitle (line 31) | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  function CardDescription (line 41) | function CardDescription({ className, ...props }: React.ComponentProps<"...
  function CardAction (line 51) | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  function CardContent (line 64) | function CardContent({ className, ...props }: React.ComponentProps<"div"...
  function CardFooter (line 74) | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {

FILE: src/components/ui/carousel.tsx
  type CarouselApi (line 11) | type CarouselApi = UseEmblaCarouselType[1]
  type UseCarouselParameters (line 12) | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
  type CarouselOptions (line 13) | type CarouselOptions = UseCarouselParameters[0]
  type CarouselPlugin (line 14) | type CarouselPlugin = UseCarouselParameters[1]
  type CarouselProps (line 16) | type CarouselProps = {
  type CarouselContextProps (line 23) | type CarouselContextProps = {
  function useCarousel (line 34) | function useCarousel() {
  function Carousel (line 44) | function Carousel({
  function CarouselContent (line 134) | function CarouselContent({ className, ...props }: React.ComponentProps<"...
  function CarouselItem (line 155) | function CarouselItem({ className, ...props }: React.ComponentProps<"div...
  function CarouselPrevious (line 173) | function CarouselPrevious({
  function CarouselNext (line 203) | function CarouselNext({

FILE: src/components/ui/chart.tsx
  constant THEMES (line 9) | const THEMES = { light: "", dark: ".dark" } as const
  type ChartConfig (line 11) | type ChartConfig = {
  type ChartContextProps (line 21) | type ChartContextProps = {
  function useChart (line 27) | function useChart() {
  function ChartContainer (line 37) | function ChartContainer({
  function ChartTooltipContent (line 107) | function ChartTooltipContent({
  function ChartLegendContent (line 253) | function ChartLegendContent({
  function getPayloadConfigFromPayload (line 308) | function getPayloadConfigFromPayload(

FILE: src/components/ui/checkbox.tsx
  function Checkbox (line 9) | function Checkbox({

FILE: src/components/ui/collapsible.tsx
  function Collapsible (line 5) | function Collapsible({
  function CollapsibleTrigger (line 11) | function CollapsibleTrigger({
  function CollapsibleContent (line 22) | function CollapsibleContent({

FILE: src/components/ui/command.tsx
  type CommandDialogProps (line 26) | interface CommandDialogProps extends DialogProps {}

FILE: src/components/ui/context-menu.tsx
  function ContextMenu (line 9) | function ContextMenu({
  function ContextMenuTrigger (line 15) | function ContextMenuTrigger({
  function ContextMenuGroup (line 23) | function ContextMenuGroup({
  function ContextMenuPortal (line 31) | function ContextMenuPortal({
  function ContextMenuSub (line 39) | function ContextMenuSub({
  function ContextMenuRadioGroup (line 45) | function ContextMenuRadioGroup({
  function ContextMenuSubTrigger (line 56) | function ContextMenuSubTrigger({
  function ContextMenuSubContent (line 80) | function ContextMenuSubContent({
  function ContextMenuContent (line 96) | function ContextMenuContent({
  function ContextMenuItem (line 114) | function ContextMenuItem({
  function ContextMenuCheckboxItem (line 137) | function ContextMenuCheckboxItem({
  function ContextMenuRadioItem (line 163) | function ContextMenuRadioItem({
  function ContextMenuLabel (line 187) | function ContextMenuLabel({
  function ContextMenuSeparator (line 207) | function ContextMenuSeparator({
  function ContextMenuShortcut (line 220) | function ContextMenuShortcut({

FILE: src/components/ui/dialog.tsx
  function Dialog (line 9) | function Dialog({
  function DialogTrigger (line 15) | function DialogTrigger({
  function DialogPortal (line 21) | function DialogPortal({
  function DialogClose (line 27) | function DialogClose({
  function DialogOverlay (line 33) | function DialogOverlay({
  function DialogContent (line 49) | function DialogContent({
  function DialogHeader (line 76) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
  function DialogFooter (line 86) | function DialogFooter({ className, ...props }: React.ComponentProps<"div...
  function DialogTitle (line 99) | function DialogTitle({
  function DialogDescription (line 112) | function DialogDescription({

FILE: src/components/ui/drawer.tsx
  function Drawer (line 8) | function Drawer({
  function DrawerTrigger (line 14) | function DrawerTrigger({
  function DrawerPortal (line 20) | function DrawerPortal({
  function DrawerClose (line 26) | function DrawerClose({
  function DrawerOverlay (line 32) | function DrawerOverlay({
  function DrawerContent (line 48) | function DrawerContent({
  function DrawerHeader (line 75) | function DrawerHeader({ className, ...props }: React.ComponentProps<"div...
  function DrawerFooter (line 85) | function DrawerFooter({ className, ...props }: React.ComponentProps<"div...
  function DrawerTitle (line 95) | function DrawerTitle({
  function DrawerDescription (line 108) | function DrawerDescription({

FILE: src/components/ui/dropdown-menu.tsx
  function DropdownMenu (line 9) | function DropdownMenu({
  function DropdownMenuPortal (line 15) | function DropdownMenuPortal({
  function DropdownMenuTrigger (line 23) | function DropdownMenuTrigger({
  function DropdownMenuContent (line 34) | function DropdownMenuContent({
  function DropdownMenuGroup (line 54) | function DropdownMenuGroup({
  function DropdownMenuItem (line 62) | function DropdownMenuItem({
  function DropdownMenuCheckboxItem (line 85) | function DropdownMenuCheckboxItem({
  function DropdownMenuRadioGroup (line 111) | function DropdownMenuRadioGroup({
  function DropdownMenuRadioItem (line 122) | function DropdownMenuRadioItem({
  function DropdownMenuLabel (line 146) | function DropdownMenuLabel({
  function DropdownMenuSeparator (line 166) | function DropdownMenuSeparator({
  function DropdownMenuShortcut (line 179) | function DropdownMenuShortcut({
  function DropdownMenuSub (line 195) | function DropdownMenuSub({
  function DropdownMenuSubTrigger (line 201) | function DropdownMenuSubTrigger({
  function DropdownMenuSubContent (line 225) | function DropdownMenuSubContent({

FILE: src/components/ui/dropzone.tsx
  type DropzoneResult (line 20) | type DropzoneResult<TUploadRes, TUploadError> =
  type FileStatus (line 33) | type FileStatus<TUploadRes, TUploadError> = {
  type DropZoneErrorCode (line 106) | type DropZoneErrorCode = (typeof dropZoneErrorCodes)[number];
  type UseDropzoneProps (line 159) | type UseDropzoneProps<TUploadRes, TUploadError> = {
  type UseDropzoneReturn (line 187) | interface UseDropzoneReturn<TUploadRes, TUploadError> {
  type DropzoneProps (line 414) | interface DropzoneProps<TUploadRes, TUploadError>
  type DropZoneAreaProps (line 429) | interface DropZoneAreaProps extends React.HTMLAttributes<HTMLDivElement> {
  type DropzoneDescriptionProps (line 477) | interface DropzoneDescriptionProps
  type DropzoneFileListContext (line 502) | interface DropzoneFileListContext<TUploadRes, TUploadError> {
  type DropZoneFileListProps (line 528) | interface DropZoneFileListProps
  type DropzoneFileListItemProps (line 552) | interface DropzoneFileListItemProps<TUploadRes, TUploadError>
  type DropzoneFileMessageProps (line 605) | interface DropzoneFileMessageProps
  type DropzoneMessageProps (line 641) | interface DropzoneMessageProps
  type DropzoneRemoveFileProps (line 671) | interface DropzoneRemoveFileProps extends ButtonVariantsProps {
  type DropzoneRetryFileProps (line 705) | interface DropzoneRetryFileProps extends ButtonVariantsProps {
  type DropzoneTriggerProps (line 744) | interface DropzoneTriggerProps
  type InfiniteProgressProps (line 796) | interface InfiniteProgressProps extends React.HTMLAttributes<HTMLDivElem...

FILE: src/components/ui/form.tsx
  type FormProps (line 23) | type FormProps<T extends FieldValues> = Omit<
  type FormFieldContextValue (line 56) | type FormFieldContextValue<
  type FormItemContextValue (line 103) | type FormItemContextValue = {
  type UseZodFormProps (line 204) | type UseZodFormProps<Z extends ZodSchema> = Exclude<

FILE: src/components/ui/github-button.tsx
  type GithubButtonProps (line 31) | interface GithubButtonProps extends React.ComponentProps<'button'>, Vari...
  function GithubButton (line 70) | function GithubButton({

FILE: src/components/ui/hover-card.tsx
  function HoverCard (line 8) | function HoverCard({
  function HoverCardTrigger (line 14) | function HoverCardTrigger({
  function HoverCardContent (line 22) | function HoverCardContent({

FILE: src/components/ui/input-otp.tsx
  function InputOTP (line 9) | function InputOTP({
  function InputOTPGroup (line 29) | function InputOTPGroup({ className, ...props }: React.ComponentProps<"di...
  function InputOTPSlot (line 39) | function InputOTPSlot({
  function InputOTPSeparator (line 69) | function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {

FILE: src/components/ui/input.tsx
  function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...

FILE: src/components/ui/label.tsx
  function Label (line 8) | function Label({

FILE: src/components/ui/menubar.tsx
  function Menubar (line 9) | function Menubar({
  function MenubarMenu (line 25) | function MenubarMenu({
  function MenubarGroup (line 31) | function MenubarGroup({
  function MenubarPortal (line 37) | function MenubarPortal({
  function MenubarRadioGroup (line 43) | function MenubarRadioGroup({
  function MenubarTrigger (line 51) | function MenubarTrigger({
  function MenubarContent (line 67) | function MenubarContent({
  function MenubarItem (line 91) | function MenubarItem({
  function MenubarCheckboxItem (line 114) | function MenubarCheckboxItem({
  function MenubarRadioItem (line 140) | function MenubarRadioItem({
  function MenubarLabel (line 164) | function MenubarLabel({
  function MenubarSeparator (line 184) | function MenubarSeparator({
  function MenubarShortcut (line 197) | function MenubarShortcut({
  function MenubarSub (line 213) | function MenubarSub({
  function MenubarSubTrigger (line 219) | function MenubarSubTrigger({
  function MenubarSubContent (line 243) | function MenubarSubContent({

FILE: src/components/ui/navigation-menu.tsx
  function NavigationMenu (line 8) | function NavigationMenu({
  function NavigationMenuList (line 32) | function NavigationMenuList({
  function NavigationMenuItem (line 48) | function NavigationMenuItem({
  function NavigationMenuTrigger (line 65) | function NavigationMenuTrigger({
  function NavigationMenuContent (line 85) | function NavigationMenuContent({
  function NavigationMenuViewport (line 102) | function NavigationMenuViewport({
  function NavigationMenuLink (line 124) | function NavigationMenuLink({
  function NavigationMenuIndicator (line 140) | function NavigationMenuIndicator({

FILE: src/components/ui/pagination.tsx
  function Pagination (line 11) | function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
  function PaginationContent (line 23) | function PaginationContent({
  function PaginationItem (line 36) | function PaginationItem({ ...props }: React.ComponentProps<"li">) {
  type PaginationLinkProps (line 40) | type PaginationLinkProps = {
  function PaginationLink (line 45) | function PaginationLink({
  function PaginationPrevious (line 68) | function PaginationPrevious({
  function PaginationNext (line 85) | function PaginationNext({
  function PaginationEllipsis (line 102) | function PaginationEllipsis({

FILE: src/components/ui/password-input-indicator.tsx
  type PasswordStrengthInputProps (line 15) | interface PasswordStrengthInputProps {
  function PasswordStrengthInput (line 50) | function PasswordStrengthInput({

FILE: src/components/ui/password-input.tsx
  type InputProps (line 9) | type InputProps = InputHTMLAttributes<HTMLInputElement>;

FILE: src/components/ui/popover.tsx
  function Popover (line 8) | function Popover({
  function PopoverTrigger (line 14) | function PopoverTrigger({
  function PopoverContent (line 20) | function PopoverContent({
  function PopoverAnchor (line 42) | function PopoverAnchor({

FILE: src/components/ui/progress.tsx
  function Progress (line 8) | function Progress({

FILE: src/components/ui/radio-group.tsx
  function RadioGroup (line 9) | function RadioGroup({
  function RadioGroupItem (line 22) | function RadioGroupItem({

FILE: src/components/ui/resizable.tsx
  function ResizablePanelGroup (line 9) | function ResizablePanelGroup({
  function ResizablePanel (line 25) | function ResizablePanel({
  function ResizableHandle (line 31) | function ResizableHandle({

FILE: src/components/ui/scroll-area.tsx
  function ScrollArea (line 8) | function ScrollArea({
  function ScrollBar (line 31) | function ScrollBar({

FILE: src/components/ui/search-input.tsx
  type IEntry (line 10) | interface IEntry {
  type SearchInputProps (line 15) | interface SearchInputProps {

FILE: src/components/ui/select.tsx
  function Select (line 9) | function Select({
  function SelectGroup (line 15) | function SelectGroup({
  function SelectValue (line 21) | function SelectValue({
  function SelectTrigger (line 27) | function SelectTrigger({
  function SelectContent (line 53) | function SelectContent({
  function SelectLabel (line 88) | function SelectLabel({
  function SelectItem (line 101) | function SelectItem({
  function SelectSeparator (line 125) | function SelectSeparator({
  function SelectScrollUpButton (line 138) | function SelectScrollUpButton({
  function SelectScrollDownButton (line 156) | function SelectScrollDownButton({

FILE: src/components/ui/separator.tsx
  function Separator (line 8) | function Separator({

FILE: src/components/ui/sheet.tsx
  function Sheet (line 9) | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive....
  function SheetTrigger (line 13) | function SheetTrigger({
  function SheetClose (line 19) | function SheetClose({
  function SheetPortal (line 25) | function SheetPortal({
  function SheetOverlay (line 31) | function SheetOverlay({
  function SheetContent (line 47) | function SheetContent({
  function SheetHeader (line 86) | function SheetHeader({ className, ...props }: React.ComponentProps<"div"...
  function SheetFooter (line 96) | function SheetFooter({ className, ...props }: React.ComponentProps<"div"...
  function SheetTitle (line 106) | function SheetTitle({
  function SheetDescription (line 119) | function SheetDescription({

FILE: src/components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 28) | const SIDEBAR_COOKIE_NAME = "sidebar_state"
  constant SIDEBAR_COOKIE_MAX_AGE (line 29) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
  constant SIDEBAR_WIDTH (line 30) | const SIDEBAR_WIDTH = "16rem"
  constant SIDEBAR_WIDTH_MOBILE (line 31) | const SIDEBAR_WIDTH_MOBILE = "18rem"
  constant SIDEBAR_WIDTH_ICON (line 32) | const SIDEBAR_WIDTH_ICON = "3rem"
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 33) | const SIDEBAR_KEYBOARD_SHORTCUT = "b"
  type SidebarContextProps (line 35) | type SidebarContextProps = {
  function useSidebar (line 47) | function useSidebar() {
  function SidebarProvider (line 56) | function SidebarProvider({
  function Sidebar (line 154) | function Sidebar({
  function SidebarTrigger (line 256) | function SidebarTrigger({
  function SidebarRail (line 282) | function SidebarRail({ className, ...props }: React.ComponentProps<"butt...
  function SidebarInset (line 307) | function SidebarInset({ className, ...props }: React.ComponentProps<"mai...
  function SidebarInput (line 321) | function SidebarInput({
  function SidebarHeader (line 335) | function SidebarHeader({ className, ...props }: React.ComponentProps<"di...
  function SidebarFooter (line 346) | function SidebarFooter({ className, ...props }: React.ComponentProps<"di...
  function SidebarSeparator (line 357) | function SidebarSeparator({
  function SidebarContent (line 371) | function SidebarContent({ className, ...props }: React.ComponentProps<"d...
  function SidebarGroup (line 385) | function SidebarGroup({ className, ...props }: React.ComponentProps<"div...
  function SidebarGroupLabel (line 396) | function SidebarGroupLabel({
  function SidebarGroupAction (line 417) | function SidebarGroupAction({
  function SidebarGroupContent (line 440) | function SidebarGroupContent({
  function SidebarMenu (line 454) | function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
  function SidebarMenuItem (line 465) | function SidebarMenuItem({ className, ...props }: React.ComponentProps<"...
  function SidebarMenuButton (line 498) | function SidebarMenuButton({
  function SidebarMenuAction (line 548) | function SidebarMenuAction({
  function SidebarMenuBadge (line 580) | function SidebarMenuBadge({
  function SidebarMenuSkeleton (line 602) | function SidebarMenuSkeleton({
  function SidebarMenuSub (line 640) | function SidebarMenuSub({ className, ...props }: React.ComponentProps<"u...
  function SidebarMenuSubItem (line 655) | function SidebarMenuSubItem({
  function SidebarMenuSubButton (line 669) | function SidebarMenuSubButton({

FILE: src/components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

FILE: src/components/ui/slider.tsx
  function Slider (line 8) | function Slider({

FILE: src/components/ui/sliding-number.tsx
  type SlidingNumberRollerProps (line 17) | type SlidingNumberRollerProps = {
  function SlidingNumberRoller (line 24) | function SlidingNumberRoller({
  type SlidingNumberDisplayProps (line 60) | type SlidingNumberDisplayProps = {
  function SlidingNumberDisplay (line 67) | function SlidingNumberDisplay({
  type SlidingNumberProps (line 98) | type SlidingNumberProps = React.ComponentProps<'span'> & {
  function SlidingNumber (line 109) | function SlidingNumber({

FILE: src/components/ui/switch.tsx
  function Switch (line 8) | function Switch({

FILE: src/components/ui/table.tsx
  function Table (line 7) | function Table({ className, ...props }: React.ComponentProps<"table">) {
  function TableHeader (line 22) | function TableHeader({ className, ...props }: React.ComponentProps<"thea...
  function TableBody (line 32) | function TableBody({ className, ...props }: React.ComponentProps<"tbody"...
  function TableFooter (line 42) | function TableFooter({ className, ...props }: React.ComponentProps<"tfoo...
  function TableRow (line 55) | function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
  function TableHead (line 68) | function TableHead({ className, ...props }: React.ComponentProps<"th">) {
  function TableCell (line 81) | function TableCell({ className, ...props }: React.ComponentProps<"td">) {
  function TableCaption (line 94) | function TableCaption({

FILE: src/components/ui/tabs.tsx
  function Tabs (line 8) | function Tabs({
  function TabsList (line 21) | function TabsList({
  function TabsTrigger (line 37) | function TabsTrigger({
  function TabsContent (line 53) | function TabsContent({

FILE: src/components/ui/textarea.tsx
  function Textarea (line 5) | function Textarea({ className, ...props }: React.ComponentProps<"textare...

FILE: src/components/ui/toast.tsx
  type ToastProps (line 115) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
  type ToastActionElement (line 117) | type ToastActionElement = React.ReactElement<typeof ToastAction>

FILE: src/components/ui/toaster.tsx
  function Toaster (line 13) | function Toaster() {

FILE: src/components/ui/toggle-group.tsx
  function ToggleGroup (line 17) | function ToggleGroup({
  function ToggleGroupItem (line 43) | function ToggleGroupItem({

FILE: src/components/ui/toggle.tsx
  function Toggle (line 31) | function Toggle({

FILE: src/components/ui/tooltip.tsx
  function TooltipProvider (line 8) | function TooltipProvider({
  function Tooltip (line 21) | function Tooltip({
  function TooltipTrigger (line 31) | function TooltipTrigger({
  function TooltipContent (line 37) | function TooltipContent({

FILE: src/components/wrappers/auth/login/forgot-password-form/forgot-password-form.schema.ts
  type ForgotPasswordType (line 10) | type ForgotPasswordType = z.infer<typeof ForgotPasswordSchema>;

FILE: src/components/wrappers/auth/login/forgot-password-form/forgot-password-form.tsx
  type ForgotPasswordFormProps (line 15) | type ForgotPasswordFormProps = {

FILE: src/components/wrappers/auth/login/login-form/login-form.schema.ts
  type LoginType (line 11) | type LoginType = z.infer<typeof LoginSchema>;

FILE: src/components/wrappers/auth/login/login-form/login-form.tsx
  type loginFormProps (line 26) | type loginFormProps = {

FILE: src/components/wrappers/auth/login/reset-password-form/reset-password-form.schema.ts
  type ResetPasswordType (line 21) | type ResetPasswordType = z.infer<typeof ResetPasswordSchema>;

FILE: src/components/wrappers/auth/login/reset-password-form/reset-password-form.tsx
  type ResetPasswordFormProps (line 18) | type ResetPasswordFormProps = {

FILE: src/components/wrappers/auth/register/register-form/register-form.schema.ts
  type RegisterType (line 25) | type RegisterType = z.infer<typeof RegisterSchema>;

FILE: src/components/wrappers/auth/register/register-form/register-form.tsx
  type registerFormProps (line 32) | type registerFormProps = {

FILE: src/components/wrappers/auth/reset-password/reset-password-form.tsx
  type ResetPasswordFormProps (line 15) | type ResetPasswordFormProps = {

FILE: src/components/wrappers/auth/reset-password/reset-password-schema.ts
  type ResetPasswordType (line 22) | type ResetPasswordType = z.infer<typeof ResetPasswordSchema>;

FILE: src/components/wrappers/auth/social-buttons.tsx
  function SocialAuthButtons (line 13) | function SocialAuthButtons({ providers }: { providers: AuthProviderConfi...

FILE: src/components/wrappers/common/bread-crumbs/bread-crumbs.tsx
  function useBreadCrumbs (line 15) | function useBreadCrumbs() {
  type BreadCrumbsProps (line 34) | interface BreadCrumbsProps {
  function BreadCrumbsWrapper (line 38) | function BreadCrumbsWrapper() {
  constant FORBIDDEN_LINKS (line 47) | const FORBIDDEN_LINKS = ["organization", "dashboard", "database", "admin...
  function BreadCrumbs (line 50) | function BreadCrumbs({}: BreadCrumbsProps) {

FILE: src/components/wrappers/common/button/back-button.tsx
  type BackButtonProps (line 6) | type BackButtonProps = React.ComponentProps<typeof Button> & {
  function BackButton (line 10) | function BackButton({ children, ...props }: BackButtonProps) {

FILE: src/components/wrappers/common/button/button-with-confirm.tsx
  type ButtonWithConfirmProps (line 18) | type ButtonWithConfirmProps = {

FILE: src/components/wrappers/common/button/button-with-loading.tsx
  type VariantButton (line 65) | type VariantButton = {
  type SizeButton (line 74) | type SizeButton = {
  type ButtonWithLoadingProps (line 81) | type ButtonWithLoadingProps = {

FILE: src/components/wrappers/common/button/copy-button.tsx
  function copyToClipboardWithMeta (line 7) | async function copyToClipboardWithMeta(value: string) {
  type CopyButtonProps (line 11) | type CopyButtonProps = {

FILE: src/components/wrappers/common/cards-with-pagination.tsx
  type CardsWithPaginationProps (line 9) | interface CardsWithPaginationProps<T> {
  function CardsWithPagination (line 21) | function CardsWithPagination<T>(props: CardsWithPaginationProps<T>) {

FILE: src/components/wrappers/common/code-snippet.tsx
  type CodeSnippetProps (line 9) | type CodeSnippetProps = PropsWithChildren<{

FILE: src/components/wrappers/common/combobox.tsx
  type ComboBoxProps (line 12) | type ComboBoxProps<T = string> = {
  function ComboBox (line 22) | function ComboBox<T = string>(props: ComboBoxProps<T>) {

FILE: src/components/wrappers/common/connection-indicator.tsx
  type ConnectionIndicatorProps (line 3) | type ConnectionIndicatorProps = {

FILE: src/components/wrappers/common/console-silencer.tsx
  function ConsoleSilencer (line 5) | function ConsoleSilencer() {

FILE: src/components/wrappers/common/day-time-picker.tsx
  type DateTimePickerProps (line 14) | interface DateTimePickerProps {
  function DateTimePicker (line 19) | function DateTimePicker({ name }: DateTimePickerProps) {

FILE: src/components/wrappers/common/dialog.tsx
  type agentRegistrationDialogProps (line 8) | type agentRegistrationDialogProps = {

FILE: src/components/wrappers/common/dropzone/dropzone-file.tsx
  type DropZoneFileProps (line 22) | type DropZoneFileProps = {

FILE: src/components/wrappers/common/empty-state-placeholder.tsx
  type EmptyStatePlaceholderProps (line 6) | type EmptyStatePlaceholderProps = {

FILE: src/components/wrappers/common/file-uploader.tsx
  type DirectionOptions (line 10) | type DirectionOptions = "rtl" | "ltr" | undefined;
  type FileUploaderContextType (line 12) | type FileUploaderContextType = {
  type FileUploaderProps (line 33) | type FileUploaderProps = {

FILE: src/components/wrappers/common/loading/loading-spinner.tsx
  type ISVGProps (line 4) | interface ISVGProps extends React.SVGProps<SVGSVGElement> {

FILE: src/components/wrappers/common/multiselect/multi-select.tsx
  type MultiSelectProps (line 35) | interface MultiSelectProps extends React.ButtonHTMLAttributes<HTMLButton...

FILE: src/components/wrappers/common/pagination/pagination-indexes.tsx
  type paginationItemsProps (line 3) | type paginationItemsProps = {

FILE: src/components/wrappers/common/pagination/pagination-navigation.tsx
  type paginationNavigationProps (line 5) | type paginationNavigationProps = {

FILE: src/components/wrappers/common/pagination/pagination-size.tsx
  type PaginationSizeProps (line 4) | type PaginationSizeProps = {

FILE: src/components/wrappers/common/status-badge.tsx
  type statusBadgeProps (line 4) | type statusBadgeProps = {

FILE: src/components/wrappers/common/table/data-table.tsx
  type DataTableProps (line 23) | interface DataTableProps<TData, TValue> {
  function DataTable (line 48) | function DataTable<TData, TValue>({

FILE: src/components/wrappers/common/table/filters.tsx
  type FilterItem (line 6) | type FilterItem = {
  type FiltersDropdownProps (line 11) | type FiltersDropdownProps = {

FILE: src/components/wrappers/common/table/table-pagination-navigation.tsx
  type paginationNavigationProps (line 3) | type paginationNavigationProps = {

FILE: src/components/wrappers/common/table/table-pagination-size.tsx
  type tablePaginationSizeProps (line 5) | type tablePaginationSizeProps = {

FILE: src/components/wrappers/common/table/table-pagination.tsx
  type tablePaginationProps (line 7) | interface tablePaginationProps {
  function TablePagination (line 14) | function TablePagination(props: tablePaginationProps) {

FILE: src/components/wrappers/common/table/table-sort-button.tsx
  type tableSortButtonProps (line 5) | interface tableSortButtonProps<TData, TValue> {
  function TableSortButton (line 11) | function TableSortButton<TData, TValue>(props: tableSortButtonProps<TDat...

FILE: src/components/wrappers/common/tooltip-custom.tsx
  type TooltipCustomProps (line 4) | type TooltipCustomProps = PropsWithChildren<{
  function TooltipCustom (line 9) | function TooltipCustom(props: TooltipCustomProps) {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal.tsx
  type ChannelAddModalProps (line 22) | type ChannelAddModalProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-card/button-delete-channel.tsx
  type DeleteChannelButtonProps (line 15) | type DeleteChannelButtonProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-card/button-edit-channel.tsx
  type EditChannelButtonProps (line 19) | type EditChannelButtonProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-card/channel-card.tsx
  type ChannelCardProps (line 18) | type ChannelCardProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form.schema.ts
  type NotificationChannelFormType (line 69) | type NotificationChannelFormType = z.infer<typeof NotificationChannelFor...
  type StorageChannelFormType (line 70) | type StorageChannelFormType = z.infer<typeof StorageChannelFormSchema>;

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form.tsx
  type NotifierFormProps (line 38) | type NotifierFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-test-button.tsx
  type NotifierTestChannelButtonProps (line 19) | type NotifierTestChannelButtonProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/discord.form.tsx
  type NotifierDiscordFormProps (line 12) | type NotifierDiscordFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/gotify.form.tsx
  type NotifierGotifyFormProps (line 13) | type NotifierGotifyFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/ntfy.form.tsx
  type NotifierNtfyFormProps (line 7) | type NotifierNtfyFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/slack.form.tsx
  type NotifierSmtpFormProps (line 12) | type NotifierSmtpFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/smtp.form.tsx
  type NotifierSmtpFormProps (line 8) | type NotifierSmtpFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/telegram.form.tsx
  type NotifierTelegramFormProps (line 13) | type NotifierTelegramFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/webhook.form.tsx
  type NotifierWebhookFormProps (line 13) | type NotifierWebhookFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/google-drive.form.tsx
  type StorageGoogleDriveFormProps (line 14) | type StorageGoogleDriveFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/s3.form.tsx
  type StorageS3FormProps (line 14) | type StorageS3FormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/channels-section.tsx
  type ChannelsSectionProps (line 15) | type ChannelsSectionProps = {

FILE: src/components/wrappers/dashboard/admin/channels/helpers/common.tsx
  type ChannelKind (line 36) | type ChannelKind = "notification" | "storage";
  function getChannelTextBasedOnKind (line 38) | function getChannelTextBasedOnKind(kind: ChannelKind) {
  type ProviderIconTypes (line 50) | type ProviderIconTypes = {

FILE: src/components/wrappers/dashboard/admin/channels/helpers/notification.tsx
  function SlackIcon (line 20) | function SlackIcon(props: SVGProps<SVGSVGElement>) {
  function DiscordIcon (line 24) | function DiscordIcon(props: SVGProps<SVGSVGElement>) {
  function TelegramIcon (line 28) | function TelegramIcon(props: SVGProps<SVGSVGElement>) {
  function NtfyIcon (line 33) | function NtfyIcon(props: SVGProps<SVGSVGElement>) {
  function GotifyIcon (line 94) | function GotifyIcon(props: SVGProps<SVGSVGElement>) {
  function WebhookIcon (line 466) | function WebhookIcon(props: SVGProps<SVGSVGElement>) {
  function MSTeamsIcon (line 488) | function MSTeamsIcon(props: SVGProps<SVGSVGElement>) {

FILE: src/components/wrappers/dashboard/admin/channels/helpers/storage.tsx
  function S3Icon (line 13) | function S3Icon(props: SVGProps<SVGSVGElement>) {
  function GoogleDriveIcon (line 22) | function GoogleDriveIcon(props: SVGProps<SVGSVGElement>) {
  function BlobIcon (line 29) | function BlobIcon(props: SVGProps<SVGSVGElement>) {
  function GCSIcon (line 43) | function GCSIcon(props: SVGProps<SVGSVGElement>) {

FILE: src/components/wrappers/dashboard/admin/channels/organization/channels-organization-form.tsx
  type ChannelOrganisationFormProps (line 22) | type ChannelOrganisationFormProps = {

FILE: src/components/wrappers/dashboard/admin/channels/organization/channels-organization.schema.ts
  type ChannelsOrganizationType (line 7) | type ChannelsOrganizationType = z.infer<typeof ChannelsOrganizationSchema>;

FILE: src/components/wrappers/dashboard/admin/notifications/logs/columns.tsx
  function notificationLogsColumns (line 12) | function notificationLogsColumns(): ColumnDef<NotificationLogWithRelatio...

FILE: src/components/wrappers/dashboard/admin/notifications/logs/notification-log-modal.tsx
  type NotificationLogModalProps (line 24) | type NotificationLogModalProps = {

FILE: src/components/wrappers/dashboard/admin/notifications/logs/notification-logs-list.tsx
  type NotificationsLogsListProps (line 10) | type NotificationsLogsListProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/admin-organizations-table.tsx
  type AdminOrganizationsTableProps (line 6) | type AdminOrganizationsTableProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/admin-organization-add-modal.tsx
  type AdminOrganizationAddModalProps (line 10) | type AdminOrganizationAddModalProps = {}

FILE: src/components/wrappers/dashboard/admin/organizations/organization/admin-organization-form.tsx
  type AdminOrganizationFormProps (line 15) | type AdminOrganizationFormProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/admin-organization-section.tsx
  type AdminOrganizationSectionProps (line 8) | type AdminOrganizationSectionProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/admin-orgnization-list.tsx
  type AdminOrganizationListProps (line 6) | type AdminOrganizationListProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/button-delete-organization.tsx
  type ButtonDeleteFleetProps (line 11) | type ButtonDeleteFleetProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/details/organization-add-member-form.tsx
  type OrganizationAddMemberFormProps (line 20) | type OrganizationAddMemberFormProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/details/organization-add-member-modal.tsx
  type OrganizationAddMemberModalProps (line 20) | type OrganizationAddMemberModalProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/details/organization-delete-member-modal.tsx
  type OrganizationDeleteMemberModalProps (line 19) | type OrganizationDeleteMemberModalProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/details/organization-member-card.tsx
  type OrganizationMemberCardProps (line 23) | type OrganizationMemberCardProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/details/organization-member-change-role.tsx
  type OrganizationMemberChangeRoleModalProps (line 25) | type OrganizationMemberChangeRoleModalProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/details/update-organization-form.tsx
  type UpdateOrganizationFormProps (line 18) | type UpdateOrganizationFormProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/organization-management.tsx
  type OrganizationManagementProps (line 22) | type OrganizationManagementProps = {

FILE: src/components/wrappers/dashboard/admin/organizations/organization/organization.schema.ts
  type OrganizationInvitationType (line 23) | type OrganizationInvitationType = z.infer<typeof OrganizationInvitationS...
  type OrganizationSchema (line 24) | type OrganizationSchema = z.infer<typeof OrganizationSchema>;
  type UpdateOrganizationSchemaType (line 25) | type UpdateOrganizationSchemaType = z.infer<typeof UpdateOrganizationSch...
  type AddMemberSchemaType (line 26) | type AddMemberSchemaType = z.infer<typeof AddMemberSchema>;

FILE: src/components/wrappers/dashboard/admin/organizations/organization/table-colums.tsx
  function organizationsListColumns (line 9) | function organizationsListColumns(): ColumnDef<OrganizationWithMembers>[] {

FILE: src/components/wrappers/dashboard/admin/settings/email/email-form/email-form.schema.ts
  type EmailFormType (line 12) | type EmailFormType = z.infer<typeof EmailFormSchema>;

FILE: src/components/wrappers/dashboard/admin/settings/email/email-form/email-form.tsx
  type EmailFormProps (line 35) | type EmailFormProps = {

FILE: src/components/wrappers/dashboard/admin/settings/email/settings-email-section.tsx
  type SettingsEmailSectionProps (line 7) | type SettingsEmailSectionProps = {

FILE: src/components/wrappers/dashboard/admin/settings/notification/settings-notification-section.tsx
  type SettingsNotificationSectionProps (line 27) | type SettingsNotificationSectionProps = {

FILE: src/components/wrappers/dashboard/admin/settings/notification/settings-notification.schema.ts
  type DefaultNotificationType (line 7) | type DefaultNotificationType = z.infer<typeof DefaultNotificationSchema>;

FILE: src/components/wrappers/dashboard/admin/settings/settings-tabs.tsx
  type SettingsTabsProps (line 16) | type SettingsTabsProps = {

FILE: src/components/wrappers/dashboard/admin/settings/storage/settings-storage-section.tsx
  type SettingsStorageSectionProps (line 31) | type SettingsStorageSectionProps = {

FILE: src/components/wrappers/dashboard/admin/settings/storage/settings-storage.schema.ts
  type DefaultStorageType (line 8) | type DefaultStorageType = z.infer<typeof DefaultStorageSchema>;

FILE: src/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.schema.ts
  type S3FormType (line 10) | type S3FormType = z.infer<typeof S3FormSchema>;
  type StorageType (line 16) | type StorageType = z.infer<typeof StorageSwitchSchema>;

FILE: src/components/wrappers/dashboard/admin/settings/storage/storage-s3/storage-s3-form.tsx
  type S3FormProps (line 17) | type S3FormProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-add-modal.tsx
  type AdminUserAddModalProps (line 11) | type AdminUserAddModalProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-change-password-modal.tsx
  type AdminUserChangePasswordProps (line 18) | type AdminUserChangePasswordProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-change-role-modal.tsx
  type AdminUserChangeRoleModalProps (line 14) | type AdminUserChangeRoleModalProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-delete-modal.tsx
  type AdminDeleteUserModalProps (line 20) | type AdminDeleteUserModalProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-edit-form.tsx
  type AdminUserEditFormProps (line 12) | type AdminUserEditFormProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-edit-modal.tsx
  type AdminUserEditPasswordProps (line 13) | type AdminUserEditPasswordProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-form.tsx
  type AdminUserFormProps (line 14) | type AdminUserFormProps = {

FILE: src/components/wrappers/dashboard/admin/users/admin-user-list.tsx
  type AdminUserListProps (line 6) | type AdminUserListProps = {

FILE: src/components/wrappers/dashboard/admin/users/table-colums.tsx
  type UsersListColumnsProps (line 12) | type UsersListColumnsProps = {
  function usersListColumns (line 16) | function usersListColumns({ isPasswordAuthEnabled }: UsersListColumnsPro...

FILE: src/components/wrappers/dashboard/admin/users/user-actions-cell.tsx
  type UserActionsCellProps (line 21) | interface UserActionsCellProps {
  function UserActionsCell (line 26) | function UserActionsCell({user, isPasswordAuthEnabled}: UserActionsCellP...

FILE: src/components/wrappers/dashboard/admin/users/user.schema.ts
  type UserType (line 9) | type UserType = z.infer<typeof UserSchema>;
  type UserEditType (line 16) | type UserEditType = z.infer<typeof UserEditSchema>;

FILE: src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx
  type AgentCardKeyProps (line 10) | type AgentCardKeyProps = {

FILE: src/components/wrappers/dashboard/agent/agent-card/agent-card.tsx
  type agentCardProps (line 16) | type agentCardProps = {

FILE: src/components/wrappers/dashboard/agent/agent-content.tsx
  type AgentContentPageProps (line 23) | type AgentContentPageProps = {

FILE: src/components/wrappers/dashboard/agent/agent-database-card.tsx
  type agentDatabaseCardProps (line 6) | type agentDatabaseCardProps = {

FILE: src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx
  type agentRegistrationDialogProps (line 13) | type agentRegistrationDialogProps = PropsWithChildren<{
  function AgentModalKey (line 17) | function AgentModalKey(props: agentRegistrationDialogProps) {

FILE: src/components/wrappers/dashboard/agent/button-delete-agent/button-delete-agent.tsx
  type ButtonDeleteAgentProps (line 11) | type ButtonDeleteAgentProps = {

FILE: src/components/wrappers/dashboard/backup/backup-button/backup-button.tsx
  type BackupButtonProps (line 12) | type BackupButtonProps = {

FILE: src/components/wrappers/dashboard/common/logged-in/logged-in-button.tsx
  type LoggedInButtonClientProps (line 10) | type LoggedInButtonClientProps = {

FILE: src/components/wrappers/dashboard/common/logged-in/logged-in-dropdown.tsx
  type LoggedInDropdownProps (line 19) | type LoggedInDropdownProps = PropsWithChildren<{

FILE: src/components/wrappers/dashboard/common/profile/profile-modal.tsx
  type ProfileModalProps (line 14) | type ProfileModalProps = {

FILE: src/components/wrappers/dashboard/common/profile/profile-sidebar.tsx
  type ProfileSidebarProps (line 8) | interface ProfileSidebarProps {
  function ProfileSidebar (line 12) | function ProfileSidebar({ user }: ProfileSidebarProps) {
  function SettingsTabTrigger (line 40) | function SettingsTabTrigger({ value, icon, children }: { value: string; ...

FILE: src/components/wrappers/dashboard/common/sidebar/app-sidebar.tsx
  function AppSidebar (line 16) | function AppSidebar() {

FILE: src/components/wrappers/dashboard/common/sidebar/menu-sidebar.tsx
  type SidebarItem (line 24) | type SidebarItem = {
  type SidebarGroupItem (line 36) | type SidebarGroupItem = {
  type SidebarMenuCustomBaseProps (line 42) | type SidebarMenuCustomBaseProps = {

FILE: src/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit.tsx
  type SideBarFooterCreditProps (line 5) | type SideBarFooterCreditProps = {};

FILE: src/components/wrappers/dashboard/common/sidebar/side-bar-logo.tsx
  type SideBarLogoProps (line 7) | type SideBarLogoProps = {};

FILE: src/components/wrappers/dashboard/database/backup/actions/backup-actions-cell.tsx
  type DatabaseActionsCellProps (line 20) | interface DatabaseActionsCellProps {
  function DatabaseActionsCell (line 27) | function DatabaseActionsCell({backup, activeMember, isAlreadyRestore, is...

FILE: src/components/wrappers/dashboard/database/backup/actions/backup-actions-form.tsx
  type BackupActionsFormProps (line 44) | type BackupActionsFormProps = {

FILE: src/components/wrappers/dashboard/database/backup/actions/backup-actions-modal.tsx
  type DatabaseActionsModalProps (line 17) | type DatabaseActionsModalProps = {}

FILE: src/components/wrappers/dashboard/database/backup/actions/backup-actions.schema.ts
  type BackupActionsType (line 10) | type BackupActionsType = z.infer<typeof BackupActionsSchema>;

FILE: src/components/wrappers/dashboard/database/backup/backup-modal-context.tsx
  type DatabaseActionKind (line 6) | type DatabaseActionKind = "restore" | "download" | "delete";
  function getBackupActionTextBasedOnActionKind (line 8) | function getBackupActionTextBasedOnActionKind(kind: DatabaseActionKind) {
  type BackupModalContextType (line 22) | type BackupModalContextType = {

FILE: src/components/wrappers/dashboard/database/channels-policy/policy-form.tsx
  type ChannelPoliciesFormProps (line 37) | type ChannelPoliciesFormProps = {

FILE: src/components/wrappers/dashboard/database/channels-policy/policy-modal.tsx
  type ChannelPoliciesModalProps (line 21) | type ChannelPoliciesModalProps = {

FILE: src/components/wrappers/dashboard/database/channels-policy/policy.schema.ts
  type PoliciesType (line 17) | type PoliciesType = z.infer<typeof PoliciesSchema>;
  type PolicyType (line 18) | type PolicyType = z.infer<typeof PolicySchema>;
  constant EVENT_KIND_BACKUP_ONLY_OPTIONS (line 20) | const EVENT_KIND_BACKUP_ONLY_OPTIONS = [
  constant EVENT_KIND_OPTIONS (line 26) | const EVENT_KIND_OPTIONS = [

FILE: src/components/wrappers/dashboard/database/cron-button/cron-button.tsx
  type CronButtonProps (line 16) | type CronButtonProps = {

FILE: src/components/wrappers/dashboard/database/cron-button/cron-input.tsx
  type CronInputProps (line 11) | type CronInputProps = {

FILE: src/components/wrappers/dashboard/database/database-form/database-form.tsx
  type DatabaseFormProps (line 15) | type DatabaseFormProps = {

FILE: src/components/wrappers/dashboard/database/database-form/form-database.schema.ts
  type DatabaseType (line 9) | type DatabaseType = z.infer<typeof DatabaseSchema>;

FILE: src/components/wrappers/dashboard/database/health/health-modal.tsx
  type HealthModalProps (line 16) | type HealthModalProps = {

FILE: src/components/wrappers/dashboard/database/import/import-modal.tsx
  type ImportModalProps (line 18) | type ImportModalProps = {

FILE: src/components/wrappers/dashboard/database/import/upload-backup-zone.tsx
  type UploadRetentionZoneProps (line 15) | type UploadRetentionZoneProps = {

FILE: src/components/wrappers/dashboard/database/restore-form.schema.ts
  type RestoreType (line 39) | type RestoreType = z.infer<typeof RestoreSchema>;

FILE: src/components/wrappers/dashboard/database/retention-policy/backup-retention-settings-form.tsx
  type BackupRetentionSettingsFormProps (line 25) | type BackupRetentionSettingsFormProps = {

FILE: src/components/wrappers/dashboard/database/retention-policy/backup-retention-settings.schema.ts
  type RetentionSettings (line 17) | type RetentionSettings = z.infer<typeof RetentionSettingsSchema>;

FILE: src/components/wrappers/dashboard/database/retention-policy/backup-retention-settings.tsx
  type RetentionPolicyType (line 19) | type RetentionPolicyType = "count" | "days" | "gfs"
  type GFSSettings (line 21) | interface GFSSettings {
  type RetentionSettings (line 28) | interface RetentionSettings {
  type BackupRetentionSettingsProps (line 35) | type BackupRetentionSettingsProps = {
  function BackupRetentionSettings (line 40) | function BackupRetentionSettings({database}: BackupRetentionSettingsProp...

FILE: src/components/wrappers/dashboard/database/retention-policy/retention-policy-sheet.tsx
  type RetentionPolicySheetProps (line 9) | type RetentionPolicySheetProps = {

FILE: src/components/wrappers/dashboard/health/heath-grid.tsx
  type HealthStatus (line 8) | type HealthStatus = "healthy" | "degraded" | "down" | "unknown"
  type HealthCheckData (line 10) | interface HealthCheckData {
  type Props (line 15) | interface Props {
  constant INTERVAL_MINUTES (line 19) | const INTERVAL_MINUTES = 10
  constant WINDOW_HOURS (line 20) | const WINDOW_HOURS = 12
  function roundDateToInterval (line 22) | function roundDateToInterval(date: Date, intervalMinutes: number): Date {
  function buildTimeSeries (line 27) | function buildTimeSeries(logs: HealthcheckLog[]): HealthCheckData[] {
  function getStatusColor (line 73) | function getStatusColor(status: HealthStatus): string {
  function getOldestLog (line 86) | function getOldestLog(logs: HealthcheckLog[]): HealthcheckLog {
  function formatTime (line 95) | function formatTime(date: Date): string {

FILE: src/components/wrappers/dashboard/organization/create-organisation-modal.tsx
  type createOrganizationModalProps (line 16) | type createOrganizationModalProps = {
  function CreateOrganizationModal (line 24) | function CreateOrganizationModal({

FILE: src/components/wrappers/dashboard/organization/delete-organization-button.tsx
  type DeleteOrganizationButtonProps (line 11) | type DeleteOrganizationButtonProps = {

FILE: src/components/wrappers/dashboard/organization/migration/migration-flow.tsx
  type MigrationFlowProps (line 18) | interface MigrationFlowProps {
  type MigrationStatus (line 29) | type MigrationStatus = "idle" | "migrating" | "completed" | "error"
  function MigrationFlow (line 32) | function MigrationFlow({

FILE: src/components/wrappers/dashboard/organization/migration/migration-tool.tsx
  type MigrationToolProps (line 14) | interface MigrationToolProps {

FILE: src/components/wrappers/dashboard/organization/migration/source-panel.tsx
  type ViewState (line 19) | type ViewState = "projects" | "databases" | "backups"
  type SourcePanelProps (line 21) | interface SourcePanelProps {
  function SourcePanel (line 32) | function SourcePanel({

FILE: src/components/wrappers/dashboard/organization/migration/target-panel.tsx
  type ViewState (line 16) | type ViewState = "projects" | "databases"
  type TargetPanelProps (line 18) | interface TargetPanelProps {
  function TargetPanel (line 27) | function TargetPanel({

FILE: src/components/wrappers/dashboard/organization/organization-combobox.tsx
  function OrganizationCombobox (line 19) | function OrganizationCombobox() {

FILE: src/components/wrappers/dashboard/organization/settings/settings-organization-members-table.tsx
  type SettingsOrganizationMembersTableProps (line 7) | interface SettingsOrganizationMembersTableProps {

FILE: src/components/wrappers/dashboard/organization/tabs/organization-channels-tab/organization-agents-tab.tsx
  type OrganizationAgentsTabProps (line 8) | type OrganizationAgentsTabProps = {

FILE: src/components/wrappers/dashboard/organization/tabs/organization-channels-tab/organization-notifiers-tab.tsx
  type OrganizationNotifiersTabProps (line 10) | type OrganizationNotifiersTabProps = {

FILE: src/components/wrappers/dashboard/organization/tabs/organization-channels-tab/organization-storages-tab.tsx
  type OrganizationNotifiersTabProps (line 10) | type OrganizationNotifiersTabProps = {

FILE: src/components/wrappers/dashboard/organization/tabs/organization-tabs.tsx
  type OrganizationTabsProps (line 24) | type OrganizationTabsProps = {

FILE: src/components/wrappers/dashboard/profile/components/avatar-with-upload.tsx
  type AvatarWithUploadProps (line 12) | type AvatarWithUploadProps = {

FILE: src/components/wrappers/dashboard/profile/components/backup-codes-list.tsx
  type BackupCodesListProps (line 8) | type BackupCodesListProps = {
  function BackupCodesList (line 13) | function BackupCodesList({ codes, className }: BackupCodesListProps) {

FILE: src/components/wrappers/dashboard/profile/form/2fa-form.tsx
  type TwoFactorFormProps (line 13) | type TwoFactorFormProps = {
  function TwoFactorForm (line 18) | function TwoFactorForm({ onSuccess, onSuccessData }: TwoFactorFormProps) {

FILE: src/components/wrappers/dashboard/profile/form/2fa.schema.ts
  type OtpSchemaType (line 8) | type OtpSchemaType = z.infer<typeof OtpSchema>;
  type BackupCodeSchemaType (line 14) | type BackupCodeSchemaType = z.infer<typeof BackupCodeSchema>;

FILE: src/components/wrappers/dashboard/profile/form/reset-password-form.tsx
  type ResetPasswordFormProps (line 16) | type ResetPasswordFormProps = {
  function ResetPasswordForm (line 21) | function ResetPasswordForm({ onSuccess, isDefault }: ResetPasswordFormPr...

FILE: src/components/wrappers/dashboard/profile/form/set-password-form.tsx
  type SetPasswordFormProps (line 14) | type SetPasswordFormProps = {
  function SetPasswordForm (line 18) | function SetPasswordForm({onSuccess}: SetPasswordFormProps) {

FILE: src/components/wrappers/dashboard/profile/modal/disable-2fa-modal.tsx
  type Password (line 22) | type Password = z.infer<typeof PasswordSchema>;
  type Disable2FAModalProps (line 24) | type Disable2FAModalProps = {
  function Disable2FAProfileProviderModal (line 29) | function Disable2FAProfileProviderModal({ onOpenChange, open }: Disable2...

FILE: src/components/wrappers/dashboard/profile/modal/reset-password-modal.tsx
  type ResetPasswordModalProps (line 7) | type ResetPasswordModalProps = {
  function ResetPasswordProfileProviderModal (line 12) | function ResetPasswordProfileProviderModal({ onOpenChange, open }: Reset...

FILE: src/components/wrappers/dashboard/profile/modal/set-password-modal.tsx
  type SetPasswordModalProps (line 7) | type SetPasswordModalProps = {
  function SetPasswordProfileProviderModal (line 12) | function SetPasswordProfileProviderModal({ onOpenChange, open }: SetPass...

FILE: src/components/wrappers/dashboard/profile/modal/setup-2fa-modal.tsx
  type Password (line 33) | type Password = z.infer<typeof PasswordSchema>;
  type Setup2FAModalProps (line 35) | type Setup2FAModalProps = {
  function Setup2FAProfileProviderModal (line 41) | function Setup2FAProfileProviderModal({onOpenChange, open, disabled}: Se...

FILE: src/components/wrappers/dashboard/profile/modal/view-backup-codes-modal.tsx
  type Password (line 21) | type Password = z.infer<typeof PasswordSchema>;
  type ViewBackupCodesModalProps (line 23) | type ViewBackupCodesModalProps = {
  function ViewBackupCodesModal (line 28) | function ViewBackupCodesModal({ onOpenChange, open }: ViewBackupCodesMod...

FILE: src/components/wrappers/dashboard/profile/profile-account.tsx
  type ProfileAccountProps (line 16) | interface ProfileAccountProps {
  function ProfileAccount (line 20) | function ProfileAccount({user}: ProfileAccountProps) {

FILE: src/components/wrappers/dashboard/profile/profile-apperance.tsx
  function ProfileAppearance (line 10) | function ProfileAppearance() {
  function ThemeSelector (line 23) | function ThemeSelector() {
  type ThemeKey (line 89) | type ThemeKey = "dark" | "light" | "system";
  constant THEME_TEXT (line 92) | const THEME_TEXT: Record<ThemeKey, string> = {

FILE: src/components/wrappers/dashboard/profile/profile-general.tsx
  type ProfileGeneralProps (line 26) | interface ProfileGeneralProps {
  function ProfileGeneral (line 30) | function ProfileGeneral({user}: ProfileGeneralProps) {
  function RoleBadge (line 121) | function RoleBadge({role}: { role: string }) {

FILE: src/components/wrappers/dashboard/profile/profile-providers.tsx
  type ProfileProviderProps (line 23) | interface ProfileProviderProps {
  function ProfileProviders (line 28) | function ProfileProviders({

FILE: src/components/wrappers/dashboard/profile/profile-security.tsx
  type ProfileSecurityProps (line 49) | interface ProfileSecurityProps {
  function ProfileSecurity (line 59) | function ProfileSecurity({
  function SessionRow (line 363) | function SessionRow({
  function PasskeyRow (line 453) | function PasskeyRow({

FILE: src/components/wrappers/dashboard/profile/schemas/account.schema.ts
  type EmailSchemaType (line 8) | type EmailSchemaType = z.infer<typeof EmailSchema>;

FILE: src/components/wrappers/dashboard/profile/schemas/general.schema.ts
  type ProfileSchemaType (line 9) | type ProfileSchemaType = z.infer<typeof ProfileSchema>;

FILE: src/components/wrappers/dashboard/profile/schemas/provider.schema.ts
  type PasswordProviderSchemaType (line 11) | type PasswordProviderSchemaType = z.infer<typeof PasswordProviderSchema>;

FILE: src/components/wrappers/dashboard/profile/schemas/security.schema.ts
  type ResetPasswordSecuritySchemaType (line 22) | type ResetPasswordSecuritySchemaType = z.infer<typeof ResetPasswordSecur...
  type Setup2FASecuritySchemaType (line 28) | type Setup2FASecuritySchemaType = z.infer<typeof Setup2FASecuritySchema>;

FILE: src/components/wrappers/dashboard/projects/button-delete-project/button-delete-project.tsx
  type ButtonDeleteProjectProps (line 13) | type ButtonDeleteProjectProps = {

FILE: src/components/wrappers/dashboard/projects/database/database-backup-list.tsx
  type DatabaseBackupListProps (line 18) | type DatabaseBackupListProps = {

FILE: src/components/wrappers/dashboard/projects/database/database-content.tsx
  type DatabaseContentProps (line 27) | type DatabaseContentProps = {

FILE: src/components/wrappers/dashboard/projects/database/database-kpi.tsx
  type DatabaseKpiPro (line 8) | type DatabaseKpiPro = {

FILE: src/components/wrappers/dashboard/projects/database/database-restore-list.tsx
  type DatabaseRestoreListProps (line 16) | type DatabaseRestoreListProps = {

FILE: src/components/wrappers/dashboard/projects/database/database-tabs.tsx
  type DatabaseTabsProps (line 12) | type DatabaseTabsProps = {

FILE: src/components/wrappers/dashboard/projects/project-card/project-card.tsx
  type projectCardProps (line 9) | type projectCardProps = {

FILE: src/components/wrappers/dashboard/projects/project-card/project-database-card.tsx
  type projectDatabaseCardProps (line 12) | type projectDatabaseCardProps = {
  type databaseCardProps (line 30) | type databaseCardProps = {

FILE: src/components/wrappers/dashboard/statistics/charts/evolution-line-chart.tsx
  type Data (line 9) | type Data = {
  type Payload (line 13) | type Payload = {
  type LineChartDatum (line 18) | type LineChartDatum = {
  type EvolutionLineChartProps (line 23) | type EvolutionLineChartProps = {
  function EvolutionLineChart (line 28) | function EvolutionLineChart(props: EvolutionLineChartProps) {
  function EvolutionTooltip (line 91) | function EvolutionTooltip({

FILE: src/components/wrappers/dashboard/statistics/charts/fake-data.ts
  type Data (line 1) | type Data = {
  function generateFakeEvolutionData (line 12) | function generateFakeEvolutionData(

FILE: src/components/wrappers/dashboard/statistics/charts/line-chart.tsx
  type LineChartCustomProps (line 8) | type LineChartCustomProps<T> = {

FILE: src/components/wrappers/dashboard/statistics/charts/percentage-line-chart.tsx
  type Data (line 9) | type Data = {
  type Payload (line 15) | type Payload = {
  type percentageLineChartProps (line 20) | type percentageLineChartProps = {
  function PercentageLineChart (line 24) | function PercentageLineChart(props: percentageLineChartProps) {
  function PourcentTooltip (line 97) | function PourcentTooltip({

FILE: src/db/index.ts
  function makeMigration (line 64) | async function makeMigration() {

FILE: src/db/migrations/0000_awesome_nomad.sql
  type "settings" (line 4) | CREATE TABLE "settings" (
  type "account" (line 22) | CREATE TABLE "account" (
  type "session" (line 38) | CREATE TABLE "session" (
  type "user" (line 52) | CREATE TABLE "user" (
  type "verification" (line 68) | CREATE TABLE "verification" (
  type "organization" (line 77) | CREATE TABLE "organization" (
  type "member" (line 88) | CREATE TABLE "member" (
  type "invitation" (line 97) | CREATE TABLE "invitation" (
  type "projects" (line 107) | CREATE TABLE "projects" (
  type "backups" (line 118) | CREATE TABLE "backups" (
  type "databases" (line 127) | CREATE TABLE "databases" (
  type "restorations" (line 143) | CREATE TABLE "restorations" (
  type "agents" (line 152) | CREATE TABLE "agents" (
  type "backups" (line 175) | CREATE UNIQUE INDEX "database_id_status_unique" ON "backups" USING btree...

FILE: src/db/migrations/0003_absent_maestro.sql
  type "retention_policies" (line 2) | CREATE TABLE "retention_policies" (

FILE: src/db/migrations/0007_last_umar.sql
  type "notification_channel" (line 2) | CREATE TABLE "notification_channel" (
  type "organization_notification_channels" (line 13) | CREATE TABLE "organization_notification_channels" (

FILE: src/db/migrations/0008_aberrant_scorpion.sql
  type "alert_policy" (line 3) | CREATE TABLE "alert_policy" (
  type "notification_log" (line 14) | CREATE TABLE "notification_log" (

FILE: src/db/migrations/0014_strong_galactus.sql
  type "passkey" (line 1) | CREATE TABLE "passkey" (
  type "two_factor" (line 16) | CREATE TABLE "two_factor" (

FILE: src/db/migrations/0020_thankful_sunspot.sql
  type "organization_storage_channels" (line 2) | CREATE TABLE "organization_storage_channels" (
  type "storage_channel" (line 8) | CREATE TABLE "storage_channel" (

FILE: src/db/migrations/0021_soft_blockbuster.sql
  type "storage_policy" (line 1) | CREATE TABLE "storage_policy" (

FILE: src/db/migrations/0022_purple_retro_girl.sql
  type "backup_storage" (line 2) | CREATE TABLE "backup_storage" (

FILE: src/db/migrations/0035_late_young_avengers.sql
  type "sso_provider" (line 1) | CREATE TABLE "sso_provider" (

FILE: src/db/migrations/0040_quick_lester.sql
  type "healthcheck_log" (line 3) | CREATE TABLE "healthcheck_log" (

FILE: src/db/migrations/0049_chief_terrax.sql
  type "organization_agents" (line 1) | CREATE TABLE "organization_agents" (

FILE: src/db/schema/00_common.ts
  function schemaWithoutMeta (line 10) | function schemaWithoutMeta<

FILE: src/db/schema/01_setting.ts
  type Setting (line 33) | type Setting = z.infer<typeof settingSchema>;

FILE: src/db/schema/02_user.ts
  type User (line 162) | type User = z.infer<typeof userSchema>;
  type UserThemeEnum (line 165) | type UserThemeEnum = z.infer<typeof userThemeEnumSchema>;
  type Session (line 168) | type Session = z.infer<typeof sessionSchema>;
  type Account (line 171) | type Account = z.infer<typeof accountSchema>;
  type FixedAccount (line 173) | type FixedAccount = Omit<BetterAuthAccount, "updatedAt"> & {
  type UserWithAccounts (line 177) | type UserWithAccounts = User & {

FILE: src/db/schema/03_organization.ts
  type Organization (line 33) | type Organization = z.infer<typeof organizationSchema>;
  type OrganizationWithMembers (line 35) | type OrganizationWithMembers = Organization & {
  type MemberWithUser (line 39) | type MemberWithUser = OrganizationMember & {
  type OrganizationWithMembersAndUsers (line 43) | type OrganizationWithMembersAndUsers = Organization & {
  type OrganizationWith (line 48) | type OrganizationWith = Organization & {

FILE: src/db/schema/04_member.ts
  type OrganizationMember (line 35) | type OrganizationMember = z.infer<typeof organizationMemberSchema>;

FILE: src/db/schema/05_invitation.ts
  type OrganizationInvitation (line 41) | type OrganizationInvitation = z.infer<typeof organizationInvitationSchema>;

FILE: src/db/schema/06_project.ts
  type Project (line 29) | type Project = z.infer<typeof projectSchema>;
  type ProjectWith (line 31) | type ProjectWith = Project & {
  type ProjectWithDatabasesAndBackups (line 36) | type ProjectWithDatabasesAndBackups = Project & {

FILE: src/db/schema/07_database.ts
  type Database (line 114) | type Database = z.infer<typeof databaseSchema>;
  type Backup (line 117) | type Backup = z.infer<typeof backupSchema>;
  type Restoration (line 120) | type Restoration = z.infer<typeof restorationSchema>;
  type RetentionPolicy (line 123) | type RetentionPolicy = z.infer<typeof retentionPolicySchema>;
  type DatabaseWith (line 126) | type DatabaseWith = Database & {
  type BackupWith (line 137) | type BackupWith = Backup & {

FILE: src/db/schema/08_agent.ts
  type Agent (line 39) | type Agent = z.infer<typeof agentSchema>;
  type AgentWith (line 59) | type AgentWith = Agent & {
  type AgentWithDatabases (line 67) | type AgentWithDatabases = Agent & {

FILE: src/db/schema/09_notification-channel.ts
  type NotificationChannel (line 52) | type NotificationChannel = z.infer<typeof notificationChannelSchema>;
  type NotificationChannelWith (line 55) | type NotificationChannelWith = NotificationChannel & {

FILE: src/db/schema/10_alert-policy.ts
  type AlertPolicy (line 39) | type AlertPolicy = z.infer<typeof alertPolicySchema>;

FILE: src/db/schema/11_notification-log.ts
  type NotificationLog (line 44) | type NotificationLog = z.infer<typeof notificationLogSchema>;
  type NotificationLevel (line 46) | type NotificationLevel = (typeof levelEnum.enumValues)[number];

FILE: src/db/schema/12_storage-channel.ts
  type StorageChannel (line 49) | type StorageChannel = z.infer<typeof storageChannelSchema>;
  type StorageChannelWith (line 52) | type StorageChannelWith = StorageChannel & {

FILE: src/db/schema/13_storage-policy.ts
  type StoragePolicy (line 33) | type StoragePolicy = z.infer<typeof storagePolicySchema>;
  type StoragePolicyWith (line 36) | type StoragePolicyWith = StoragePolicy & {

FILE: src/db/schema/14_storage-backup.ts
  type BackupStorage (line 44) | type BackupStorage = z.infer<typeof backupStorageSchema>;
  type BackupStorageWith (line 46) | type BackupStorageWith = BackupStorage & {

FILE: src/db/schema/15_healthcheck-log.ts
  type HealthcheckLog (line 20) | type HealthcheckLog = z.infer<typeof healthcheckLogSchema>;
  type HealthcheckKind (line 22) | type HealthcheckKind = (typeof healthcheckKindEnum.enumValues)[number];
  type HealthcheckStatus (line 23) | type HealthcheckStatus = (typeof healthCheckStatusEnum.enumValues)[number];

FILE: src/db/schema/types.ts
  type EDbmsSchema (line 10) | type EDbmsSchema = z.infer<typeof dbmsEnumSchema>;
  type EStatusSchema (line 13) | type EStatusSchema = z.infer<typeof statusEnumSchema>;
  type ETypeStorageSchema (line 16) | type ETypeStorageSchema = z.infer<typeof typeStorageEnumSchema>;

FILE: src/db/services/agent.ts
  function getOrganizationAgents (line 6) | async function getOrganizationAgents(organizationId: string) {

FILE: src/db/services/backup.ts
  function getDatabaseBackups (line 6) | async function getDatabaseBackups(databaseId: string) {

FILE: src/db/services/database.ts
  function getOrganizationAvailableDatabases (line 6) | async function getOrganizationAvailableDatabases(

FILE: src/db/services/healthcheck.ts
  function getHealthLast12hLogs (line 10) | async function getHealthLast12hLogs({id}: { id: string }) {
  function deleteHealthLogsOlderThan12h (line 25) | async function deleteHealthLogsOlderThan12h() {
  function checkAgentsHealthError (line 47) | async function checkAgentsHealthError() {
  function checkDatabasesHealthError (line 110) | async function checkDatabasesHealthError() {

FILE: src/db/services/notification-channel.ts
  function getOrganizationChannels (line 10) | async function getOrganizationChannels(organizationId: string) {

FILE: src/db/services/notification-log.ts
  type NotificationLogWithRelations (line 7) | type NotificationLogWithRelations = {
  function getNotificationHistory (line 28) | async function getNotificationHistory(

FILE: src/db/services/storage-channel.ts
  function getOrganizationStorageChannels (line 5) | async function getOrganizationStorageChannels(organizationId: string) {

FILE: src/db/services/user.ts
  function createUserDb (line 8) | async function createUserDb(data: SignUpUser): Promise<User> {

FILE: src/db/utils/index.ts
  function withUpdatedAt (line 1) | function withUpdatedAt<T extends object>(data: T): T & { updatedAt: Date...

FILE: src/features/agents/agents.schema.ts
  type AgentType (line 9) | type AgentType = z.infer<typeof AgentSchema>;

FILE: src/features/agents/components/agent-organizations.form.tsx
  type AgentOrganisationFormProps (line 15) | type AgentOrganisationFormProps = {

FILE: src/features/agents/components/agent-organizations.schema.ts
  type AgentOrganizationType (line 7) | type AgentOrganizationType = z.infer<typeof AgentOrganizationSchema>;

FILE: src/features/agents/components/agent.dialog.tsx
  type AgentDialogProps (line 22) | type AgentDialogProps = {

FILE: src/features/agents/components/agent.form.tsx
  type agentFormProps (line 23) | type agentFormProps = {

FILE: src/features/browser/theme-meta-updater-root.tsx
  function ThemeMetaUpdaterRoot (line 6) | function ThemeMetaUpdaterRoot() {

FILE: src/features/browser/theme-meta-updater.tsx
  function ThemeMetaUpdater (line 7) | function ThemeMetaUpdater() {

FILE: src/features/dashboard/backup/columns.tsx
  function backupColumns (line 16) | function backupColumns(

FILE: src/features/dashboard/organization-cookie.ts
  constant COOKIE_NAME (line 5) | const COOKIE_NAME = 'PORTABASE_ORGANIZATION_SLUG';
  function getCurrentOrganizationSlug (line 7) | async function getCurrentOrganizationSlug() {
  function setCurrentOrganizationSlug (line 11) | async function setCurrentOrganizationSlug(slug: string) {
  function deleteOrganizationCookie (line 15) | async function deleteOrganizationCookie() {

FILE: src/features/dashboard/restore/columns.tsx
  function restoreColumns (line 28) | function restoreColumns(

FILE: src/features/keys/keys.action.ts
  function getPublicServerKeyContent (line 10) | async function getPublicServerKeyContent() {
  function getMasterServerKeyContent (line 27) | async function getMasterServerKeyContent() {
  function downloadMasterKeyAction (line 41) | async function downloadMasterKeyAction() {

FILE: src/features/layout/card-auth.tsx
  function CardAuth (line 5) | function CardAuth({ className, ...props }: React.ComponentProps<"div">) {

FILE: src/features/notifications/dispatch.ts
  function dispatchNotification (line 11) | async function dispatchNotification(

FILE: src/features/notifications/helpers.ts
  function sendNotificationsBackupRestore (line 8) | async function sendNotificationsBackupRestore(database: DatabaseWith, ev...

FILE: src/features/notifications/providers/discord.ts
  function sendDiscord (line 3) | async function sendDiscord(

FILE: src/features/notifications/providers/gotify.ts
  function sendGotify (line 3) | async function sendGotify(

FILE: src/features/notifications/providers/index.ts
  function dispatchViaProvider (line 24) | async function dispatchViaProvider(

FILE: src/features/notifications/providers/ntfy.ts
  function sendNtfy (line 34) | async function sendNtfy(

FILE: src/features/notifications/providers/slack.ts
  function sendSlack (line 3) | async function sendSlack(

FILE: src/features/notifications/providers/smtp.ts
  function sendSmtp (line 7) | async function sendSmtp(

FILE: src/features/notifications/providers/telegram.ts
  function sendTelegram (line 3) | async function sendTelegram(

FILE: src/features/notifications/providers/webhook.ts
  function sendWebhook (line 3) | async function sendWebhook(

FILE: src/features/notifications/types.ts
  type ProviderKind (line 1) | type ProviderKind = 'slack' | 'smtp' | 'discord' | 'telegram' | 'gotify'...
  type DispatchResult (line 3) | interface DispatchResult {
  type EventPayload (line 12) | interface EventPayload {
  type EventKind (line 21) | type EventKind = ("error_backup" | "error_restore" | "success_restore" |...

FILE: src/features/organization/components/edit-organization.dialog.tsx
  type EditOrganizationDialogProps (line 19) | type EditOrganizationDialogProps = {

FILE: src/features/organization/components/organization.form.tsx
  type organizationFormProps (line 31) | type organizationFormProps = {

FILE: src/features/organization/organization.schema.ts
  type CreateOrganizationType (line 16) | type CreateOrganizationType = z.infer<typeof CreateOrganizationSchema>;
  type UpdateOrganizationType (line 17) | type UpdateOrganizationType = z.infer<typeof UpdateOrganizationSchema>;

FILE: src/features/projects/components/project.dialog.tsx
  type ProjectDialogProps (line 21) | type ProjectDialogProps = {

FILE: src/features/projects/components/project.form.tsx
  type projectFormProps (line 16) | type projectFormProps = {

FILE: src/features/projects/projects.schema.ts
  type ProjectType (line 8) | type ProjectType = z.infer<typeof ProjectSchema>;

FILE: src/features/storages/dispatch.ts
  function dispatchStorage (line 16) | async function dispatchStorage(

FILE: src/features/storages/helpers.ts
  function computeChecksum (line 18) | function computeChecksum(buffer: Buffer): string {
  function storeBackupFiles (line 22) | async function storeBackupFiles(
  function generateFileUrl (line 128) | async function generateFileUrl(input: { data: StorageGetInput | StorageU...

FILE: src/features/storages/providers/google-drive/helpers.ts
  function getGoogleDriveClient (line 6) | async function getGoogleDriveClient(config: GoogleDriveConfig): Promise<...
  function findFileByName (line 25) | async function findFileByName(
  function ensureFolderPath (line 42) | async function ensureFolderPath(client: any, path: string, rootFolderId:...
  function resolveFilePath (line 74) | async function resolveFilePath(client: any, fullPath: string, rootFolder...

FILE: src/features/storages/providers/google-drive/index.ts
  function uploadGoogleDrive (line 19) | async function uploadGoogleDrive(
  function getGoogleDrive (line 78) | async function getGoogleDrive(
  function deleteGoogleDrive (line 121) | async function deleteGoogleDrive(
  function pingGoogleDrive (line 134) | async function pingGoogleDrive(config: GoogleDriveConfig): Promise<Stora...
  function copyGoogleDrive (line 157) | async function copyGoogleDrive(

FILE: src/features/storages/providers/google-drive/types.ts
  type GoogleDriveConfig (line 2) | type GoogleDriveConfig = {

FILE: src/features/storages/providers/index.ts
  type ProviderHandler (line 17) | type ProviderHandler = {
  function dispatchViaProvider (line 49) | async function dispatchViaProvider(

FILE: src/features/storages/providers/local.ts
  constant BASE_DIR (line 17) | const BASE_DIR = path.join(env.PRIVATE_PATH!, "/uploads");
  function uploadLocal (line 19) | async function uploadLocal(
  function getLocal (line 76) | async function getLocal(
  function deleteLocal (line 132) | async function deleteLocal(
  function pingLocal (line 148) | async function pingLocal(config: {
  function copyLocal (line 164) | async function copyLocal(

FILE: src/features/storages/providers/s3.ts
  type S3Config (line 12) | type S3Config = {
  function getS3Client (line 22) | async function getS3Client(config: S3Config) {
  constant BASE_DIR (line 33) | const BASE_DIR = "";
  function ensureBucket (line 36) | async function ensureBucket(config: S3Config) {
  function uploadS3 (line 42) | async function uploadS3(
  function getS3 (line 71) | async function getS3(
  function deleteS3 (line 100) | async function deleteS3(config: S3Config, input: {
  function pingS3 (line 115) | async function pingS3(config: S3Config): Promise<StorageResult> {
  function copyS3 (line 144) | async function copyS3(

FILE: src/features/storages/types.ts
  type StorageProviderKind (line 3) | type StorageProviderKind =
  type StorageAction (line 9) | type StorageAction =
  type StorageFileKind (line 14) | type StorageFileKind =
  type StorageMetaData (line 18) | type StorageMetaData = {
  type StorageUploadInput (line 23) | interface StorageUploadInput {
  type StorageGetInput (line 31) | interface StorageGetInput {
  type StorageDeleteInput (line 37) | interface StorageDeleteInput {
  type StorageCopyInput (line 42) | interface StorageCopyInput {
  type StorageInput (line 47) | type StorageInput =
  type StorageResult (line 54) | interface StorageResult {

FILE: src/features/theme/mode-toggle.tsx
  function ModeToggle (line 21) | function ModeToggle() {

FILE: src/features/theme/theme-provider.tsx
  function ThemeProvider (line 8) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {

FILE: src/features/updates/hooks/use-update-check.ts
  constant DISMISS_KEY (line 8) | const DISMISS_KEY = "portabase-update-dismissed";
  constant DISMISS_DURATION (line 9) | const DISMISS_DURATION = 1000 * 60 * 60 * 24;

FILE: src/features/updates/services/github.ts
  type GitHubRelease (line 1) | interface GitHubRelease {
  type ParsedVersion (line 9) | type ParsedVersion = {
  function parseVersion (line 16) | function parseVersion(version: string): ParsedVersion {

FILE: src/hooks/use-mobile.ts
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768
  function useIsMobile (line 5) | function useIsMobile() {

FILE: src/hooks/use-mobile.tsx
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768
  function useIsMobile (line 5) | function useIsMobile() {

FILE: src/lib/acl/organization-acl.ts
  type OrganizationPermissions (line 4) | type OrganizationPermissions = {

FILE: src/lib/auth/auth.ts
  method sendVerificationEmail (line 76) | async sendVerificationEmail({user, token, url}) {
  method afterEmailVerification (line 94) | async afterEmailVerification(user) {
  method before (line 337) | async before(user, context) {
  method before (line 351) | async before(user, context) {
  method after (line 371) | async after(user, context) {

FILE: src/lib/auth/config.ts
  type AuthProviderConfig (line 6) | interface AuthProviderConfig {
  constant SUPPORTED_PROVIDERS (line 24) | const SUPPORTED_PROVIDERS: AuthProviderConfig[] = [

FILE: src/lib/auth/oauth.ts
  type OAuthProvider (line 3) | interface OAuthProvider {
  constant PROVIDER_ICONS (line 18) | const PROVIDER_ICONS: Record<string, string> = {
  function getProviderIcon (line 44) | function getProviderIcon(providerId: string, envIcon?: string): string {
  function getOAuthProviders (line 56) | function getOAuthProviders(): OAuthProvider[] {

FILE: src/lib/auth/oidc.ts
  type OIDCProvider (line 3) | interface OIDCProvider {
  function getOidcProviders (line 23) | function getOidcProviders(): OIDCProvider[] {

FILE: src/lib/email/types.ts
  type Payload (line 4) | type Payload = {
  type Server (line 11) | type Server = {

FILE: src/lib/logger.ts
  function getLocalTimestamp (line 5) | function getLocalTimestamp() {
  method level (line 29) | level(label) {

FILE: src/lib/safe-actions/actions.ts
  class ActionError (line 4) | class ActionError extends Error {
    method constructor (line 5) | constructor(message: string) {

FILE: src/lib/tasks/database/index.ts
  function enforceRetention (line 33) | async function enforceRetention(

FILE: src/lib/tasks/database/retention-count.ts
  function enforceRetentionCount (line 9) | async function enforceRetentionCount(databaseId: string, count: number) {

FILE: src/lib/tasks/database/retention-days.ts
  function enforceRetentionDays (line 9) | async function enforceRetentionDays(databaseId: string, days: number) {

FILE: src/lib/tasks/database/retention-gsf.ts
  function enforceRetentionGFS (line 10) | async function enforceRetentionGFS(databaseId: string, gfsSettings: {

FILE: src/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {

FILE: src/middleware/loggingMiddleware.ts
  function loggingMiddleware (line 3) | function loggingMiddleware(request: NextRequest) {

FILE: src/types/action-type.ts
  type ServerActionResult (line 4) | type ServerActionResult<T> =
  type ActionErrorMessage (line 11) | type ActionErrorMessage = {
  type ActionSuccessMessage (line 18) | type ActionSuccessMessage = { message?: string; messageParams?: Record<s...
  type ActionResult (line 20) | type ActionResult<T> = SafeActionResult<

FILE: src/types/auth.ts
  type SignUpUser (line 2) | type SignUpUser = {
  type BetterAuthError (line 12) | type BetterAuthError = {

FILE: src/types/common.ts
  type MemberRole (line 1) | type MemberRole = "member" | "admin" | "owner";
  type MemberRoleType (line 2) | type MemberRoleType = MemberRole | MemberRole[];

FILE: src/types/next.ts
  type LayoutParams (line 1) | type LayoutParams<T extends Record<string, string | string[]>> = {
  type PageParams (line 6) | type PageParams<T extends Record<string, string | string[]>> = {

FILE: src/utils/common.ts
  function buildOrganizationWithMembers (line 7) | function buildOrganizationWithMembers(
  function getFileExtension (line 41) | function getFileExtension(dbType: string) {
  function getFileHeadersBasedOnDbms (line 52) | function getFileHeadersBasedOnDbms(dbType: string): Record<string, strin...

FILE: src/utils/date-formatting.ts
  constant TIMEZONE (line 7) | const TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
  constant LOCALE (line 8) | const LOCALE = Intl.DateTimeFormat().resolvedOptions().locale;
  function formatLocalizedDate (line 13) | function formatLocalizedDate(date: string | number | Date) {
  function humanReadableDate (line 26) | function humanReadableDate(rawDate: string | number | Date) {
  function timeAgo (line 30) | function timeAgo(rawDate: string | number | Date) {
  function formatDateLastContact (line 35) | function formatDateLastContact(lastContact: string | number | Date | nul...
  function formatDayOnly (line 41) | function formatDayOnly(date: Date) {
  function getTodayISODate (line 50) | function getTodayISODate() {

FILE: src/utils/detection.ts
  type DeviceDetails (line 3) | interface DeviceDetails {

FILE: src/utils/edge_key.ts
  function generateEdgeKey (line 4) | async function generateEdgeKey(serverUrl: string, agentId: string): Prom...
  function decodeEdgeKey (line 15) | function decodeEdgeKey(edgeKey: string): object {

FILE: src/utils/init.ts
  function init (line 13) | async function init() {
  function setupCronJobs (line 40) | async function setupCronJobs() {
  function createSettingsIfNotExist (line 50) | async function createSettingsIfNotExist() {
  function createDefaultOrganization (line 113) | async function createDefaultOrganization() {
  function consoleAscii (line 134) | function consoleAscii() {

FILE: src/utils/name-from-email.ts
  function extractNameFromEmail (line 1) | function extractNameFromEmail(email: string): string {

FILE: src/utils/os-parser.ts
  function detectOSWithUA (line 1) | function detectOSWithUA(userAgent: string) {

FILE: src/utils/password.ts
  function generateValidPassword (line 1) | function generateValidPassword(length = 12) {

FILE: src/utils/rsa-keys.ts
  function generateRSAKeys (line 22) | async function generateRSAKeys(dir = path.join(env.PRIVATE_PATH!, '/keys...
  function getOrCreateMasterKey (line 56) | async function getOrCreateMasterKey(filePath = path.join(env.PRIVATE_PAT...

FILE: src/utils/text.ts
  function truncateWords (line 1) | function truncateWords(text: string, wordLimit: number = 10): string {
  function capitalizeFirstLetter (line 8) | function capitalizeFirstLetter(text: string): string {
  function isUUID (line 12) | function isUUID(str: string) {
  function isImportedFilename (line 16) | function isImportedFilename(name: string): boolean {
  function formatBytes (line 20) | function formatBytes(bytes: number | null, decimals = 2): string {

FILE: src/utils/verify-uuid.ts
  function isUuidv4 (line 3) | function isUuidv4(value: string): value is string {
Condensed preview — 665 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,188K chars).
[
  {
    "path": ".dockerignore",
    "chars": 289,
    "preview": "# Ignore Docker-related configs\n.dockerignore\ndocker-compose.yml\ndocker-compose*.yml\n\n# Development/Editor config\n.idea\n"
  },
  {
    "path": ".eslintrc.json",
    "chars": 189,
    "preview": "{\n  \"extends\": [\n    \"next/core-web-vitals\",\n    \"plugin:tailwindcss/recommended\"\n  ],\n  \"rules\": {\n    \"react/no-unesca"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 5221,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2611,
    "preview": "---\n\n# Contributing to Portabase\n\nThank you for considering contributing to **Portabase!** 🎉 Contributions help make thi"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 882,
    "preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 834,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 1483,
    "preview": "\n---\n\n# Security Policy\n\n## Supported Versions\n\nWe take security seriously and aim to support the following versions of "
  },
  {
    "path": ".github/workflows/discord.yml",
    "chars": 2190,
    "preview": "name: Discord Notification\n\non:\n  workflow_call:\n    inputs:\n      release_tag:\n        required: true\n        type: str"
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 4301,
    "preview": "name: Docker Publish\n\non:\n  workflow_call:\n    inputs:\n      version:\n        required: true\n        type: string\n      "
  },
  {
    "path": ".github/workflows/e2e.yml",
    "chars": 5242,
    "preview": "name: End-to-end testing\n\non:\n  pull_request:\n    branches: [main]\n\njobs:\n  build-and-test:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".github/workflows/helm.yml",
    "chars": 962,
    "preview": "name: Publish Helm Chart\non:\n  workflow_call:\n    inputs:\n      version:\n        required: true\n        type: string\n   "
  },
  {
    "path": ".github/workflows/release-candidate.yml",
    "chars": 730,
    "preview": "name: Publish Docker image for release candidate\n\non:\n  push:\n    tags:\n      - \"*.*.*-rc*\"\n      - \"!*-*-rc*\"\n\npermissi"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 4048,
    "preview": "name: Auto Release & Publish\n\non:\n  pull_request_target:\n    types: [ closed ]\n    branches:\n      - main\n\npermissions:\n"
  },
  {
    "path": ".github/workflows/security.yml",
    "chars": 753,
    "preview": "name: Security Checks\non:\n  pull_request:\n  push:\n    branches: [ main ]\n\njobs:\n  sca-deps:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".gitignore",
    "chars": 643,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n.idea\n.vscode\n# dependencies\n/node"
  },
  {
    "path": ".gitleaks.toml",
    "chars": 142,
    "preview": "title = \"Custom gitleaks config\"\n\n[allowlist]\n# Global allowlist patterns (won’t be flagged)\nregexes = []\npaths = [\n    "
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 102,
    "preview": "repos:\n  - repo: https://github.com/gitleaks/gitleaks\n    rev: v8.24.2\n    hooks:\n      - id: gitleaks"
  },
  {
    "path": ".release-it.json",
    "chars": 1199,
    "preview": "{\n  \"github\": {\n    \"release\": true,\n    \"draft\": true,\n    \"tokenRef\": \"GITHUB_TOKEN\"\n  },\n  \"git\": {\n    \"commit\": tru"
  },
  {
    "path": "CITATION.cff",
    "chars": 913,
    "preview": "cff-version: 1.2.0\ntitle: Portabase\nmessage: If you use this software, please cite it as below.\ntype: software\nauthors:\n"
  },
  {
    "path": "LICENSE",
    "chars": 11338,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Makefile",
    "chars": 3399,
    "preview": "CLUSTER_SCRIPT=docker/entrypoints/app-dev-entrypoint.sh\n\nup:\n\t@bash $(CLUSTER_SCRIPT)\n\nseed-keycloak:\n\t@[ $$(ls -1 seeds"
  },
  {
    "path": "README.md",
    "chars": 5838,
    "preview": "<br />\n<div align=\"center\">\n  <a href=\"https://portabase.io\">\n    <img src=\"/.github/assets/logo.png\" alt=\"Logo\" width=\""
  },
  {
    "path": "app/(auth)/forgot-password/page.tsx",
    "chars": 1202,
    "preview": "import { CardContent, CardHeader } from \"@/components/ui/card\";\n\nimport { TooltipProvider } from \"@/components/ui/toolti"
  },
  {
    "path": "app/(auth)/guard/page.tsx",
    "chars": 1252,
    "preview": "import {CardContent, CardHeader} from \"@/components/ui/card\";\nimport {TooltipProvider} from \"@/components/ui/tooltip\";\ni"
  },
  {
    "path": "app/(auth)/layout.tsx",
    "chars": 1185,
    "preview": "import React from \"react\";\nimport { redirect } from \"next/navigation\";\nimport { currentUser } from \"@/lib/auth/current-u"
  },
  {
    "path": "app/(auth)/login/page.tsx",
    "chars": 2230,
    "preview": "import { LoginForm } from \"@/components/wrappers/auth/login/login-form/login-form\";\nimport { Metadata } from \"next\";\nimp"
  },
  {
    "path": "app/(auth)/register/page.tsx",
    "chars": 607,
    "preview": "import { PageParams } from \"@/types/next\";\nimport { RegisterForm } from \"@/components/wrappers/auth/register/register-fo"
  },
  {
    "path": "app/(auth)/reset-password/page.tsx",
    "chars": 3007,
    "preview": "import { CardContent, CardHeader } from \"@/components/ui/card\";\nimport { TooltipProvider } from \"@/components/ui/tooltip"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/admin/organizations/[organizationId]/page.tsx",
    "chars": 1714,
    "preview": "import {notFound} from \"next/navigation\";\nimport {eq} from \"drizzle-orm\";\nimport {db} from \"@/db\";\nimport * as drizzleDb"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/admin/organizations/page.tsx",
    "chars": 1282,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageActions, PageContent, PageHeader, PageTitle} from \"@/features"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/admin/settings/page.tsx",
    "chars": 1935,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageContent, PageHeader, PageTitle} from \"@/features/layout/page\""
  },
  {
    "path": "app/(customer)/dashboard/(admin)/admin/users/page.tsx",
    "chars": 1625,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageActions, PageContent, PageHeader, PageTitle} from \"@/features"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/agents/[agentId]/page.tsx",
    "chars": 2680,
    "preview": "import { PageParams } from \"@/types/next\";\nimport {\n  Page,\n  PageContent,\n  PageDescription,\n  PageTitle,\n} from \"@/fea"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/agents/page.tsx",
    "chars": 1681,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {AgentCard} from \"@/components/wrappers/dashboard/agent/agent-card/agent"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/layout.tsx",
    "chars": 372,
    "preview": "import React from \"react\";\nimport { notFound } from \"next/navigation\";\nimport { currentUser } from \"@/lib/auth/current-u"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/notifications/channels/page.tsx",
    "chars": 1718,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageActions, PageContent, PageHeader, PageTitle} from \"@/features"
  },
  {
    "path": "app/(customer)/dashboard/(admin)/notifications/logs/page.tsx",
    "chars": 923,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageContent, PageHeader, PageTitle} from \"@/features/layout/page\""
  },
  {
    "path": "app/(customer)/dashboard/(admin)/storages/channels/page.tsx",
    "chars": 1816,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageActions, PageContent, PageHeader, PageTitle} from \"@/features"
  },
  {
    "path": "app/(customer)/dashboard/(organization)/migration/page.tsx",
    "chars": 2199,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageContent, PageHeader, PageTitle} from \"@/features/layout/page\""
  },
  {
    "path": "app/(customer)/dashboard/(organization)/projects/[projectId]/database/[databaseId]/page.tsx",
    "chars": 4196,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {notFound, redirect} from \"next/navigation\";\nimport {Page} from \"@/featu"
  },
  {
    "path": "app/(customer)/dashboard/(organization)/projects/[projectId]/page.tsx",
    "chars": 4182,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageContent, PageTitle} from \"@/features/layout/page\";\nimport {\n "
  },
  {
    "path": "app/(customer)/dashboard/(organization)/projects/page.tsx",
    "chars": 2621,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {CardsWithPagination} from \"@/components/wrappers/common/cards-with-pagi"
  },
  {
    "path": "app/(customer)/dashboard/(organization)/settings/agents/[agentId]/page.tsx",
    "chars": 3447,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {\n    Page,\n    PageContent,\n    PageDescription,\n    PageTitle,\n} from "
  },
  {
    "path": "app/(customer)/dashboard/(organization)/settings/agents/page.tsx",
    "chars": 139,
    "preview": "import { redirect } from \"next/navigation\";\n\nexport default async function RoutePage() {\n    redirect(\"/dashboard/settin"
  },
  {
    "path": "app/(customer)/dashboard/(organization)/settings/page.tsx",
    "chars": 4968,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {\n    Page,\n    PageContent,\n    PageHeader,\n    PageTitle,\n} from \"@/fe"
  },
  {
    "path": "app/(customer)/dashboard/(organization)/statistics/page.tsx",
    "chars": 5579,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageContent, PageHeader, PageTitle} from \"@/features/layout/page\""
  },
  {
    "path": "app/(customer)/dashboard/home/page.tsx",
    "chars": 6727,
    "preview": "import {PageParams} from \"@/types/next\";\nimport {Page, PageContent, PageHeader, PageTitle} from \"@/features/layout/page\""
  },
  {
    "path": "app/(customer)/dashboard/layout.tsx",
    "chars": 877,
    "preview": "import { ReactNode } from \"react\";\nimport { redirect } from \"next/navigation\";\n\nimport { SidebarInset, SidebarProvider }"
  },
  {
    "path": "app/(customer)/dashboard/loading.tsx",
    "chars": 284,
    "preview": "import { LoadingSpinner } from \"@/components/wrappers/common/loading/loading-spinner\";\n\nexport default function Loading("
  },
  {
    "path": "app/(landing)/home/page.tsx",
    "chars": 325,
    "preview": "export default function Home() {\n    return (\n        <div className=\"grid grid-rows-[20px_1fr_20px] items-center justif"
  },
  {
    "path": "app/(landing)/page.tsx",
    "chars": 491,
    "preview": "import { redirect } from \"next/navigation\";\nimport { getCurrentOrganizationSlug } from \"@/features/dashboard/organizatio"
  },
  {
    "path": "app/api/agent/[agentId]/backup/helpers.ts",
    "chars": 1679,
    "preview": "import {NextResponse} from \"next/server\";\nimport {and, eq} from \"drizzle-orm\";\nimport {db} from \"@/db\";\nimport * as driz"
  },
  {
    "path": "app/api/agent/[agentId]/backup/route.ts",
    "chars": 4811,
    "preview": "import {NextResponse} from \"next/server\";\nimport {and, eq} from \"drizzle-orm\";\nimport * as drizzleDb from \"@/db\";\nimport"
  },
  {
    "path": "app/api/agent/[agentId]/backup/upload/init/route.ts",
    "chars": 2385,
    "preview": "import {NextResponse} from \"next/server\";\nimport {and, eq} from \"drizzle-orm\";\nimport * as drizzleDb from \"@/db\";\nimport"
  },
  {
    "path": "app/api/agent/[agentId]/backup/upload/status/route.ts",
    "chars": 3059,
    "preview": "import {NextResponse} from \"next/server\";\nimport {and, eq} from \"drizzle-orm\";\nimport * as drizzleDb from \"@/db\";\nimport"
  },
  {
    "path": "app/api/agent/[agentId]/restore/route.ts",
    "chars": 2741,
    "preview": "import {NextResponse} from \"next/server\";\nimport {isUuidv4} from \"@/utils/verify-uuid\";\nimport * as drizzleDb from \"@/db"
  },
  {
    "path": "app/api/agent/[agentId]/status/helpers.ts",
    "chars": 10343,
    "preview": "import {NextResponse} from \"next/server\";\nimport {Body} from \"./route\";\nimport {isUuidv4} from \"@/utils/verify-uuid\";\nim"
  },
  {
    "path": "app/api/agent/[agentId]/status/route.ts",
    "chars": 2880,
    "preview": "import {NextResponse} from \"next/server\";\nimport {handleDatabases} from \"./helpers\";\nimport * as drizzleDb from \"@/db\";\n"
  },
  {
    "path": "app/api/auth/[...all]/route.ts",
    "chars": 156,
    "preview": "import { auth } from \"@/lib/auth/auth\";\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\nexport const { GET, POST"
  },
  {
    "path": "app/api/config/route.ts",
    "chars": 256,
    "preview": "import { NextResponse } from \"next/server\";\n\nexport async function GET() {\n  return NextResponse.json({\n    PROJECT_URL:"
  },
  {
    "path": "app/api/events/route.ts",
    "chars": 1507,
    "preview": "import {auth} from \"@/lib/auth/auth\";\nimport {headers} from \"next/headers\";\nimport {NextResponse} from \"next/server\";\nim"
  },
  {
    "path": "app/api/files/backups/route.ts",
    "chars": 2570,
    "preview": "import {NextResponse} from \"next/server\";\nimport path from \"path\";\nimport type {StorageInput} from \"@/features/storages/"
  },
  {
    "path": "app/api/files/images/[fileName]/route.ts",
    "chars": 2881,
    "preview": "import {NextResponse} from \"next/server\";\nimport {auth} from \"@/lib/auth/auth\";\nimport {headers} from \"next/headers\";\nim"
  },
  {
    "path": "app/api/google/drive/callback/route.ts",
    "chars": 535,
    "preview": "export async function GET(request: Request) {\n    const url = new URL(request.url);\n    const code = url.searchParams.ge"
  },
  {
    "path": "app/api/tus/hooks/route.ts",
    "chars": 3185,
    "preview": "import {NextResponse} from \"next/server\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport {env} from \"@/env.mjs\";\ni"
  },
  {
    "path": "app/error/page.tsx",
    "chars": 902,
    "preview": "\"use client\";\n\nimport { Loader2 } from \"lucide-react\";\nimport { useRouter, useSearchParams } from \"next/navigation\";\nimp"
  },
  {
    "path": "app/globals.css",
    "chars": 5400,
    "preview": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n    --color-bac"
  },
  {
    "path": "app/layout.tsx",
    "chars": 1062,
    "preview": "import type { Metadata } from \"next\";\nimport type React from \"react\";\nimport \"./globals.css\";\nimport { ConsoleSilencer }"
  },
  {
    "path": "app/manifest.json",
    "chars": 439,
    "preview": "{\n  \"name\": \"Portabase\",\n  \"short_name\": \"Portabase\",\n  \"icons\": [\n    {\n      \"src\": \"/web-app-manifest-192x192.png\",\n "
  },
  {
    "path": "app/not-found.tsx",
    "chars": 662,
    "preview": "import BackButton from \"@/components/wrappers/common/button/back-button\";\n\nexport default async function NotFound() {\n  "
  },
  {
    "path": "app/providers.tsx",
    "chars": 945,
    "preview": "\"use client\";\n\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { type PropsWithChildren"
  },
  {
    "path": "components.json",
    "chars": 512,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {"
  },
  {
    "path": "docker/dockerfile/Dockerfile",
    "chars": 3092,
    "preview": "FROM --platform=$BUILDPLATFORM node:22-bullseye AS base\n\nRUN apt-get update && apt-get install -y \\\n    curl \\\n    ca-ce"
  },
  {
    "path": "docker/entrypoints/app-dev-entrypoint.sh",
    "chars": 206,
    "preview": "#!/bin/bash\n\nset -euo pipefail\n\necho \"▶ Running Drizzle codegen...\"\npnpm drizzle-kit generate\n\necho \"▶ Applying migratio"
  },
  {
    "path": "docker/entrypoints/app-prod-entrypoint.sh",
    "chars": 2649,
    "preview": "#!/bin/bash\n\nif [ -n \"$TZ\" ]; then\n    echo \"[INFO] Application timezone set to $TZ (environment only)\"\n    export TZ=\"$"
  },
  {
    "path": "docker/nginx/nginx.conf",
    "chars": 1185,
    "preview": "events {}\n\nhttp {\n    client_max_body_size 20G;\n    ignore_invalid_headers  off;\n\n    server {\n        listen 80;\n\n     "
  },
  {
    "path": "docker-compose.e2e.yml",
    "chars": 741,
    "preview": "services:\n  app:\n    build:\n      context: .\n      dockerfile: docker/dockerfile/Dockerfile\n      target: prod\n    ports"
  },
  {
    "path": "docker-compose.func.yml",
    "chars": 930,
    "preview": "name: portabase-dev-func\n\nservices:\n  keycloak:\n    image: quay.io/keycloak/keycloak:latest\n    command: start-dev --imp"
  },
  {
    "path": "docker-compose.prod.yml",
    "chars": 823,
    "preview": "services:\n  app:\n#        build:\n#          context: .\n#          dockerfile: docker/dockerfile/Dockerfile\n#          ta"
  },
  {
    "path": "docker-compose.yml",
    "chars": 794,
    "preview": "name: portabase-dev\n\nservices:\n  db:\n    image: postgres:17-alpine\n    ports:\n      - \"5433:5432\"\n    volumes:\n      - p"
  },
  {
    "path": "drizzle.config.ts",
    "chars": 336,
    "preview": "import { defineConfig } from \"drizzle-kit\";\n\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    path: \".env\",\n});\n\nexport"
  },
  {
    "path": "e2e/access-management.spec.ts",
    "chars": 3594,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {users} from \"./helpers/auth\";\nimport {changeUserRole, create, swi"
  },
  {
    "path": "e2e/agent.spec.ts",
    "chars": 4810,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {execSync} from \"child_process\";\nimport fs from \"fs\";\nimport os fr"
  },
  {
    "path": "e2e/auth.spec.ts",
    "chars": 3868,
    "preview": "import {test, expect} from '@playwright/test';\nimport {login, register, users} from \"./helpers/auth\";\nimport {LOCAL_STOR"
  },
  {
    "path": "e2e/cleanup.spec.ts",
    "chars": 945,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport fs from \"fs\";\nimport {logout} from \"./helpers/auth\";\nimport {LOCAL"
  },
  {
    "path": "e2e/helpers/access-management.ts",
    "chars": 2266,
    "preview": "import {Page} from \"@playwright/test\";\n\nexport function getUserRow(page: Page, email: string) {\n    return page.locator("
  },
  {
    "path": "e2e/helpers/agent-cli.ts",
    "chars": 3442,
    "preview": "import * as pty from \"node-pty\";\n\ntype InteractiveStep = {\n    match: RegExp;\n    reply: string;\n};\n\nfunction normalizeO"
  },
  {
    "path": "e2e/helpers/agent.ts",
    "chars": 2380,
    "preview": "import {Page} from \"@playwright/test\";\n\n\n/**\n * Locate an agent card in the list.\n\n * Executes from: `/dashboard/agents`"
  },
  {
    "path": "e2e/helpers/auth.ts",
    "chars": 1557,
    "preview": "import {Page} from \"@playwright/test\";\n\nexport type UserCredentials = {\n    username: string\n    email: string\n    passw"
  },
  {
    "path": "e2e/helpers/env.ts",
    "chars": 1988,
    "preview": "const REQUIRED_E2E_ENV_VARS = [\n    \"E2E_NOTIFICATION_SMTP_HOST\",\n    \"E2E_NOTIFICATION_SMTP_PORT\",\n    \"E2E_NOTIFICATIO"
  },
  {
    "path": "e2e/helpers/notification.ts",
    "chars": 3175,
    "preview": "import {Page} from \"@playwright/test\";\n\n\n/**\n * Locate a notification channel card in the list.\n *\n * Executes from: `/d"
  },
  {
    "path": "e2e/helpers/project.ts",
    "chars": 1827,
    "preview": "import {Page} from \"@playwright/test\";\n\n\n/**\n * Locate a project card in the list.\n *\n * Executes from: `/dashboard/proj"
  },
  {
    "path": "e2e/helpers/session.ts",
    "chars": 62,
    "preview": "export const LOCAL_STORAGE_PATH = \"./e2e/local-storage.json\";\n"
  },
  {
    "path": "e2e/helpers/storage.ts",
    "chars": 3282,
    "preview": "import {Page} from \"@playwright/test\";\n\n\n/**\n * Locate a storage channel card in the channels list.\n *\n * Executes from:"
  },
  {
    "path": "e2e/notification/discord.spec.ts",
    "chars": 4035,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {\n    cancel, create, get, remove, submit, testFromEdit,\n} from \"."
  },
  {
    "path": "e2e/notification/gotify.spec.ts",
    "chars": 3566,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {\n    cancel, create, get, remove, submit, testFromEdit,\n} from \"."
  },
  {
    "path": "e2e/notification/ntfy.spec.ts",
    "chars": 4155,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {\n    cancel, create, get, remove, submit, testFromEdit,\n} from \"."
  },
  {
    "path": "e2e/notification/slack.spec.ts",
    "chars": 3141,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {\n    cancel, create, get, remove, submit, testFromEdit,\n} from \"."
  },
  {
    "path": "e2e/notification/smtp.spec.ts",
    "chars": 4075,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {getEnv} from \"../helpers/env\";\nimport {\n    cancel, create, get, "
  },
  {
    "path": "e2e/notification/teams.spec.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "e2e/notification/telegram.spec.ts",
    "chars": 4146,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {\n    cancel, create, get, remove, submit, testFromEdit,\n} from \"."
  },
  {
    "path": "e2e/notification/webhook.spec.ts",
    "chars": 3996,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {\n    cancel, create, get, remove, submit, testFromEdit,\n} from \"."
  },
  {
    "path": "e2e/project.spec.ts",
    "chars": 2597,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {create, edit, get, remove} from \"./helpers/project\";\nimport {LOCA"
  },
  {
    "path": "e2e/setup.spec.ts",
    "chars": 394,
    "preview": "import {test} from \"@playwright/test\";\nimport fs from \"fs\";\nimport {LOCAL_STORAGE_PATH} from \"./helpers/session\";\n\ntest."
  },
  {
    "path": "e2e/storage/azure.spec.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "e2e/storage/gcs.spec.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "e2e/storage/google-drive.spec.ts",
    "chars": 3388,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {getEnv} from \"../helpers/env\";\nimport {LOCAL_STORAGE_PATH} from \""
  },
  {
    "path": "e2e/storage/s3.spec.ts",
    "chars": 7182,
    "preview": "import {expect, test} from \"@playwright/test\";\nimport {getEnv} from \"../helpers/env\";\nimport {LOCAL_STORAGE_PATH} from \""
  },
  {
    "path": "eslint.config.mjs",
    "chars": 316,
    "preview": "import {defineConfig, globalIgnores} from 'eslint/config'\nimport nextVitals from 'eslint-config-next/core-web-vitals'\n\nc"
  },
  {
    "path": "helm/.helmignore",
    "chars": 127,
    "preview": ".DS_Store\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n*.swp\n*.bak\n*.tmp\n*.orig\n*~\n.project\n.idea/\n*.tmproj\n.v"
  },
  {
    "path": "helm/Chart.yaml",
    "chars": 624,
    "preview": "apiVersion: v2\nname: portabase\ndescription: Helm chart for Portabase\ntype: application\nversion: 0.0.0\nappVersion: \"lates"
  },
  {
    "path": "helm/README.md",
    "chars": 462,
    "preview": "# Development Notes\n\n## Check that Kubernetes is reachable locally\n\n```bash\nkubectl get nodes\n```\n\n## Install the local "
  },
  {
    "path": "helm/templates/deployment.yaml",
    "chars": 807,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: portabase\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      "
  },
  {
    "path": "helm/templates/pvc.yaml",
    "chars": 189,
    "preview": "apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: portabase-data\nspec:\n  accessModes:\n    - ReadWriteOnce\n  r"
  },
  {
    "path": "helm/templates/service.yaml",
    "chars": 244,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: portabase\nspec:\n  type: {{ .Values.service.type }}\n  selector:\n    app: p"
  },
  {
    "path": "helm/values.yaml",
    "chars": 696,
    "preview": "image:\n  repository: portabase/portabase\n  tag: latest\n  pullPolicy: Always\n\nreplicaCount: 1\n\nservice:\n  type: ClusterIP"
  },
  {
    "path": "instrumentation.ts",
    "chars": 170,
    "preview": "export async function register() {\n    if (process.env.NEXT_RUNTIME === \"nodejs\") {\n        const init = await import(\"@"
  },
  {
    "path": "next.config.ts",
    "chars": 2953,
    "preview": "import type {NextConfig} from \"next\";\nimport {PORTABASE_DEFAULT_SETTINGS} from \"./portabase.config\";\n\nconst isDev = proc"
  },
  {
    "path": "package.json",
    "chars": 4626,
    "preview": "{\n  \"name\": \"portabase\",\n  \"version\": \"1.13.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack -p 88"
  },
  {
    "path": "playwright.config.ts",
    "chars": 2316,
    "preview": "import { defineConfig, devices } from '@playwright/test';\nimport dotenv from 'dotenv';\nimport path from 'path';\n\n/**\n * "
  },
  {
    "path": "pnpm-workspace.yaml",
    "chars": 232,
    "preview": "ignoredBuiltDependencies:\n  - node-pty\nonlyBuiltDependencies:\n  - '@prisma/client'\n  - '@prisma/engines'\n  - argon2\n  - "
  },
  {
    "path": "portabase.config.ts",
    "chars": 2053,
    "preview": "\n\nexport const PORTABASE_DEFAULT_SETTINGS = {\n    SECURITY: {\n        CSP: {\n            DEFAULT_SRC: [\"'self'\"],\n      "
  },
  {
    "path": "postcss.config.mjs",
    "chars": 146,
    "preview": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n}"
  },
  {
    "path": "proxy.ts",
    "chars": 2489,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { loggingMiddleware } from \"@/middleware/loggingMiddlewa"
  },
  {
    "path": "seeds/keycloak/master-realm.json",
    "chars": 82952,
    "preview": "{\n  \"id\" : \"fb9cf3e7-ba35-48d2-b40e-8a48a98f8acd\",\n  \"realm\" : \"master\",\n  \"displayName\" : \"Portabase\",\n  \"displayNameHt"
  },
  {
    "path": "src/components/emails/auth/email-forgot-password.tsx",
    "chars": 1599,
    "preview": "import * as React from \"react\";\nimport EmailLayout from \"../email-layout\";\nimport {Heading, Text, Section, Button} from "
  },
  {
    "path": "src/components/emails/auth/email-new-login.tsx",
    "chars": 1060,
    "preview": "import * as React from \"react\";\nimport EmailLayout from \"../email-layout\";\nimport {Heading, Text} from \"@react-email/com"
  },
  {
    "path": "src/components/emails/auth/email-verification.tsx",
    "chars": 2099,
    "preview": "import {Heading, Text, Section, Button} from \"@react-email/components\";\nimport {getServerUrl} from \"@/utils/get-server-u"
  },
  {
    "path": "src/components/emails/email-create-user.tsx",
    "chars": 1674,
    "preview": "import * as React from \"react\";\nimport EmailLayout from \"./email-layout\";\nimport {Heading, Text, Section, Button} from \""
  },
  {
    "path": "src/components/emails/email-layout.tsx",
    "chars": 1334,
    "preview": "import { Body, Container, Head, Html, Img, Preview, Section, Tailwind } from \"@react-email/components\";\nimport * as Reac"
  },
  {
    "path": "src/components/emails/email-notification.tsx",
    "chars": 3222,
    "preview": "// import * as React from \"react\";\n// import EmailLayout from \"./email-layout\";\n// import {Text, Section, Button} from \""
  },
  {
    "path": "src/components/emails/email-settings-test.tsx",
    "chars": 561,
    "preview": "import { Text } from \"@react-email/components\";\nimport * as React from \"react\";\nimport EmailLayout from \"./email-layout\""
  },
  {
    "path": "src/components/emails/email-text.tsx",
    "chars": 304,
    "preview": "import {Text as ReactEmailText} from \"@react-email/components\";\nimport {ComponentPropsWithoutRef} from \"react\";\n\nexport "
  },
  {
    "path": "src/components/layout.tsx",
    "chars": 449,
    "preview": "import { twx } from \"@/lib/twx\";\nimport { cn } from \"@/lib/utils\";\n\nexport const LayoutAdmin = twx.div((props) => [`w-fu"
  },
  {
    "path": "src/components/ui/accordion.tsx",
    "chars": 2053,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\"\nimport { Ch"
  },
  {
    "path": "src/components/ui/alert-dialog.tsx",
    "chars": 3864,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\"\n\nimpor"
  },
  {
    "path": "src/components/ui/alert.tsx",
    "chars": 1614,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "src/components/ui/aspect-ratio.tsx",
    "chars": 280,
    "preview": "\"use client\"\n\nimport * as AspectRatioPrimitive from \"@radix-ui/react-aspect-ratio\"\n\nfunction AspectRatio({\n  ...props\n}:"
  },
  {
    "path": "src/components/ui/avatar.tsx",
    "chars": 1097,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\n\nimport { cn } fr"
  },
  {
    "path": "src/components/ui/badge.tsx",
    "chars": 1631,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class"
  },
  {
    "path": "src/components/ui/breadcrumb.tsx",
    "chars": 2381,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cn } from \"@/lib/utils\"\nimport { Che"
  },
  {
    "path": "src/components/ui/button.tsx",
    "chars": 2195,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class"
  },
  {
    "path": "src/components/ui/calendar.tsx",
    "chars": 3278,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { DayPicker } from \"react-day-picker\";\n\nimport { cn } from \"@/lib/"
  },
  {
    "path": "src/components/ui/card.tsx",
    "chars": 1989,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Card({ className, ...props }: React.Component"
  },
  {
    "path": "src/components/ui/carousel.tsx",
    "chars": 5580,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport useEmblaCarousel, {\n  type UseEmblaCarouselType,\n} from \"embla-carou"
  },
  {
    "path": "src/components/ui/chart.tsx",
    "chars": 9781,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RechartsPrimitive from \"recharts\"\n\nimport { cn } from \"@/lib/ut"
  },
  {
    "path": "src/components/ui/checkbox.tsx",
    "chars": 1226,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Chec"
  },
  {
    "path": "src/components/ui/collapsible.tsx",
    "chars": 800,
    "preview": "\"use client\"\n\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\"\n\nfunction Collapsible({\n  ...props\n}: "
  },
  {
    "path": "src/components/ui/command.tsx",
    "chars": 4986,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DialogProps } from \"@radix-ui/react-dialog\"\nimport { Magnifyi"
  },
  {
    "path": "src/components/ui/context-menu.tsx",
    "chars": 8222,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\"\nimport"
  },
  {
    "path": "src/components/ui/dialog.tsx",
    "chars": 4510,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { XIcon } "
  },
  {
    "path": "src/components/ui/drawer.tsx",
    "chars": 4072,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Drawer as DrawerPrimitive } from \"vaul\"\n\nimport { cn } from \"@/lib"
  },
  {
    "path": "src/components/ui/dropdown-menu.tsx",
    "chars": 8284,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimpo"
  },
  {
    "path": "src/components/ui/dropzone.tsx",
    "chars": 25948,
    "preview": "\"use client\"\nimport {cn} from \"@/lib/utils\";\nimport {\n    createContext,\n    forwardRef,\n    useCallback,\n    useContext"
  },
  {
    "path": "src/components/ui/form.tsx",
    "chars": 6163,
    "preview": "import {cn} from \"@/lib/utils\";\nimport {zodResolver} from \"@hookform/resolvers/zod\";\nimport type * as LabelPrimitive fro"
  },
  {
    "path": "src/components/ui/github-button.tsx",
    "chars": 9755,
    "preview": "'use client';\n\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { cva, type VariantProps } from '"
  },
  {
    "path": "src/components/ui/hover-card.tsx",
    "chars": 1532,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as HoverCardPrimitive from \"@radix-ui/react-hover-card\"\n\nimport { "
  },
  {
    "path": "src/components/ui/input-otp.tsx",
    "chars": 2254,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { OTPInput, OTPInputContext } from \"input-otp\"\nimport { MinusIcon } "
  },
  {
    "path": "src/components/ui/input.tsx",
    "chars": 963,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.Co"
  },
  {
    "path": "src/components/ui/label.tsx",
    "chars": 611,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from"
  },
  {
    "path": "src/components/ui/menubar.tsx",
    "chars": 8394,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as MenubarPrimitive from \"@radix-ui/react-menubar\"\nimport { CheckI"
  },
  {
    "path": "src/components/ui/navigation-menu.tsx",
    "chars": 6664,
    "preview": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva }"
  },
  {
    "path": "src/components/ui/pagination.tsx",
    "chars": 2712,
    "preview": "import * as React from \"react\"\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  MoreHorizontalIcon,\n} from \"lucide-reac"
  },
  {
    "path": "src/components/ui/password-input-indicator.tsx",
    "chars": 6533,
    "preview": "\"use client\";\n\nimport {Ref, useEffect, useState} from \"react\";\nimport {Check, X} from \"lucide-react\";\nimport {motion, An"
  },
  {
    "path": "src/components/ui/password-input.tsx",
    "chars": 1672,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { EyeIcon, EyeOffIcon } from \"lucide-react\";\nimport { Button } fro"
  },
  {
    "path": "src/components/ui/popover.tsx",
    "chars": 1635,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } "
  },
  {
    "path": "src/components/ui/progress.tsx",
    "chars": 740,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\"\n\nimport { cn "
  },
  {
    "path": "src/components/ui/radio-group.tsx",
    "chars": 1466,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\"\nimport {"
  },
  {
    "path": "src/components/ui/resizable.tsx",
    "chars": 2028,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { GripVerticalIcon } from \"lucide-react\"\nimport * as ResizablePrimit"
  },
  {
    "path": "src/components/ui/scroll-area.tsx",
    "chars": 1645,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport "
  },
  {
    "path": "src/components/ui/search-input.tsx",
    "chars": 7177,
    "preview": "\"use client\"\n\nimport type React from \"react\"\nimport { useState, useRef, useEffect, forwardRef } from \"react\"\nimport { Se"
  },
  {
    "path": "src/components/ui/select.tsx",
    "chars": 6253,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { CheckIco"
  },
  {
    "path": "src/components/ui/separator.tsx",
    "chars": 704,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { c"
  },
  {
    "path": "src/components/ui/sheet.tsx",
    "chars": 4991,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\"\nimport { XIcon } f"
  },
  {
    "path": "src/components/ui/sidebar.tsx",
    "chars": 21648,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { VariantProps, cva } fr"
  },
  {
    "path": "src/components/ui/skeleton.tsx",
    "chars": 276,
    "preview": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n "
  },
  {
    "path": "src/components/ui/slider.tsx",
    "chars": 2001,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } fr"
  },
  {
    "path": "src/components/ui/sliding-number.tsx",
    "chars": 7519,
    "preview": "'use client';\n\nimport * as React from 'react';\nimport {\n    useSpring,\n    useTransform,\n    motion,\n    useInView,\n    "
  },
  {
    "path": "src/components/ui/sonner.tsx",
    "chars": 564,
    "preview": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, ToasterProps } from \"sonner\"\n\nconst Toa"
  },
  {
    "path": "src/components/ui/switch.tsx",
    "chars": 1177,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\"\n\nimport { cn } fr"
  },
  {
    "path": "src/components/ui/table.tsx",
    "chars": 2448,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Table({ className, ...props }: "
  },
  {
    "path": "src/components/ui/tabs.tsx",
    "chars": 1969,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \""
  },
  {
    "path": "src/components/ui/textarea.tsx",
    "chars": 759,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Textarea({ className, ...props }: React.Compo"
  },
  {
    "path": "src/components/ui/toast.tsx",
    "chars": 4861,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Cross2Icon } from \"@radix-ui/react-icons\"\nimport * as ToastPrimiti"
  },
  {
    "path": "src/components/ui/toaster.tsx",
    "chars": 786,
    "preview": "\"use client\"\n\nimport { useToast } from \"@/hooks/use-toast\"\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastPr"
  },
  {
    "path": "src/components/ui/toggle-group.tsx",
    "chars": 1925,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ToggleGroupPrimitive from \"@radix-ui/react-toggle-group\"\nimport"
  },
  {
    "path": "src/components/ui/toggle.tsx",
    "chars": 1570,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, typ"
  },
  {
    "path": "src/components/ui/tooltip.tsx",
    "chars": 1891,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } "
  },
  {
    "path": "src/components/wrappers/auth/auth-logo-section.tsx",
    "chars": 1174,
    "preview": "\"use client\";\n\nimport {env} from \"@/env.mjs\";\nimport {useTheme} from \"next-themes\";\nimport Image from \"next/image\";\nimpo"
  },
  {
    "path": "src/components/wrappers/auth/guard/guard-form.tsx",
    "chars": 711,
    "preview": "\"use client\";\n\nimport { toast } from \"sonner\";\nimport { useRouter, useSearchParams } from \"next/navigation\";\nimport TwoF"
  },
  {
    "path": "src/components/wrappers/auth/login/forgot-password-form/forgot-password-form.schema.ts",
    "chars": 221,
    "preview": "\"use client\";\n\nimport { z } from \"zod\";\nimport { zEmail } from \"@/lib/zod\";\n\nexport const ForgotPasswordSchema = z.objec"
  },
  {
    "path": "src/components/wrappers/auth/login/forgot-password-form/forgot-password-form.tsx",
    "chars": 2895,
    "preview": "\"use client\";\n\nimport { useMutation } from \"@tanstack/react-query\";\nimport { toast } from \"sonner\";\n\nimport { FormContro"
  },
  {
    "path": "src/components/wrappers/auth/login/forgot-password-form/forgot-password.actions.ts",
    "chars": 2445,
    "preview": "\"use server\";\nimport { ServerActionResult } from \"@/types/action-type\";\nimport { auth } from \"@/lib/auth/auth\";\nimport {"
  },
  {
    "path": "src/components/wrappers/auth/login/login-form/login-form.schema.ts",
    "chars": 239,
    "preview": "\"use client\";\n\nimport { z } from \"zod\";\nimport { zEmail, zString } from \"@/lib/zod\";\n\nexport const LoginSchema = z.objec"
  },
  {
    "path": "src/components/wrappers/auth/login/login-form/login-form.tsx",
    "chars": 3545,
    "preview": "\"use client\";\n\nimport { useMutation } from \"@tanstack/react-query\";\nimport Link from \"next/link\";\nimport { useRouter, us"
  },
  {
    "path": "src/components/wrappers/auth/login/reset-password-form/reset-password-form.action.ts",
    "chars": 2012,
    "preview": "\"use server\";\nimport { ServerActionResult } from \"@/types/action-type\";\nimport { auth } from \"@/lib/auth/auth\";\nimport {"
  },
  {
    "path": "src/components/wrappers/auth/login/reset-password-form/reset-password-form.schema.ts",
    "chars": 575,
    "preview": "\"use client\";\n\nimport {z} from \"zod\";\nimport {zPassword} from \"@/lib/zod\";\n\nexport const ResetPasswordSchema = z\n    .ob"
  },
  {
    "path": "src/components/wrappers/auth/login/reset-password-form/reset-password-form.tsx",
    "chars": 3376,
    "preview": "\"use client\";\n\nimport {useMutation} from \"@tanstack/react-query\";\nimport {toast} from \"sonner\";\n\nimport {FormControl, Fo"
  },
  {
    "path": "src/components/wrappers/auth/register/register-form/register-form.schema.ts",
    "chars": 1011,
    "preview": "import { z } from \"zod\";\n\n// Minimum 8 characters, at least one uppercase letter, one lowercase letter, one number and o"
  }
]

// ... and 465 more files (download for full content)

About this extraction

This page contains the full source code of the Portabase/portabase GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 665 files (4.5 MB), approximately 1.2M tokens, and a symbol index with 920 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.

Copied to clipboard!