├── .dockerignore
├── .env.example
├── .env.test
├── .env.test.basic
├── .gitignore
├── .goreleaser.yaml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE.md
├── Makefile
├── README.md
├── apis
├── admin.go
├── admin_test.go
├── api_error.go
├── api_error_test.go
├── backup.go
├── backup_test.go
├── base.go
├── base_test.go
├── collection.go
├── collection_test.go
├── file.go
├── file_test.go
├── health.go
├── health_test.go
├── logs.go
├── logs_test.go
├── middlewares.go
├── middlewares_test.go
├── realtime.go
├── realtime_test.go
├── record_auth.go
├── record_auth_test.go
├── record_crud.go
├── record_crud_test.go
├── record_helpers.go
├── record_helpers_test.go
├── serve.go
├── settings.go
└── settings_test.go
├── cmd
├── admin.go
├── admin_test.go
└── serve.go
├── core
├── app.go
├── base.go
├── base_backup.go
├── base_backup_test.go
├── base_settings_test.go
├── base_test.go
├── collections_cache.go
├── db_pg.go
├── events.go
├── events_test.go
└── log_printer.go
├── daos
├── admin.go
├── admin_test.go
├── base.go
├── base_retry.go
├── base_retry_test.go
├── base_test.go
├── collection.go
├── collection_test.go
├── external_auth.go
├── external_auth_test.go
├── log.go
├── log_test.go
├── param.go
├── param_test.go
├── record.go
├── record_expand.go
├── record_expand_test.go
├── record_table_sync.go
├── record_table_sync_test.go
├── record_test.go
├── settings.go
├── settings_test.go
├── table.go
├── table_test.go
├── view.go
└── view_test.go
├── docker-compose.yaml
├── drop.sh
├── examples
└── base
│ ├── .gitignore
│ └── main.go
├── forms
├── admin_login.go
├── admin_login_test.go
├── admin_password_reset_confirm.go
├── admin_password_reset_confirm_test.go
├── admin_password_reset_request.go
├── admin_password_reset_request_test.go
├── admin_upsert.go
├── admin_upsert_test.go
├── apple_client_secret_create.go
├── apple_client_secret_create_test.go
├── backup_create.go
├── backup_create_test.go
├── backup_upload.go
├── backup_upload_test.go
├── base.go
├── collection_upsert.go
├── collection_upsert_test.go
├── collections_import.go
├── collections_import_test.go
├── realtime_subscribe.go
├── realtime_subscribe_test.go
├── record_email_change_confirm.go
├── record_email_change_confirm_test.go
├── record_email_change_request.go
├── record_email_change_request_test.go
├── record_oauth2_login.go
├── record_oauth2_login_test.go
├── record_password_login.go
├── record_password_login_test.go
├── record_password_reset_confirm.go
├── record_password_reset_confirm_test.go
├── record_password_reset_request.go
├── record_password_reset_request_test.go
├── record_upsert.go
├── record_upsert_test.go
├── record_verification_confirm.go
├── record_verification_confirm_test.go
├── record_verification_request.go
├── record_verification_request_test.go
├── settings_upsert.go
├── settings_upsert_test.go
├── test_email_send.go
├── test_email_send_test.go
├── test_s3_filesystem.go
├── test_s3_filesystem_test.go
└── validators
│ ├── file.go
│ ├── file_test.go
│ ├── model.go
│ ├── model_test.go
│ ├── record_data.go
│ ├── record_data_test.go
│ ├── string.go
│ ├── string_test.go
│ └── validators.go
├── go.mod
├── go.sum
├── golangci.yml
├── keys
└── .gitignore
├── mails
├── admin.go
├── admin_test.go
├── base.go
├── record.go
├── record_test.go
└── templates
│ ├── admin_password_reset.go
│ ├── html_content.go
│ ├── layout.go
│ └── password_login_alert.go
├── migrations
├── 1640988000_init.go
├── 1673167670_multi_match_migrate.go
├── 1677152688_rename_authentik_to_oidc.go
├── 1679943780_normalize_single_multiple_values.go
├── 1679943781_add_indexes_column.go
├── 1685164450_check_fk.go
├── 1689579878_renormalize_single_multiple_values.go
├── 1690319366_reset_null_values.go
├── 1690454337_transform_relations_to_views.go
├── 1691747913_resave_views.go
├── 1692609521_copy_display_fields.go
├── 1701496825_allow_single_oauth2_provider_in_multiple_auth_collections.go
├── 1702134272_set_default_json_max_size.go
├── 1718706525_add_login_alert_column.go
└── logs
│ ├── 1640988000_init.go
│ ├── 1660821103_add_user_ip_column.go
│ ├── 1677760279_uppsercase_method.go
│ └── 1699187560_logs_generalization.go
├── models
├── admin.go
├── admin_test.go
├── backup_file_info.go
├── base.go
├── base_test.go
├── collection.go
├── collection_test.go
├── external_auth.go
├── external_auth_test.go
├── log.go
├── param.go
├── param_test.go
├── record.go
├── record_test.go
├── request.go
├── request_info.go
├── request_info_test.go
├── request_test.go
├── schema
│ ├── schema.go
│ ├── schema_field.go
│ ├── schema_field_test.go
│ └── schema_test.go
├── settings
│ ├── settings.go
│ ├── settings_templates.go
│ └── settings_test.go
└── table_info.go
├── plugins
├── ghupdate
│ ├── ghupdate.go
│ ├── ghupdate_test.go
│ ├── release.go
│ └── release_test.go
├── jsvm
│ ├── binds.go
│ ├── binds_test.go
│ ├── form_data.go
│ ├── form_data_test.go
│ ├── internal
│ │ └── types
│ │ │ ├── generated
│ │ │ ├── embed.go
│ │ │ └── types.d.ts
│ │ │ └── types.go
│ ├── jsvm.go
│ ├── mapper.go
│ ├── mapper_test.go
│ └── pool.go
└── migratecmd
│ ├── automigrate.go
│ ├── migratecmd.go
│ ├── migratecmd_test.go
│ └── templates.go
├── resolvers
├── multi_match_subquery.go
├── record_field_resolve_runner.go
├── record_field_resolver.go
├── record_field_resolver_test.go
└── resolvers.go
├── rocketbase.go
├── rocketbase_test.go
├── test_sh.sh
├── tests
├── api.go
├── app.go
├── auth_helper_test.go
├── data
│ ├── .gitignore
│ ├── empty.file
│ └── storage
│ │ ├── 9n89pl5vkct6330
│ │ ├── la4y2w4o98acwuj
│ │ │ ├── 300_uh_lkx91_hvb_Da8K5pl069.png
│ │ │ ├── 300_uh_lkx91_hvb_Da8K5pl069.png.attrs
│ │ │ └── thumbs_300_uh_lkx91_hvb_Da8K5pl069.png
│ │ │ │ ├── 100x100_300_uh_lkx91_hvb_Da8K5pl069.png
│ │ │ │ └── 100x100_300_uh_lkx91_hvb_Da8K5pl069.png.attrs
│ │ └── qjeql998mtp1azp
│ │ │ ├── logo_vcf_jjg5_tah_9MtIHytOmZ.svg
│ │ │ └── logo_vcf_jjg5_tah_9MtIHytOmZ.svg.attrs
│ │ ├── _pb_users_auth_
│ │ ├── 4q1xlclmfloku33
│ │ │ ├── 300_1SEi6Q6U72.png
│ │ │ ├── 300_1SEi6Q6U72.png.attrs
│ │ │ └── thumbs_300_1SEi6Q6U72.png
│ │ │ │ ├── 0x50_300_1SEi6Q6U72.png
│ │ │ │ ├── 0x50_300_1SEi6Q6U72.png.attrs
│ │ │ │ ├── 100x100_300_1SEi6Q6U72.png
│ │ │ │ ├── 100x100_300_1SEi6Q6U72.png.attrs
│ │ │ │ ├── 70x0_300_1SEi6Q6U72.png
│ │ │ │ ├── 70x0_300_1SEi6Q6U72.png.attrs
│ │ │ │ ├── 70x50_300_1SEi6Q6U72.png
│ │ │ │ ├── 70x50_300_1SEi6Q6U72.png.attrs
│ │ │ │ ├── 70x50b_300_1SEi6Q6U72.png
│ │ │ │ ├── 70x50b_300_1SEi6Q6U72.png.attrs
│ │ │ │ ├── 70x50f_300_1SEi6Q6U72.png
│ │ │ │ ├── 70x50f_300_1SEi6Q6U72.png.attrs
│ │ │ │ ├── 70x50t_300_1SEi6Q6U72.png
│ │ │ │ └── 70x50t_300_1SEi6Q6U72.png.attrs
│ │ └── oap640cot4yru2s
│ │ │ ├── test_kfd2wYLxkz.txt
│ │ │ └── test_kfd2wYLxkz.txt.attrs
│ │ ├── wsmn24bux7wo113
│ │ ├── 84nmscqy84lsi1t
│ │ │ ├── 300_WlbFWSGmW9.png
│ │ │ ├── 300_WlbFWSGmW9.png.attrs
│ │ │ ├── logo_vcfJJG5TAh.svg
│ │ │ ├── logo_vcfJJG5TAh.svg.attrs
│ │ │ ├── test_MaWC6mWyrP.txt
│ │ │ ├── test_MaWC6mWyrP.txt.attrs
│ │ │ ├── test_QZFjKjXchk.txt
│ │ │ ├── test_QZFjKjXchk.txt.attrs
│ │ │ ├── test_d61b33QdDU.txt
│ │ │ ├── test_d61b33QdDU.txt.attrs
│ │ │ ├── test_tC1Yc87DfC.txt
│ │ │ ├── test_tC1Yc87DfC.txt.attrs
│ │ │ └── thumbs_300_WlbFWSGmW9.png
│ │ │ │ ├── 100x100_300_WlbFWSGmW9.png
│ │ │ │ └── 100x100_300_WlbFWSGmW9.png.attrs
│ │ └── al1h9ijdeojtsjy
│ │ │ ├── 300_Jsjq7RdBgA.png
│ │ │ ├── 300_Jsjq7RdBgA.png.attrs
│ │ │ └── thumbs_300_Jsjq7RdBgA.png
│ │ │ ├── 100x100_300_Jsjq7RdBgA.png
│ │ │ └── 100x100_300_Jsjq7RdBgA.png.attrs
│ │ └── wzlqyes4orhoygb
│ │ ├── 7nwo8tuiatetxdm
│ │ ├── test_JnXeKEwgwr.txt
│ │ └── test_JnXeKEwgwr.txt.attrs
│ │ ├── lcl9d87w22ml6jy
│ │ ├── 300_UhLKX91HVb.png
│ │ ├── 300_UhLKX91HVb.png.attrs
│ │ ├── test_FLurQTgrY8.txt
│ │ ├── test_FLurQTgrY8.txt.attrs
│ │ └── thumbs_300_UhLKX91HVb.png
│ │ │ ├── 100x100_300_UhLKX91HVb.png
│ │ │ └── 100x100_300_UhLKX91HVb.png.attrs
│ │ └── mk5fmymtx4wsprk
│ │ ├── 300_JdfBOieXAW.png
│ │ ├── 300_JdfBOieXAW.png.attrs
│ │ └── thumbs_300_JdfBOieXAW.png
│ │ ├── 100x100_300_JdfBOieXAW.png
│ │ └── 100x100_300_JdfBOieXAW.png.attrs
├── logs.go
├── mailer.go
└── request.go
├── tokens
├── admin.go
├── admin_test.go
├── record.go
├── record_test.go
└── tokens.go
├── tools
├── archive
│ ├── create.go
│ ├── create_test.go
│ ├── extract.go
│ └── extract_test.go
├── auth
│ ├── apple.go
│ ├── auth.go
│ ├── auth_test.go
│ ├── base_provider.go
│ ├── base_provider_test.go
│ ├── bitbucket.go
│ ├── discord.go
│ ├── facebook.go
│ ├── gitea.go
│ ├── gitee.go
│ ├── github.go
│ ├── gitlab.go
│ ├── google.go
│ ├── instagram.go
│ ├── kakao.go
│ ├── livechat.go
│ ├── mailcow.go
│ ├── microsoft.go
│ ├── oidc.go
│ ├── patreon.go
│ ├── planningcenter.go
│ ├── spotify.go
│ ├── strava.go
│ ├── twitch.go
│ ├── twitter.go
│ ├── vk.go
│ └── yandex.go
├── cron
│ ├── cron.go
│ ├── cron_test.go
│ ├── schedule.go
│ └── schedule_test.go
├── dbutils
│ ├── index.go
│ ├── index_test.go
│ ├── json.go
│ └── json_test.go
├── filesystem
│ ├── file.go
│ ├── file_test.go
│ ├── filesystem.go
│ ├── filesystem_test.go
│ ├── ignore_signing_headers.go
│ └── internal
│ │ └── s3lite
│ │ └── s3lite.go
├── hook
│ ├── hook.go
│ ├── hook_test.go
│ ├── tagged.go
│ └── tagged_test.go
├── inflector
│ ├── inflector.go
│ └── inflector_test.go
├── list
│ ├── list.go
│ └── list_test.go
├── logger
│ ├── batch_handler.go
│ ├── batch_handler_test.go
│ └── log.go
├── mailer
│ ├── html2text.go
│ ├── html2text_test.go
│ ├── mailer.go
│ ├── sendmail.go
│ ├── smtp.go
│ └── smtp_test.go
├── migrate
│ ├── list.go
│ ├── list_test.go
│ ├── runner.go
│ └── runner_test.go
├── osutils
│ ├── dir.go
│ └── dir_test.go
├── rest
│ ├── excerpt_modifier.go
│ ├── excerpt_modifier_test.go
│ ├── json_serializer.go
│ ├── json_serializer_test.go
│ ├── multi_binder.go
│ ├── multi_binder_test.go
│ ├── uploaded_file.go
│ ├── uploaded_file_test.go
│ ├── url.go
│ └── url_test.go
├── routine
│ ├── routine.go
│ └── routine_test.go
├── search
│ ├── filter.go
│ ├── filter_test.go
│ ├── identifier_macros.go
│ ├── identifier_macros_test.go
│ ├── provider.go
│ ├── provider_test.go
│ ├── simple_field_resolver.go
│ ├── simple_field_resolver_test.go
│ ├── sort.go
│ └── sort_test.go
├── security
│ ├── crypto.go
│ ├── crypto_test.go
│ ├── encrypt.go
│ ├── encrypt_test.go
│ ├── jwt.go
│ ├── jwt_test.go
│ ├── random.go
│ └── random_test.go
├── store
│ ├── store.go
│ └── store_test.go
├── subscriptions
│ ├── broker.go
│ ├── broker_test.go
│ ├── client.go
│ └── client_test.go
├── template
│ ├── registry.go
│ ├── registry_test.go
│ ├── renderer.go
│ └── renderer_test.go
├── test_utils
│ └── test_utils.go
├── tokenizer
│ ├── tokenizer.go
│ └── tokenizer_test.go
└── types
│ ├── datetime.go
│ ├── datetime_test.go
│ ├── json_array.go
│ ├── json_array_test.go
│ ├── json_map.go
│ ├── json_map_test.go
│ ├── json_raw.go
│ ├── json_raw_test.go
│ ├── types.go
│ └── types_test.go
├── ui
├── .env
├── .env.development
├── .gitignore
├── README.md
├── dist
│ ├── assets
│ │ ├── AuthMethodsDocs-CrEPr1Fz.js
│ │ ├── AuthRefreshDocs-1k07W-sL.js
│ │ ├── AuthWithOAuth2Docs-6Svn1qsJ.js
│ │ ├── AuthWithPasswordDocs-Deuu1l41.js
│ │ ├── CodeEditor-CoTpL2eu.js
│ │ ├── ConfirmEmailChangeDocs-DiV0Y4lP.js
│ │ ├── ConfirmPasswordResetDocs-a6y5hSaH.js
│ │ ├── ConfirmVerificationDocs-zC3h5AjK.js
│ │ ├── CreateApiDocs-BhzDfmVz.js
│ │ ├── DeleteApiDocs-DgdFM5Qa.js
│ │ ├── FieldsQueryParam-BoYS20e2.js
│ │ ├── FilterAutocompleteInput-C6aEn8iB.js
│ │ ├── ListApiDocs-B8tjPuF-.js
│ │ ├── ListApiDocs-ByASLUZu.css
│ │ ├── ListExternalAuthsDocs-CeO88Sbf.js
│ │ ├── PageAdminConfirmPasswordReset-Dp7faOXH.js
│ │ ├── PageAdminRequestPasswordReset-MZKeemZH.js
│ │ ├── PageOAuth2RedirectFailure-BS8Cmo1H.js
│ │ ├── PageOAuth2RedirectSuccess-D1_hRUhp.js
│ │ ├── PageRecordConfirmEmailChange-DnPlwx9F.js
│ │ ├── PageRecordConfirmPasswordReset-Xbuf0XcM.js
│ │ ├── PageRecordConfirmVerification-DA7_iPCH.js
│ │ ├── RealtimeApiDocs-DIqCMSA9.js
│ │ ├── RequestEmailChangeDocs-C-z_qGdi.js
│ │ ├── RequestPasswordResetDocs-BrBiwzUj.js
│ │ ├── RequestVerificationDocs-1QGGi1Cu.js
│ │ ├── SdkTabs-C5m8rJlh.js
│ │ ├── SdkTabs-CSWLpEgj.css
│ │ ├── UnlinkExternalAuthDocs-DSSa7KWo.js
│ │ ├── UpdateApiDocs-DV_6nyeC.js
│ │ ├── ViewApiDocs-CaT6LoZ7.js
│ │ ├── autocomplete.worker-CGnTjHzG.js
│ │ ├── index-BH9k9ewW.js
│ │ ├── index-CH1esvLs.js
│ │ └── index-CyfS9YzQ.css
│ ├── fonts
│ │ ├── remixicon
│ │ │ └── remixicon.woff2
│ │ ├── source-sans-pro
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-600.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-600italic.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-700.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-700italic.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-italic.woff2
│ │ │ └── source-sans-pro-v18-latin_cyrillic-regular.woff2
│ │ └── ubuntu-mono
│ │ │ ├── ubuntu-mono-v17-cyrillic_latin-700.woff2
│ │ │ └── ubuntu-mono-v17-cyrillic_latin-regular.woff2
│ ├── images
│ │ ├── avatars
│ │ │ ├── avatar0.svg
│ │ │ ├── avatar1.svg
│ │ │ ├── avatar2.svg
│ │ │ ├── avatar3.svg
│ │ │ ├── avatar4.svg
│ │ │ ├── avatar5.svg
│ │ │ ├── avatar6.svg
│ │ │ ├── avatar7.svg
│ │ │ ├── avatar8.svg
│ │ │ └── avatar9.svg
│ │ ├── favicon
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-512x512.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── browserconfig.xml
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.ico
│ │ │ ├── mstile-144x144.png
│ │ │ ├── mstile-150x150.png
│ │ │ ├── mstile-310x150.png
│ │ │ ├── mstile-310x310.png
│ │ │ ├── mstile-70x70.png
│ │ │ ├── safari-pinned-tab.svg
│ │ │ └── site.webmanifest
│ │ ├── logo.svg
│ │ └── oauth2
│ │ │ ├── apple.svg
│ │ │ ├── bitbucket.svg
│ │ │ ├── discord.svg
│ │ │ ├── facebook.svg
│ │ │ ├── gitea.svg
│ │ │ ├── gitee.svg
│ │ │ ├── github.svg
│ │ │ ├── gitlab.svg
│ │ │ ├── google.svg
│ │ │ ├── instagram.svg
│ │ │ ├── kakao.svg
│ │ │ ├── livechat.svg
│ │ │ ├── mailcow.svg
│ │ │ ├── microsoft.svg
│ │ │ ├── oidc.svg
│ │ │ ├── patreon.svg
│ │ │ ├── planningcenter.svg
│ │ │ ├── spotify.svg
│ │ │ ├── strava.svg
│ │ │ ├── twitch.svg
│ │ │ ├── twitter.svg
│ │ │ ├── vk.svg
│ │ │ └── yandex.svg
│ ├── index.html
│ └── libs
│ │ ├── prism
│ │ ├── prism.min.css
│ │ └── prism.min.js
│ │ └── tinymce
│ │ ├── icons
│ │ └── default
│ │ │ └── icons.min.js
│ │ ├── license.txt
│ │ ├── models
│ │ └── dom
│ │ │ └── model.min.js
│ │ ├── plugins
│ │ ├── anchor
│ │ │ └── plugin.min.js
│ │ ├── autolink
│ │ │ └── plugin.min.js
│ │ ├── autoresize
│ │ │ └── plugin.min.js
│ │ ├── autosave
│ │ │ └── plugin.min.js
│ │ ├── code
│ │ │ └── plugin.min.js
│ │ ├── codesample
│ │ │ └── plugin.min.js
│ │ ├── directionality
│ │ │ └── plugin.min.js
│ │ ├── fullscreen
│ │ │ └── plugin.min.js
│ │ ├── image
│ │ │ └── plugin.min.js
│ │ ├── importcss
│ │ │ └── plugin.min.js
│ │ ├── insertdatetime
│ │ │ └── plugin.min.js
│ │ ├── link
│ │ │ └── plugin.min.js
│ │ ├── lists
│ │ │ └── plugin.min.js
│ │ ├── media
│ │ │ └── plugin.min.js
│ │ ├── nonbreaking
│ │ │ └── plugin.min.js
│ │ ├── pagebreak
│ │ │ └── plugin.min.js
│ │ ├── preview
│ │ │ └── plugin.min.js
│ │ ├── quickbars
│ │ │ └── plugin.min.js
│ │ ├── save
│ │ │ └── plugin.min.js
│ │ ├── searchreplace
│ │ │ └── plugin.min.js
│ │ ├── table
│ │ │ └── plugin.min.js
│ │ ├── template
│ │ │ └── plugin.min.js
│ │ ├── visualblocks
│ │ │ └── plugin.min.js
│ │ ├── visualchars
│ │ │ └── plugin.min.js
│ │ └── wordcount
│ │ │ └── plugin.min.js
│ │ ├── skins
│ │ ├── content
│ │ │ ├── dark
│ │ │ │ └── content.min.css
│ │ │ ├── default
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ │ ├── document
│ │ │ │ └── content.min.css
│ │ │ ├── pocketbase
│ │ │ │ ├── content.css
│ │ │ │ └── content.min.css
│ │ │ └── writer
│ │ │ │ └── content.min.css
│ │ └── ui
│ │ │ ├── oxide
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.min.css
│ │ │ ├── skin.min.css
│ │ │ └── skin.shadowdom.min.css
│ │ │ └── pocketbase
│ │ │ ├── content.css
│ │ │ ├── content.inline.css
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.min.css
│ │ │ ├── content.mobile.css
│ │ │ ├── content.mobile.min.css
│ │ │ ├── fonts
│ │ │ └── tinymce-mobile.woff
│ │ │ ├── skin.css
│ │ │ ├── skin.min.css
│ │ │ ├── skin.mobile.css
│ │ │ └── skin.mobile.min.css
│ │ ├── themes
│ │ └── silver
│ │ │ └── theme.min.js
│ │ └── tinymce.min.js
├── embed.go
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│ ├── fonts
│ │ ├── remixicon
│ │ │ └── remixicon.woff2
│ │ ├── source-sans-pro
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-600.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-600italic.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-700.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-700italic.woff2
│ │ │ ├── source-sans-pro-v18-latin_cyrillic-italic.woff2
│ │ │ └── source-sans-pro-v18-latin_cyrillic-regular.woff2
│ │ └── ubuntu-mono
│ │ │ ├── ubuntu-mono-v17-cyrillic_latin-700.woff2
│ │ │ └── ubuntu-mono-v17-cyrillic_latin-regular.woff2
│ ├── images
│ │ ├── avatars
│ │ │ ├── avatar0.svg
│ │ │ ├── avatar1.svg
│ │ │ ├── avatar2.svg
│ │ │ ├── avatar3.svg
│ │ │ ├── avatar4.svg
│ │ │ ├── avatar5.svg
│ │ │ ├── avatar6.svg
│ │ │ ├── avatar7.svg
│ │ │ ├── avatar8.svg
│ │ │ └── avatar9.svg
│ │ ├── favicon
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-512x512.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── browserconfig.xml
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.ico
│ │ │ ├── mstile-144x144.png
│ │ │ ├── mstile-150x150.png
│ │ │ ├── mstile-310x150.png
│ │ │ ├── mstile-310x310.png
│ │ │ ├── mstile-70x70.png
│ │ │ ├── safari-pinned-tab.svg
│ │ │ └── site.webmanifest
│ │ ├── logo.svg
│ │ └── oauth2
│ │ │ ├── apple.svg
│ │ │ ├── bitbucket.svg
│ │ │ ├── discord.svg
│ │ │ ├── facebook.svg
│ │ │ ├── gitea.svg
│ │ │ ├── gitee.svg
│ │ │ ├── github.svg
│ │ │ ├── gitlab.svg
│ │ │ ├── google.svg
│ │ │ ├── instagram.svg
│ │ │ ├── kakao.svg
│ │ │ ├── livechat.svg
│ │ │ ├── mailcow.svg
│ │ │ ├── microsoft.svg
│ │ │ ├── oidc.svg
│ │ │ ├── patreon.svg
│ │ │ ├── planningcenter.svg
│ │ │ ├── spotify.svg
│ │ │ ├── strava.svg
│ │ │ ├── twitch.svg
│ │ │ ├── twitter.svg
│ │ │ ├── vk.svg
│ │ │ └── yandex.svg
│ └── libs
│ │ ├── prism
│ │ ├── prism.min.css
│ │ └── prism.min.js
│ │ └── tinymce
│ │ ├── icons
│ │ └── default
│ │ │ └── icons.min.js
│ │ ├── license.txt
│ │ ├── models
│ │ └── dom
│ │ │ └── model.min.js
│ │ ├── plugins
│ │ ├── anchor
│ │ │ └── plugin.min.js
│ │ ├── autolink
│ │ │ └── plugin.min.js
│ │ ├── autoresize
│ │ │ └── plugin.min.js
│ │ ├── autosave
│ │ │ └── plugin.min.js
│ │ ├── code
│ │ │ └── plugin.min.js
│ │ ├── codesample
│ │ │ └── plugin.min.js
│ │ ├── directionality
│ │ │ └── plugin.min.js
│ │ ├── fullscreen
│ │ │ └── plugin.min.js
│ │ ├── image
│ │ │ └── plugin.min.js
│ │ ├── importcss
│ │ │ └── plugin.min.js
│ │ ├── insertdatetime
│ │ │ └── plugin.min.js
│ │ ├── link
│ │ │ └── plugin.min.js
│ │ ├── lists
│ │ │ └── plugin.min.js
│ │ ├── media
│ │ │ └── plugin.min.js
│ │ ├── nonbreaking
│ │ │ └── plugin.min.js
│ │ ├── pagebreak
│ │ │ └── plugin.min.js
│ │ ├── preview
│ │ │ └── plugin.min.js
│ │ ├── quickbars
│ │ │ └── plugin.min.js
│ │ ├── save
│ │ │ └── plugin.min.js
│ │ ├── searchreplace
│ │ │ └── plugin.min.js
│ │ ├── table
│ │ │ └── plugin.min.js
│ │ ├── template
│ │ │ └── plugin.min.js
│ │ ├── visualblocks
│ │ │ └── plugin.min.js
│ │ ├── visualchars
│ │ │ └── plugin.min.js
│ │ └── wordcount
│ │ │ └── plugin.min.js
│ │ ├── skins
│ │ ├── content
│ │ │ ├── dark
│ │ │ │ └── content.min.css
│ │ │ ├── default
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ │ ├── document
│ │ │ │ └── content.min.css
│ │ │ ├── pocketbase
│ │ │ │ ├── content.css
│ │ │ │ └── content.min.css
│ │ │ └── writer
│ │ │ │ └── content.min.css
│ │ └── ui
│ │ │ ├── oxide
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.min.css
│ │ │ ├── skin.min.css
│ │ │ └── skin.shadowdom.min.css
│ │ │ └── pocketbase
│ │ │ ├── content.css
│ │ │ ├── content.inline.css
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.min.css
│ │ │ ├── content.mobile.css
│ │ │ ├── content.mobile.min.css
│ │ │ ├── fonts
│ │ │ └── tinymce-mobile.woff
│ │ │ ├── skin.css
│ │ │ ├── skin.min.css
│ │ │ ├── skin.mobile.css
│ │ │ └── skin.mobile.min.css
│ │ ├── themes
│ │ └── silver
│ │ │ └── theme.min.js
│ │ └── tinymce.min.js
├── src
│ ├── App.svelte
│ ├── actions
│ │ ├── scrollend.js
│ │ └── tooltip.js
│ ├── autocomplete.worker.js
│ ├── components
│ │ ├── PageIndex.svelte
│ │ ├── admins
│ │ │ ├── AdminUpsertPanel.svelte
│ │ │ ├── PageAdminConfirmPasswordReset.svelte
│ │ │ ├── PageAdminLogin.svelte
│ │ │ ├── PageAdminRequestPasswordReset.svelte
│ │ │ └── PageAdmins.svelte
│ │ ├── base
│ │ │ ├── Accordion.svelte
│ │ │ ├── AutoExpandTextarea.svelte
│ │ │ ├── BaseSelectOption.svelte
│ │ │ ├── CodeBlock.svelte
│ │ │ ├── CodeEditor.svelte
│ │ │ ├── Confirmation.svelte
│ │ │ ├── CopyIcon.svelte
│ │ │ ├── Draggable.svelte
│ │ │ ├── Dragline.svelte
│ │ │ ├── Field.svelte
│ │ │ ├── FilterAutocompleteInput.svelte
│ │ │ ├── FormattedDate.svelte
│ │ │ ├── FullPage.svelte
│ │ │ ├── InitialsAvatar.svelte
│ │ │ ├── Installer.svelte
│ │ │ ├── MimeTypeSelectOption.svelte
│ │ │ ├── ModelDateIcon.svelte
│ │ │ ├── MultipleValueInput.svelte
│ │ │ ├── ObjectSelect.svelte
│ │ │ ├── OverlayPanel.svelte
│ │ │ ├── PageSidebar.svelte
│ │ │ ├── PageWrapper.svelte
│ │ │ ├── PreviewPopup.svelte
│ │ │ ├── RedactedPasswordInput.svelte
│ │ │ ├── RefreshButton.svelte
│ │ │ ├── Scroller.svelte
│ │ │ ├── Searchbar.svelte
│ │ │ ├── SecretGeneratorButton.svelte
│ │ │ ├── Select.svelte
│ │ │ ├── SortHeader.svelte
│ │ │ ├── TinyMCE.svelte
│ │ │ ├── Toasts.svelte
│ │ │ ├── Toggler.svelte
│ │ │ └── UploadedFilePreview.svelte
│ │ ├── collections
│ │ │ ├── CollectionAuthOptionsTab.svelte
│ │ │ ├── CollectionDocsPanel.svelte
│ │ │ ├── CollectionFieldsTab.svelte
│ │ │ ├── CollectionQueryTab.svelte
│ │ │ ├── CollectionRulesTab.svelte
│ │ │ ├── CollectionSidebarItem.svelte
│ │ │ ├── CollectionUpdateConfirm.svelte
│ │ │ ├── CollectionUpsertPanel.svelte
│ │ │ ├── CollectionsDiffTable.svelte
│ │ │ ├── CollectionsSidebar.svelte
│ │ │ ├── IndexUpsertPanel.svelte
│ │ │ ├── IndexesList.svelte
│ │ │ ├── RuleField.svelte
│ │ │ ├── docs
│ │ │ │ ├── AuthMethodsDocs.svelte
│ │ │ │ ├── AuthRefreshDocs.svelte
│ │ │ │ ├── AuthWithOAuth2Docs.svelte
│ │ │ │ ├── AuthWithPasswordDocs.svelte
│ │ │ │ ├── ConfirmEmailChangeDocs.svelte
│ │ │ │ ├── ConfirmPasswordResetDocs.svelte
│ │ │ │ ├── ConfirmVerificationDocs.svelte
│ │ │ │ ├── CreateApiDocs.svelte
│ │ │ │ ├── DeleteApiDocs.svelte
│ │ │ │ ├── FieldsQueryParam.svelte
│ │ │ │ ├── FilterSyntax.svelte
│ │ │ │ ├── ListApiDocs.svelte
│ │ │ │ ├── ListExternalAuthsDocs.svelte
│ │ │ │ ├── RealtimeApiDocs.svelte
│ │ │ │ ├── RequestEmailChangeDocs.svelte
│ │ │ │ ├── RequestPasswordResetDocs.svelte
│ │ │ │ ├── RequestVerificationDocs.svelte
│ │ │ │ ├── SdkTabs.svelte
│ │ │ │ ├── UnlinkExternalAuthDocs.svelte
│ │ │ │ ├── UpdateApiDocs.svelte
│ │ │ │ └── ViewApiDocs.svelte
│ │ │ └── schema
│ │ │ │ ├── NewField.svelte
│ │ │ │ ├── SchemaField.svelte
│ │ │ │ ├── SchemaFieldBool.svelte
│ │ │ │ ├── SchemaFieldDate.svelte
│ │ │ │ ├── SchemaFieldEditor.svelte
│ │ │ │ ├── SchemaFieldEmail.svelte
│ │ │ │ ├── SchemaFieldFile.svelte
│ │ │ │ ├── SchemaFieldJson.svelte
│ │ │ │ ├── SchemaFieldNumber.svelte
│ │ │ │ ├── SchemaFieldRelation.svelte
│ │ │ │ ├── SchemaFieldSelect.svelte
│ │ │ │ ├── SchemaFieldText.svelte
│ │ │ │ └── SchemaFieldUrl.svelte
│ │ ├── logs
│ │ │ ├── LogDate.svelte
│ │ │ ├── LogLevel.svelte
│ │ │ ├── LogViewPanel.svelte
│ │ │ ├── LogsChart.svelte
│ │ │ ├── LogsLevelsInfo.svelte
│ │ │ ├── LogsList.svelte
│ │ │ ├── LogsSettingsPanel.svelte
│ │ │ └── PageLogs.svelte
│ │ ├── records
│ │ │ ├── ExternalAuthsList.svelte
│ │ │ ├── PageOAuth2Redirect.svelte
│ │ │ ├── PageOAuth2RedirectFailure.svelte
│ │ │ ├── PageOAuth2RedirectSuccess.svelte
│ │ │ ├── PageRecordConfirmEmailChange.svelte
│ │ │ ├── PageRecordConfirmPasswordReset.svelte
│ │ │ ├── PageRecordConfirmVerification.svelte
│ │ │ ├── PageRecords.svelte
│ │ │ ├── RecordFieldValue.svelte
│ │ │ ├── RecordFilePicker.svelte
│ │ │ ├── RecordFileThumb.svelte
│ │ │ ├── RecordInfo.svelte
│ │ │ ├── RecordPreviewPanel.svelte
│ │ │ ├── RecordUpsertPanel.svelte
│ │ │ ├── RecordsCount.svelte
│ │ │ ├── RecordsList.svelte
│ │ │ ├── RecordsPicker.svelte
│ │ │ └── fields
│ │ │ │ ├── AuthFields.svelte
│ │ │ │ ├── BoolField.svelte
│ │ │ │ ├── DateField.svelte
│ │ │ │ ├── EditorField.svelte
│ │ │ │ ├── EmailField.svelte
│ │ │ │ ├── FileField.svelte
│ │ │ │ ├── JsonField.svelte
│ │ │ │ ├── NumberField.svelte
│ │ │ │ ├── RelationField.svelte
│ │ │ │ ├── SelectField.svelte
│ │ │ │ ├── TextField.svelte
│ │ │ │ └── UrlField.svelte
│ │ └── settings
│ │ │ ├── AuthProviderCard.svelte
│ │ │ ├── AuthProviderPanel.svelte
│ │ │ ├── BackupCreatePanel.svelte
│ │ │ ├── BackupRestorePanel.svelte
│ │ │ ├── BackupUploadButton.svelte
│ │ │ ├── BackupsList.svelte
│ │ │ ├── EmailTemplateAccordion.svelte
│ │ │ ├── EmailTestPopup.svelte
│ │ │ ├── ImportPopup.svelte
│ │ │ ├── PageApplication.svelte
│ │ │ ├── PageAuthProviders.svelte
│ │ │ ├── PageBackups.svelte
│ │ │ ├── PageExportCollections.svelte
│ │ │ ├── PageImportCollections.svelte
│ │ │ ├── PageMail.svelte
│ │ │ ├── PageStorage.svelte
│ │ │ ├── PageTokenOptions.svelte
│ │ │ ├── S3Fields.svelte
│ │ │ ├── SettingsSidebar.svelte
│ │ │ ├── TokenField.svelte
│ │ │ └── providers
│ │ │ ├── AppleOptions.svelte
│ │ │ ├── AppleSecretPopup.svelte
│ │ │ ├── MicrosoftOptions.svelte
│ │ │ ├── OIDCOptions.svelte
│ │ │ └── SelfHostedOptions.svelte
│ ├── main.js
│ ├── mimes.js
│ ├── providers.js
│ ├── routes.js
│ ├── scss
│ │ ├── _accordion.scss
│ │ ├── _alert.scss
│ │ ├── _animations.scss
│ │ ├── _base.scss
│ │ ├── _bulkbar.scss
│ │ ├── _collections_export.scss
│ │ ├── _docs_panel.scss
│ │ ├── _dropdown.scss
│ │ ├── _file_picker.scss
│ │ ├── _flatpickr.scss
│ │ ├── _fonts.scss
│ │ ├── _form.scss
│ │ ├── _grid.scss
│ │ ├── _icons.scss
│ │ ├── _layout.scss
│ │ ├── _mixins.scss
│ │ ├── _overlay_panel.scss
│ │ ├── _reset.scss
│ │ ├── _schema_field.scss
│ │ ├── _searchbar.scss
│ │ ├── _table.scss
│ │ ├── _tabs.scss
│ │ ├── _tooltip.scss
│ │ ├── _vars.scss
│ │ ├── main.scss
│ │ └── prism_light.scss
│ ├── stores
│ │ ├── admin.js
│ │ ├── app.js
│ │ ├── collections.js
│ │ ├── confirmation.js
│ │ ├── errors.js
│ │ └── toasts.js
│ └── utils
│ │ ├── ApiClient.js
│ │ └── CommonHelper.js
└── vite.config.js
└── volumes
└── .gitignore
/.dockerignore:
--------------------------------------------------------------------------------
1 | /.vscode/
2 | .idea
3 |
4 | .DS_Store
5 |
6 | # goreleaser builds folder
7 | /.builds/
8 |
9 | # tests coverage
10 | coverage.out
11 |
12 | # plaintask todo files
13 | *.todo
14 |
15 | # generated markdown previews
16 | README.html
17 | CHANGELOG.html
18 | LICENSE.html
19 |
20 | # application specific ignores and go build outputs
21 | volumes/*
22 | pb_migrations/*
23 | pb_data/*
24 | server
25 | *.exe
26 | keys/*
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | LOGS_DATABASE="postgresql://your_user:your_pass@localhost/rocketbase_logs?sslmode=disable"
2 | DATABASE="postgresql://your_user:your_pass@localhost/rocketbase?sslmode=disable"
3 | BCRYPT_COST=10 # default is 12
4 |
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | LOGS_DATABASE="postgresql://your_user:your_pass@localhost/test_rocketbase_logs?sslmode=disable"
2 | DATABASE="postgresql://your_user:your_pass@localhost/test_rocketbase?sslmode=disable"
3 | BCRYPT_COST=10 # default is 12
4 |
--------------------------------------------------------------------------------
/.env.test.basic:
--------------------------------------------------------------------------------
1 | LOGS_DATABASE="postgresql://your_user:your_pass@localhost/test_rocketbase_logs_basic?sslmode=disable"
2 | DATABASE="postgresql://your_user:your_pass@localhost/test_rocketbase_basic?sslmode=disable"
3 | BCRYPT_COST=10 # default is 12
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode/
2 | .idea
3 |
4 | .DS_Store
5 |
6 | # goreleaser builds folder
7 | /.builds/
8 |
9 | # tests coverage
10 | coverage.out
11 |
12 | # plaintask todo files
13 | *.todo
14 |
15 | # generated markdown previews
16 | README.html
17 | CHANGELOG.html
18 | LICENSE.html
19 |
20 | # application specific ignores and go build outputs
21 | volumes/*
22 | pb_migrations/*
23 | pb_data/*
24 | server
25 | *.exe
26 | keys/*
27 | .env
28 | tmp/
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | project_name: pocketbase
2 |
3 | dist: .builds
4 |
5 | before:
6 | hooks:
7 | - go mod tidy
8 |
9 | builds:
10 | # used only for tests
11 | - id: build_cgo
12 | main: ./examples/base
13 | binary: pocketbase
14 | ldflags:
15 | - -s -w -X github.com/pocketbase/pocketbase.Version={{ .Version }}
16 | env:
17 | - CGO_ENABLED=1
18 | goos:
19 | - linux
20 | goarch:
21 | - amd64
22 | tags:
23 | - pq
24 | - sqlite
25 |
26 | - id: build_noncgo
27 | main: ./examples/base
28 | binary: pocketbase
29 | ldflags:
30 | - -s -w -X github.com/pocketbase/pocketbase.Version={{ .Version }}
31 | env:
32 | - CGO_ENABLED=0
33 | goos:
34 | - linux
35 | - windows
36 | - darwin
37 | goarch:
38 | - amd64
39 | - arm64
40 | - arm
41 | tags:
42 | - pq
43 | goarm:
44 | - 7
45 | ignore:
46 | - goos: windows
47 | goarch: arm
48 | - goos: darwin
49 | goarch: arm
50 |
51 | release:
52 | draft: true
53 |
54 | archives:
55 | - id: archive_noncgo
56 | builds: [build_noncgo]
57 | format: zip
58 | files:
59 | - LICENSE.md
60 | - CHANGELOG.md
61 |
62 | checksum:
63 | name_template: 'checksums.txt'
64 |
65 | snapshot:
66 | name_template: '{{ incpatch .Version }}-next'
67 |
68 | changelog:
69 | sort: asc
70 | filters:
71 | exclude:
72 | - '^examples:'
73 | - '^ui:'
74 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v0.21.0
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to RocketBase
2 |
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG GO_VERSION=1.21.5
2 | ARG ALPINE_VERSION=3.17
3 |
4 | # build stage
5 | FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-env
6 | RUN apk add build-base
7 |
8 | ADD . /src
9 |
10 | # CGO_ENABLED=0
11 | ENV CGO_ENABLED=0
12 | RUN cd /src && go build -tags pq -o server ./examples/base/main.go
13 |
14 | # final stage
15 | FROM alpine:${ALPINE_VERSION}
16 |
17 | RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
18 | WORKDIR /app
19 |
20 | COPY --from=build-env /src/server /app/
21 |
22 | # create user and give it permissions
23 | RUN addgroup -S appgroup && adduser -S appuser -G appgroup
24 | RUN mkdir /app/pb_data
25 | RUN mkdir /app/pb_migrations
26 | RUN mkdir /app/pb_config
27 |
28 | RUN chown -R appuser:appgroup /app
29 | USER appuser
30 |
31 | CMD ["/app/server","serve", "--http=0.0.0.0:8090"]
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2022 - present, Gani Georgiev
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5 | and associated documentation files (the "Software"), to deal in the Software without restriction,
6 | including without limitation the rights to use, copy, modify, merge, publish, distribute,
7 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or
11 | substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | lint:
2 | golangci-lint run -c ./golangci.yml ./...
3 |
4 | test:
5 | go test ./... -v --cover
6 |
7 | test-fast:
8 | go test -failfast -v ./...
9 |
10 | jstypes:
11 | go run ./plugins/jsvm/internal/types/types.go
12 |
13 | test-report:
14 | go test ./... -v --cover -coverprofile=coverage.out
15 | go tool cover -html=coverage.out
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Rocketbase
2 |
3 | > Rocketbase = Pocketbase + PostgreSQL + a lot of goodies
4 |
5 | ## Setup
6 |
7 | ```bash
8 | psql
9 | ```
10 |
11 | ```sql
12 | CREATE
13 | USER your_user WITH PASSWORD 'your_pass';
14 | CREATE
15 | DATABASE rocketbase;
16 | CREATE
17 | DATABASE rocketbase_logs;
18 | GRANT ALL PRIVILEGES ON DATABASE
19 | rocketbase TO your_user;
20 | GRANT ALL PRIVILEGES ON DATABASE
21 | rocketbase_logs TO your_user;
22 | -- test
23 | CREATE
24 | DATABASE test_rocketbase;
25 | CREATE
26 | DATABASE test_rocketbase_logs;
27 | GRANT ALL PRIVILEGES ON DATABASE
28 | test_rocketbase TO your_user;
29 | GRANT ALL PRIVILEGES ON DATABASE
30 | test_rocketbase_logs TO your_user;
31 |
32 | CREATE
33 | DATABASE test_rocketbase_basic;
34 | CREATE
35 | DATABASE test_rocketbase_logs_basic;
36 | GRANT ALL PRIVILEGES ON DATABASE
37 | test_rocketbase_basic TO your_user;
38 | GRANT ALL PRIVILEGES ON DATABASE
39 | test_rocketbase_logs_basic TO your_user;
40 | ```
41 |
42 | ## Credit
43 |
44 | - pocketbase for main codebase
45 | - postgresbase for adapting postgres
46 |
47 |
--------------------------------------------------------------------------------
/apis/health.go:
--------------------------------------------------------------------------------
1 | package apis
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/hylarucoder/rocketbase/core"
7 | "github.com/labstack/echo/v5"
8 | )
9 |
10 | // bindHealthApi registers the health api endpoint.
11 | func bindHealthApi(app core.App, rg *echo.Group) {
12 | api := healthApi{app: app}
13 |
14 | subGroup := rg.Group("/health")
15 | subGroup.HEAD("", api.healthCheck)
16 | subGroup.GET("", api.healthCheck)
17 | }
18 |
19 | type healthApi struct {
20 | app core.App
21 | }
22 |
23 | type healthCheckResponse struct {
24 | Message string `json:"message"`
25 | Code int `json:"code"`
26 | Data struct {
27 | CanBackup bool `json:"canBackup"`
28 | } `json:"data"`
29 | }
30 |
31 | // healthCheck returns a 200 OK response if the server is healthy.
32 | func (api *healthApi) healthCheck(c echo.Context) error {
33 | if c.Request().Method == http.MethodHead {
34 | return c.NoContent(http.StatusOK)
35 | }
36 |
37 | resp := new(healthCheckResponse)
38 | resp.Code = http.StatusOK
39 | resp.Message = "API is healthy."
40 | resp.Data.CanBackup = !api.app.Store().Has(core.StoreKeyActiveBackup)
41 |
42 | return c.JSON(http.StatusOK, resp)
43 | }
44 |
--------------------------------------------------------------------------------
/apis/health_test.go:
--------------------------------------------------------------------------------
1 | package apis_test
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 |
7 | "github.com/hylarucoder/rocketbase/tests"
8 | "github.com/stretchr/testify/suite"
9 | )
10 |
11 | func (suite *HealthTestSuite) TestHealthAPI() {
12 | t := suite.T()
13 |
14 | scenarios := []tests.ApiScenario{
15 | {
16 | Name: "HEAD health status",
17 | Method: http.MethodHead,
18 | Url: "/api/health",
19 | ExpectedStatus: 200,
20 | },
21 | {
22 | Name: "GET health status",
23 | Method: http.MethodGet,
24 | Url: "/api/health",
25 | ExpectedStatus: 200,
26 | ExpectedContent: []string{
27 | `"code":200`,
28 | `"data":{`,
29 | `"canBackup":true`,
30 | },
31 | },
32 | }
33 |
34 | for _, scenario := range scenarios {
35 | scenario.Test(t)
36 | }
37 | }
38 |
39 | type HealthTestSuite struct {
40 | suite.Suite
41 | App *tests.TestApp
42 | Var int
43 | }
44 |
45 | func (suite *HealthTestSuite) SetupSuite() {
46 | app, _ := tests.NewTestApp()
47 | suite.Var = 5
48 | suite.App = app
49 | }
50 |
51 | func (suite *HealthTestSuite) TearDownSuite() {
52 | suite.App.Cleanup()
53 | }
54 |
55 | func TestHealthTestSuite(t *testing.T) {
56 | suite.Run(t, new(HealthTestSuite))
57 | }
58 |
--------------------------------------------------------------------------------
/core/db_pg.go:
--------------------------------------------------------------------------------
1 | //TODO: go:build pq
2 |
3 | package core
4 |
5 | import (
6 | _ "github.com/lib/pq"
7 | "github.com/pocketbase/dbx"
8 | )
9 |
10 | func ConnectDB(dsn string) (*dbx.DB, error) {
11 | return dbx.MustOpen("postgres", dsn)
12 | }
13 |
--------------------------------------------------------------------------------
/examples/base/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore everything
2 | /*
3 |
4 | # exclude from the ignore filter
5 | !.gitignore
6 | !main.go
7 |
--------------------------------------------------------------------------------
/forms/base.go:
--------------------------------------------------------------------------------
1 | // Package models implements various services used for request data
2 | // validation and applying changes to existing DB models through the app Dao.
3 | package forms
4 |
5 | import (
6 | "regexp"
7 | )
8 |
9 | // base ID value regex pattern
10 | var idRegex = regexp.MustCompile(`^[^\@\#\$\&\|\.\,\'\"\\\/\s]+$`)
11 |
12 | // InterceptorNextFunc is a interceptor handler function.
13 | // Usually used in combination with InterceptorFunc.
14 | type InterceptorNextFunc[T any] func(t T) error
15 |
16 | // InterceptorFunc defines a single interceptor function that
17 | // will execute the provided next func handler.
18 | type InterceptorFunc[T any] func(next InterceptorNextFunc[T]) InterceptorNextFunc[T]
19 |
20 | // runInterceptors executes the provided list of interceptors.
21 | func runInterceptors[T any](
22 | data T,
23 | next InterceptorNextFunc[T],
24 | interceptors ...InterceptorFunc[T],
25 | ) error {
26 | for i := len(interceptors) - 1; i >= 0; i-- {
27 | next = interceptors[i](next)
28 | }
29 |
30 | return next(data)
31 | }
32 |
--------------------------------------------------------------------------------
/forms/realtime_subscribe.go:
--------------------------------------------------------------------------------
1 | package forms
2 |
3 | import (
4 | validation "github.com/go-ozzo/ozzo-validation/v4"
5 | )
6 |
7 | // RealtimeSubscribe is a realtime subscriptions request form.
8 | type RealtimeSubscribe struct {
9 | ClientId string `form:"clientId" json:"clientId"`
10 | Subscriptions []string `form:"subscriptions" json:"subscriptions"`
11 | }
12 |
13 | // NewRealtimeSubscribe creates new RealtimeSubscribe request form.
14 | func NewRealtimeSubscribe() *RealtimeSubscribe {
15 | return &RealtimeSubscribe{}
16 | }
17 |
18 | // Validate makes the form validatable by implementing [validation.Validatable] interface.
19 | func (form *RealtimeSubscribe) Validate() error {
20 | return validation.ValidateStruct(form,
21 | validation.Field(&form.ClientId, validation.Required, validation.Length(1, 255)),
22 | validation.Field(&form.Subscriptions, validation.Length(0, 1000)),
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/forms/realtime_subscribe_test.go:
--------------------------------------------------------------------------------
1 | package forms_test
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/hylarucoder/rocketbase/forms"
8 | )
9 |
10 | func TestRealtimeSubscribeValidate(t *testing.T) {
11 | t.Parallel()
12 |
13 | scenarios := []struct {
14 | clientId string
15 | expectError bool
16 | }{
17 | {"", true},
18 | {strings.Repeat("a", 256), true},
19 | {"test", false},
20 | }
21 |
22 | for i, s := range scenarios {
23 | form := forms.NewRealtimeSubscribe()
24 | form.ClientId = s.clientId
25 |
26 | err := form.Validate()
27 |
28 | hasErr := err != nil
29 | if hasErr != s.expectError {
30 | t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, s.expectError, hasErr, err)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/forms/validators/model.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import (
4 | "database/sql"
5 | "errors"
6 |
7 | validation "github.com/go-ozzo/ozzo-validation/v4"
8 | "github.com/hylarucoder/rocketbase/daos"
9 | "github.com/pocketbase/dbx"
10 | )
11 |
12 | // UniqueId checks whether the provided model id already exists.
13 | //
14 | // Example:
15 | //
16 | // validation.Field(&form.Id, validation.By(validators.UniqueId(form.dao, tableName)))
17 | func UniqueId(dao *daos.Dao, tableName string) validation.RuleFunc {
18 | return func(value any) error {
19 | v, _ := value.(string)
20 | if v == "" {
21 | return nil // nothing to check
22 | }
23 |
24 | var foundId string
25 |
26 | err := dao.DB().
27 | Select("id").
28 | From(tableName).
29 | Where(dbx.HashExp{"id": v}).
30 | Limit(1).
31 | Row(&foundId)
32 |
33 | if (err != nil && !errors.Is(err, sql.ErrNoRows)) || foundId != "" {
34 | return validation.NewError("validation_invalid_id", "The model id is invalid or already exists.")
35 | }
36 |
37 | return nil
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/forms/validators/model_test.go:
--------------------------------------------------------------------------------
1 | package validators_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/forms/validators"
7 | "github.com/hylarucoder/rocketbase/tests"
8 | )
9 |
10 | func TestUniqueId(t *testing.T) {
11 | t.Parallel()
12 |
13 | app, _ := tests.NewTestApp()
14 | defer app.Cleanup()
15 |
16 | scenarios := []struct {
17 | id string
18 | tableName string
19 | expectError bool
20 | }{
21 | {"", "", false},
22 | {"test", "", true},
23 | {"2108348993330216960", "_collections", true},
24 | {"test_unique_id", "unknown_table", true},
25 | {"test_unique_id", "_collections", false},
26 | }
27 |
28 | for i, s := range scenarios {
29 | err := validators.UniqueId(app.Dao(), s.tableName)(s.id)
30 |
31 | hasErr := err != nil
32 | if hasErr != s.expectError {
33 | t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, s.expectError, hasErr, err)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/forms/validators/string.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import (
4 | validation "github.com/go-ozzo/ozzo-validation/v4"
5 | )
6 |
7 | // Compare checks whether the validated value matches another string.
8 | //
9 | // Example:
10 | //
11 | // validation.Field(&form.PasswordConfirm, validation.By(validators.Compare(form.Password)))
12 | func Compare(valueToCompare string) validation.RuleFunc {
13 | return func(value any) error {
14 | v, _ := value.(string)
15 |
16 | if v != valueToCompare {
17 | return validation.NewError("validation_values_mismatch", "Values don't match.")
18 | }
19 |
20 | return nil
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/forms/validators/string_test.go:
--------------------------------------------------------------------------------
1 | package validators_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/forms/validators"
7 | )
8 |
9 | func TestCompare(t *testing.T) {
10 | t.Parallel()
11 |
12 | scenarios := []struct {
13 | valA string
14 | valB string
15 | expectError bool
16 | }{
17 | {"", "", false},
18 | {"", "456", true},
19 | {"123", "", true},
20 | {"123", "456", true},
21 | {"123", "123", false},
22 | }
23 |
24 | for i, s := range scenarios {
25 | err := validators.Compare(s.valA)(s.valB)
26 |
27 | hasErr := err != nil
28 | if hasErr != s.expectError {
29 | t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, s.expectError, hasErr, err)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/forms/validators/validators.go:
--------------------------------------------------------------------------------
1 | // Package validators implements custom shared PocketBase validators.
2 | package validators
3 |
--------------------------------------------------------------------------------
/golangci.yml:
--------------------------------------------------------------------------------
1 | run:
2 | go: 1.21
3 | concurrency: 4
4 | timeout: 10m
5 |
6 | linters:
7 | disable-all: true
8 | enable:
9 | - asciicheck
10 | - gofmt
11 | - goimports
12 | - gomodguard
13 | - goprintffuncname
14 | - gosimple
15 | - govet
16 | - ineffassign
17 | - misspell
18 | - nakedret
19 | - nolintlint
20 | - prealloc
21 | - staticcheck
22 | - typecheck
23 | - unconvert
24 | - unused
25 | - whitespace
--------------------------------------------------------------------------------
/keys/.gitignore:
--------------------------------------------------------------------------------
1 | ./*
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/mails/admin_test.go:
--------------------------------------------------------------------------------
1 | package mails_test
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/hylarucoder/rocketbase/mails"
8 | "github.com/hylarucoder/rocketbase/tests"
9 | )
10 |
11 | func TestSendAdminPasswordReset(t *testing.T) {
12 | t.Parallel()
13 |
14 | testApp, _ := tests.NewTestApp()
15 | defer testApp.Cleanup()
16 |
17 | // ensure that action url normalization will be applied
18 | testApp.Settings().Meta.AppUrl = "http://localhost:8090////"
19 |
20 | admin, _ := testApp.Dao().FindAdminByEmail("test@example.com")
21 |
22 | err := mails.SendAdminPasswordReset(testApp, admin)
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 |
27 | if testApp.TestMailer.TotalSend != 1 {
28 | t.Fatalf("Expected one email to be sent, got %d", testApp.TestMailer.TotalSend)
29 | }
30 |
31 | expectedParts := []string{
32 | "http://localhost:8090/_/#/confirm-password-reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
33 | }
34 | for _, part := range expectedParts {
35 | if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
36 | t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastMessage.HTML)
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/mails/base.go:
--------------------------------------------------------------------------------
1 | // Package mails implements various helper methods for sending user and admin
2 | // emails like forgotten password, verification, etc.
3 | package mails
4 |
5 | import (
6 | "bytes"
7 | "text/template"
8 | )
9 |
10 | // resolveTemplateContent resolves inline html template strings.
11 | func resolveTemplateContent(data any, content ...string) (string, error) {
12 | if len(content) == 0 {
13 | return "", nil
14 | }
15 |
16 | t := template.New("inline_template")
17 |
18 | var parseErr error
19 | for _, v := range content {
20 | t, parseErr = t.Parse(v)
21 | if parseErr != nil {
22 | return "", parseErr
23 | }
24 | }
25 |
26 | var wr bytes.Buffer
27 |
28 | if executeErr := t.Execute(&wr, data); executeErr != nil {
29 | return "", executeErr
30 | }
31 |
32 | return wr.String(), nil
33 | }
34 |
--------------------------------------------------------------------------------
/mails/templates/admin_password_reset.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | // Available variables:
4 | //
5 | // ```
6 | // Admin *models.Admin
7 | // AppName string
8 | // AppUrl string
9 | // Token string
10 | // ActionUrl string
11 | // ```
12 | const AdminPasswordResetBody = `
13 | {{define "content"}}
14 |
Hello,
15 | Follow this link to reset your admin password for {{.AppName}}.
16 |
17 | Reset password
18 |
19 | If you did not request to reset your password, please ignore this email and the link will expire on its own.
20 | {{end}}
21 | `
22 |
--------------------------------------------------------------------------------
/mails/templates/html_content.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | // Available variables:
4 | //
5 | // ```
6 | // HtmlContent template.HTML
7 | // ```
8 | const HtmlBody = `{{define "content"}}{{.HtmlContent}}{{end}}`
9 |
--------------------------------------------------------------------------------
/mails/templates/password_login_alert.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | // Available variables:
4 | //
5 | // ```
6 | // Record *models.Record
7 | // AppName string
8 | // AppUrl string
9 | // ProviderNames []string
10 | // ```
11 | const PasswordLoginAlertBody = `
12 | {{define "content"}}
13 | Hello,
14 |
15 | Just to let you know that someone has logged in to your {{.AppName}} account using a password while you already have
16 | OAuth2
17 | {{range $index, $provider := .ProviderNames }}
18 | {{if $index}}|{{end}}
19 | {{ $provider }}
20 | {{ end }}
21 | auth linked.
22 |
23 | If you have recently signed in with a password, you may disregard this email.
24 | If you don't recognize the above action, you should immediately change your {{.AppName}} account password.
25 |
26 | Thanks,
27 | {{.AppName}} team
28 |
29 | {{end}}
30 | `
31 |
--------------------------------------------------------------------------------
/migrations/1677152688_rename_authentik_to_oidc.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/pocketbase/dbx"
5 | )
6 |
7 | // This migration replaces the "authentikAuth" setting with "oidc".
8 | func init() {
9 | AppMigrations.Register(func(db dbx.Builder) error {
10 | _, err := db.NewQuery(`
11 | UPDATE {{_params}}
12 | SET [[value]] = replace([[value]]::text, '"authentikAuth":', '"oidcAuth":')::json
13 | WHERE [[key]] = 'settings'
14 | `).Execute()
15 |
16 | return err
17 | }, func(db dbx.Builder) error {
18 | _, err := db.NewQuery(`
19 | UPDATE {{_params}}
20 | SET [[value]] = replace([[value]]::text, '"oidcAuth":', '"authentikAuth":')::json
21 | WHERE [[key]] = 'settings'
22 | `).Execute()
23 |
24 | return err
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/migrations/1685164450_check_fk.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/pocketbase/dbx"
5 | )
6 |
7 | // Cleanup dangling deleted collections references
8 | // (see https://github.com/hylarucoder/rocketbase/discussions/2570).
9 | func init() {
10 | AppMigrations.Register(func(db dbx.Builder) error {
11 | _, err := db.NewQuery(`
12 | DELETE FROM {{_externalAuths}}
13 | WHERE [[collectionId]] NOT IN (SELECT [[id]] FROM {{_collections}})
14 | `).Execute()
15 |
16 | return err
17 | }, func(db dbx.Builder) error {
18 | return nil
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/migrations/1689579878_renormalize_single_multiple_values.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/pocketbase/dbx"
5 | )
6 |
7 | // Renormalizes old single and multiple values of MultiValuer fields (file, select, relation)
8 | // (see https://github.com/hylarucoder/rocketbase/issues/2930).
9 | func init() {
10 | AppMigrations.Register(func(db dbx.Builder) error {
11 | return normalizeMultivaluerFields(db)
12 | }, func(db dbx.Builder) error {
13 | return nil
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/migrations/1690319366_reset_null_values.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/hylarucoder/rocketbase/daos"
7 | "github.com/hylarucoder/rocketbase/models"
8 | "github.com/hylarucoder/rocketbase/models/schema"
9 | "github.com/pocketbase/dbx"
10 | )
11 |
12 | // Reset all previously inserted NULL values to the fields zero-default.
13 | func init() {
14 | AppMigrations.Register(func(db dbx.Builder) error {
15 | dao := daos.New(db)
16 |
17 | collections := []*models.Collection{}
18 | if err := dao.CollectionQuery().All(&collections); err != nil {
19 | return err
20 | }
21 |
22 | for _, collection := range collections {
23 | if collection.IsView() {
24 | continue
25 | }
26 |
27 | for _, f := range collection.Schema.Fields() {
28 | defaultVal := "''"
29 |
30 | switch f.Type {
31 | case schema.FieldTypeJson:
32 | continue
33 | case schema.FieldTypeBool:
34 | defaultVal = "FALSE"
35 | case schema.FieldTypeNumber:
36 | defaultVal = "0"
37 | default:
38 | if opt, ok := f.Options.(schema.MultiValuer); ok && opt.IsMultiple() {
39 | defaultVal = "'[]'"
40 | }
41 | }
42 |
43 | _, err := db.NewQuery(fmt.Sprintf(
44 | "UPDATE {{%s}} SET [[%s]] = %s WHERE [[%s]] IS NULL",
45 | collection.Name,
46 | f.Name,
47 | defaultVal,
48 | f.Name,
49 | )).Execute()
50 | if err != nil {
51 | return err
52 | }
53 | }
54 | }
55 |
56 | return nil
57 | }, nil)
58 | }
59 |
--------------------------------------------------------------------------------
/migrations/1691747913_resave_views.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/hylarucoder/rocketbase/daos"
5 | "github.com/hylarucoder/rocketbase/models"
6 | "github.com/pocketbase/dbx"
7 | )
8 |
9 | // Resave all view collections to ensure that the proper id normalization is applied.
10 | // (see https://github.com/hylarucoder/rocketbase/issues/3110)
11 | func init() {
12 | AppMigrations.Register(func(db dbx.Builder) error {
13 | dao := daos.New(db)
14 |
15 | collections, err := dao.FindCollectionsByType(models.CollectionTypeView)
16 | if err != nil {
17 | return nil
18 | }
19 |
20 | for _, collection := range collections {
21 | // ignore errors to allow users to adjust
22 | // the view queries after app start
23 | dao.SaveCollection(collection)
24 | }
25 |
26 | return nil
27 | }, nil)
28 | }
29 |
--------------------------------------------------------------------------------
/migrations/1701496825_allow_single_oauth2_provider_in_multiple_auth_collections.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/pocketbase/dbx"
5 | )
6 |
7 | // Fixes the unique _externalAuths constraint for old installations
8 | // to allow a single OAuth2 provider to be registered for different auth collections.
9 | func init() {
10 | AppMigrations.Register(func(db dbx.Builder) error {
11 | _, createErr := db.NewQuery("CREATE UNIQUE INDEX IF NOT EXISTS _externalAuths_collection_provider_idx on {{_externalAuths}} ([[collectionId]], [[provider]], [[providerId]])").Execute()
12 | if createErr != nil {
13 | return createErr
14 | }
15 |
16 | _, dropErr := db.NewQuery("DROP INDEX IF EXISTS _externalAuths_provider_providerId_idx").Execute()
17 | if dropErr != nil {
18 | return dropErr
19 | }
20 |
21 | return nil
22 | }, nil)
23 | }
24 |
--------------------------------------------------------------------------------
/migrations/1702134272_set_default_json_max_size.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/hylarucoder/rocketbase/daos"
5 | "github.com/hylarucoder/rocketbase/models"
6 | "github.com/hylarucoder/rocketbase/models/schema"
7 | "github.com/pocketbase/dbx"
8 | )
9 |
10 | // Update all collections with json fields to have a default MaxSize json field option.
11 | func init() {
12 | AppMigrations.Register(func(db dbx.Builder) error {
13 | dao := daos.New(db)
14 |
15 | // note: update even the view collections to prevent
16 | // unnecessary change detections during the automigrate
17 | collections := []*models.Collection{}
18 | if err := dao.CollectionQuery().All(&collections); err != nil {
19 | return err
20 | }
21 |
22 | for _, collection := range collections {
23 | var needSave bool
24 |
25 | for _, f := range collection.Schema.Fields() {
26 | if f.Type != schema.FieldTypeJson {
27 | continue
28 | }
29 |
30 | options, _ := f.Options.(*schema.JsonOptions)
31 | if options == nil {
32 | options = &schema.JsonOptions{}
33 | }
34 | options.MaxSize = 2000000 // 2mb
35 | f.Options = options
36 | needSave = true
37 | }
38 |
39 | if !needSave {
40 | continue
41 | }
42 |
43 | // save only the collection model without updating its records table
44 | if err := dao.Save(collection); err != nil {
45 | return err
46 | }
47 | }
48 |
49 | return nil
50 | }, nil)
51 | }
52 |
--------------------------------------------------------------------------------
/migrations/logs/1677760279_uppsercase_method.go:
--------------------------------------------------------------------------------
1 | package logs
2 |
3 | import (
4 | "github.com/pocketbase/dbx"
5 | )
6 |
7 | // This migration normalizes the request logs method to UPPERCASE (eg. "get" => "GET").
8 | func init() {
9 | LogsMigrations.Register(func(db dbx.Builder) error {
10 | _, err := db.NewQuery("UPDATE {{_requests}} SET method=UPPER(method)").Execute()
11 |
12 | return err
13 | }, func(db dbx.Builder) error {
14 | _, err := db.NewQuery("UPDATE {{_requests}} SET method=LOWER(method)").Execute()
15 |
16 | return err
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/models/backup_file_info.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "github.com/hylarucoder/rocketbase/tools/types"
4 |
5 | type BackupFileInfo struct {
6 | Key string `json:"key"`
7 | Size int64 `json:"size"`
8 | Modified types.DateTime `json:"modified"`
9 | }
10 |
--------------------------------------------------------------------------------
/models/external_auth.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | var _ Model = (*ExternalAuth)(nil)
4 |
5 | type ExternalAuth struct {
6 | BaseModel
7 |
8 | CollectionId string `db:"collectionId" json:"collectionId"`
9 | RecordId string `db:"recordId" json:"recordId"`
10 | Provider string `db:"provider" json:"provider"`
11 | ProviderId string `db:"providerId" json:"providerId"`
12 | }
13 |
14 | func (m *ExternalAuth) TableName() string {
15 | return "_externalAuths"
16 | }
17 |
--------------------------------------------------------------------------------
/models/external_auth_test.go:
--------------------------------------------------------------------------------
1 | package models_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/models"
7 | )
8 |
9 | func TestExternalAuthTableName(t *testing.T) {
10 | t.Parallel()
11 |
12 | m := models.ExternalAuth{}
13 | if m.TableName() != "_externalAuths" {
14 | t.Fatalf("Unexpected table name, got %q", m.TableName())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/models/log.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/hylarucoder/rocketbase/tools/types"
5 | )
6 |
7 | var _ Model = (*Log)(nil)
8 |
9 | type Log struct {
10 | BaseModel
11 |
12 | Data types.JsonMap `db:"data" json:"data"`
13 | Message string `db:"message" json:"message"`
14 | Level int `db:"level" json:"level"`
15 | }
16 |
17 | func (m *Log) TableName() string {
18 | return "_logs"
19 | }
20 |
--------------------------------------------------------------------------------
/models/param.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/hylarucoder/rocketbase/tools/types"
5 | )
6 |
7 | var _ Model = (*Param)(nil)
8 |
9 | const (
10 | ParamAppSettings = "settings"
11 | )
12 |
13 | type Param struct {
14 | BaseModel
15 |
16 | Key string `db:"key" json:"key"`
17 | Value types.JsonRaw `db:"value" json:"value"`
18 | }
19 |
20 | func (m *Param) TableName() string {
21 | return "_params"
22 | }
23 |
--------------------------------------------------------------------------------
/models/param_test.go:
--------------------------------------------------------------------------------
1 | package models_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/models"
7 | )
8 |
9 | func TestParamTableName(t *testing.T) {
10 | t.Parallel()
11 |
12 | m := models.Param{}
13 | if m.TableName() != "_params" {
14 | t.Fatalf("Unexpected table name, got %q", m.TableName())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/models/request.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "github.com/hylarucoder/rocketbase/tools/types"
4 |
5 | var _ Model = (*Request)(nil)
6 |
7 | // list with the supported values for `Request.Auth`
8 | const (
9 | RequestAuthGuest = "guest"
10 | RequestAuthAdmin = "admin"
11 | RequestAuthRecord = "authRecord"
12 | )
13 |
14 | // Deprecated: Replaced by the Log model and will be removed in a future version.
15 | type Request struct {
16 | BaseModel
17 |
18 | Url string `db:"url" json:"url"`
19 | Method string `db:"method" json:"method"`
20 | Status int `db:"status" json:"status"`
21 | Auth string `db:"auth" json:"auth"`
22 | UserIp string `db:"userIp" json:"userIp"`
23 | RemoteIp string `db:"remoteIp" json:"remoteIp"`
24 | Referer string `db:"referer" json:"referer"`
25 | UserAgent string `db:"userAgent" json:"userAgent"`
26 | Meta types.JsonMap `db:"meta" json:"meta"`
27 | }
28 |
29 | func (m *Request) TableName() string {
30 | return "_requests"
31 | }
32 |
--------------------------------------------------------------------------------
/models/request_info.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/hylarucoder/rocketbase/models/schema"
7 | )
8 |
9 | const (
10 | RequestInfoContextDefault = "default"
11 | RequestInfoContextRealtime = "realtime"
12 | RequestInfoContextProtectedFile = "protectedFile"
13 | RequestInfoContextOAuth2 = "oauth2"
14 | )
15 |
16 | // RequestInfo defines a HTTP request data struct, usually used
17 | // as part of the `@request.*` filter resolver.
18 | type RequestInfo struct {
19 | Context string `json:"context"`
20 | Query map[string]any `json:"query"`
21 | Data map[string]any `json:"data"`
22 | Headers map[string]any `json:"headers"`
23 | AuthRecord *Record `json:"authRecord"`
24 | Admin *Admin `json:"admin"`
25 | Method string `json:"method"`
26 | }
27 |
28 | // HasModifierDataKeys loosely checks if the current struct has any modifier Data keys.
29 | func (r *RequestInfo) HasModifierDataKeys() bool {
30 | allModifiers := schema.FieldValueModifiers()
31 |
32 | for key := range r.Data {
33 | for _, m := range allModifiers {
34 | if strings.HasSuffix(key, m) {
35 | return true
36 | }
37 | }
38 | }
39 |
40 | return false
41 | }
42 |
--------------------------------------------------------------------------------
/models/request_info_test.go:
--------------------------------------------------------------------------------
1 | package models_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/models"
7 | )
8 |
9 | func TestRequestInfoHasModifierDataKeys(t *testing.T) {
10 | t.Parallel()
11 |
12 | scenarios := []struct {
13 | name string
14 | requestInfo *models.RequestInfo
15 | expected bool
16 | }{
17 | {
18 | "empty",
19 | &models.RequestInfo{},
20 | false,
21 | },
22 | {
23 | "Data with regular fields",
24 | &models.RequestInfo{
25 | Query: map[string]any{"data+": "demo"}, // should be ignored
26 | Data: map[string]any{"a": 123, "b": "test", "c.d": false},
27 | },
28 | false,
29 | },
30 | {
31 | "Data with +modifier fields",
32 | &models.RequestInfo{
33 | Data: map[string]any{"a+": 123, "b": "test", "c.d": false},
34 | },
35 | true,
36 | },
37 | {
38 | "Data with -modifier fields",
39 | &models.RequestInfo{
40 | Data: map[string]any{"a": 123, "b-": "test", "c.d": false},
41 | },
42 | true,
43 | },
44 | {
45 | "Data with mixed modifier fields",
46 | &models.RequestInfo{
47 | Data: map[string]any{"a": 123, "b-": "test", "c.d+": false},
48 | },
49 | true,
50 | },
51 | }
52 |
53 | for _, s := range scenarios {
54 | result := s.requestInfo.HasModifierDataKeys()
55 |
56 | if result != s.expected {
57 | t.Fatalf("[%s] Expected %v, got %v", s.name, s.expected, result)
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/models/request_test.go:
--------------------------------------------------------------------------------
1 | package models_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/models"
7 | )
8 |
9 | func TestRequestTableName(t *testing.T) {
10 | m := models.Request{}
11 | if m.TableName() != "_requests" {
12 | t.Fatalf("Unexpected table name, got %q", m.TableName())
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/models/table_info.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "github.com/hylarucoder/rocketbase/tools/types"
4 |
5 | type TableInfoRow struct {
6 | // the `db:"pk"` tag has special semantic so we cannot rename
7 | // the original field without specifying a custom mapper
8 | PK int
9 |
10 | Index int `db:"cid"`
11 | Name string `db:"column_name"`
12 | Type string `db:"type"`
13 | NotNull bool `db:"notnull"`
14 | DefaultValue types.JsonRaw `db:"column_default"`
15 | }
16 |
--------------------------------------------------------------------------------
/plugins/ghupdate/ghupdate_test.go:
--------------------------------------------------------------------------------
1 | package ghupdate
2 |
3 | import "testing"
4 |
5 | func TestCompareVersions(t *testing.T) {
6 | scenarios := []struct {
7 | a string
8 | b string
9 | expected int
10 | }{
11 | {"", "", 0},
12 | {"0", "", 0},
13 | {"1", "1.0.0", 0},
14 | {"1.1", "1.1.0", 0},
15 | {"1.1", "1.1.1", 1},
16 | {"1.1", "1.0.1", -1},
17 | {"1.0", "1.0.1", 1},
18 | {"1.10", "1.9", -1},
19 | {"1.2", "1.12", 1},
20 | {"3.2", "1.6", -1},
21 | {"0.0.2", "0.0.1", -1},
22 | {"0.16.2", "0.17.0", 1},
23 | {"1.15.0", "0.16.1", -1},
24 | {"1.2.9", "1.2.10", 1},
25 | {"3.2", "4.0", 1},
26 | {"3.2.4", "3.2.3", -1},
27 | }
28 |
29 | for i, s := range scenarios {
30 | result := compareVersions(s.a, s.b)
31 |
32 | if result != s.expected {
33 | t.Fatalf("[%d] Expected %q vs %q to result in %d, got %d", i, s.a, s.b, s.expected, result)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/ghupdate/release.go:
--------------------------------------------------------------------------------
1 | package ghupdate
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | type releaseAsset struct {
9 | Name string `json:"name"`
10 | DownloadUrl string `json:"browser_download_url"`
11 | Id int `json:"id"`
12 | Size int `json:"size"`
13 | }
14 |
15 | type release struct {
16 | Name string `json:"name"`
17 | Tag string `json:"tag_name"`
18 | Published string `json:"published_at"`
19 | Url string `json:"html_url"`
20 | Body string `json:"body"`
21 | Assets []*releaseAsset `json:"assets"`
22 | Id int `json:"id"`
23 | }
24 |
25 | // findAssetBySuffix returns the first available asset containing the specified suffix.
26 | func (r *release) findAssetBySuffix(suffix string) (*releaseAsset, error) {
27 | if suffix != "" {
28 | for _, asset := range r.Assets {
29 | if strings.HasSuffix(asset.Name, suffix) {
30 | return asset, nil
31 | }
32 | }
33 | }
34 |
35 | return nil, errors.New("missing asset containing " + suffix)
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/ghupdate/release_test.go:
--------------------------------------------------------------------------------
1 | package ghupdate
2 |
3 | import "testing"
4 |
5 | func TestReleaseFindAssetBySuffix(t *testing.T) {
6 | r := release{
7 | Assets: []*releaseAsset{
8 | {Name: "test1.zip", Id: 1},
9 | {Name: "test2.zip", Id: 2},
10 | {Name: "test22.zip", Id: 22},
11 | {Name: "test3.zip", Id: 3},
12 | },
13 | }
14 |
15 | asset, err := r.findAssetBySuffix("2.zip")
16 | if err != nil {
17 | t.Fatalf("Expected nil, got err: %v", err)
18 | }
19 |
20 | if asset.Id != 2 {
21 | t.Fatalf("Expected asset with id %d, got %v", 2, asset)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/plugins/jsvm/internal/types/generated/embed.go:
--------------------------------------------------------------------------------
1 | package generated
2 |
3 | import "embed"
4 |
5 | //go:embed types.d.ts
6 | var Types embed.FS
7 |
--------------------------------------------------------------------------------
/plugins/jsvm/mapper_test.go:
--------------------------------------------------------------------------------
1 | package jsvm_test
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/hylarucoder/rocketbase/plugins/jsvm"
8 | )
9 |
10 | func TestFieldMapper(t *testing.T) {
11 | mapper := jsvm.FieldMapper{}
12 |
13 | scenarios := []struct {
14 | name string
15 | expected string
16 | }{
17 | {"", ""},
18 | {"test", "test"},
19 | {"Test", "test"},
20 | {"miXeD", "miXeD"},
21 | {"MiXeD", "miXeD"},
22 | {"ResolveRequestAsJSON", "resolveRequestAsJSON"},
23 | {"Variable_with_underscore", "variable_with_underscore"},
24 | {"ALLCAPS", "allcaps"},
25 | {"ALL_CAPS_WITH_UNDERSCORE", "all_caps_with_underscore"},
26 | {"OIDCMap", "oidcMap"},
27 | {"MD5", "md5"},
28 | }
29 |
30 | for i, s := range scenarios {
31 | field := reflect.StructField{Name: s.name}
32 | if v := mapper.FieldName(nil, field); v != s.expected {
33 | t.Fatalf("[%d] Expected FieldName %q, got %q", i, s.expected, v)
34 | }
35 |
36 | method := reflect.Method{Name: s.name}
37 | if v := mapper.MethodName(nil, method); v != s.expected {
38 | t.Fatalf("[%d] Expected MethodName %q, got %q", i, s.expected, v)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/resolvers/resolvers.go:
--------------------------------------------------------------------------------
1 | // Package resolvers contains custom search.FieldResolver implementations.
2 | package resolvers
3 |
--------------------------------------------------------------------------------
/test_sh.sh:
--------------------------------------------------------------------------------
1 | # only TestAdminQuery
2 | go test -run TestAdminQuery ./...
--------------------------------------------------------------------------------
/tests/auth_helper_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/golang-jwt/jwt/v4"
7 | "github.com/hylarucoder/rocketbase/tools/security"
8 | )
9 |
10 | func TestRecordAuth(t *testing.T) {
11 | app, _ := NewTestApp()
12 | defer app.Cleanup()
13 | //record, _ := app.Dao().FindFirstRecordByFilter(""+
14 | // "users",
15 | // `email = 'test@example.com'`,
16 | //)
17 | //record, _ := app.Dao().FindFirstRecordByFilter(""+
18 | // "clients",
19 | // `email = 'test@example.com'`,
20 | //)
21 | //token, _ := security.NewJWT(
22 | // jwt.MapClaims{
23 | // "id": record.Collection().Id,
24 | // "type": "authRecord",
25 | // "collectionId": record.Id,
26 | // "verified": true,
27 | // },
28 | // record.TokenKey()+app.Settings().RecordAuthToken.Secret,
29 | // app.Settings().RecordAuthToken.Duration,
30 | //)
31 | record, _ := app.Dao().FindAdminByEmail("test@example.com")
32 | token, _ := security.NewJWT(
33 | jwt.MapClaims{
34 | "id": record.Id,
35 | "type": "admin",
36 | },
37 | record.TokenKey+app.Settings().AdminAuthToken.Secret,
38 | app.Settings().AdminAuthToken.Duration,
39 | )
40 | println("token =>", token)
41 | }
42 |
--------------------------------------------------------------------------------
/tests/data/.gitignore:
--------------------------------------------------------------------------------
1 | *.db-shm
2 | *.db-wal
3 | types.d.ts
4 |
--------------------------------------------------------------------------------
/tests/data/empty.file:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/empty.file
--------------------------------------------------------------------------------
/tests/data/storage/9n89pl5vkct6330/la4y2w4o98acwuj/300_uh_lkx91_hvb_Da8K5pl069.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/9n89pl5vkct6330/la4y2w4o98acwuj/300_uh_lkx91_hvb_Da8K5pl069.png
--------------------------------------------------------------------------------
/tests/data/storage/9n89pl5vkct6330/la4y2w4o98acwuj/300_uh_lkx91_hvb_Da8K5pl069.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":{"original-filename":"300_UhLKX91HVb.png"},"md5":"zZhZjzVvCvpcxtMAJie3GQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/9n89pl5vkct6330/la4y2w4o98acwuj/thumbs_300_uh_lkx91_hvb_Da8K5pl069.png/100x100_300_uh_lkx91_hvb_Da8K5pl069.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/9n89pl5vkct6330/la4y2w4o98acwuj/thumbs_300_uh_lkx91_hvb_Da8K5pl069.png/100x100_300_uh_lkx91_hvb_Da8K5pl069.png
--------------------------------------------------------------------------------
/tests/data/storage/9n89pl5vkct6330/la4y2w4o98acwuj/thumbs_300_uh_lkx91_hvb_Da8K5pl069.png/100x100_300_uh_lkx91_hvb_Da8K5pl069.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"R7TqfvF8HP3C4+FO2eZ9tg=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/9n89pl5vkct6330/qjeql998mtp1azp/logo_vcf_jjg5_tah_9MtIHytOmZ.svg.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/svg+xml","user.metadata":{"original-filename":"logo_vcfJJG5TAh.svg"},"md5":"9/B7afas4c3O6vbFcbpOug=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"zZhZjzVvCvpcxtMAJie3GQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/0x50_300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/0x50_300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/0x50_300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"iqCiUST0LvGibMMM1qxZAA=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/100x100_300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/100x100_300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/100x100_300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"R7TqfvF8HP3C4+FO2eZ9tg=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x0_300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x0_300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x0_300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"XQUKRr4ZwZ9MTo2kR+KfIg=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50_300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50_300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50_300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"EoyFICWlQdYZgUYTEMsp/A=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50b_300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50b_300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50b_300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"Pb/AI46vKOtcBW9bOsdREA=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50f_300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50f_300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50f_300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"iqCiUST0LvGibMMM1qxZAA=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50t_300_1SEi6Q6U72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50t_300_1SEi6Q6U72.png
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/4q1xlclmfloku33/thumbs_300_1SEi6Q6U72.png/70x50t_300_1SEi6Q6U72.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"rcwrlxKlvwTgDpsHLHzBqA=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/oap640cot4yru2s/test_kfd2wYLxkz.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tests/data/storage/_pb_users_auth_/oap640cot4yru2s/test_kfd2wYLxkz.txt.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"2Oj8otwPiW/Xy0ywAxuiSQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/300_WlbFWSGmW9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/300_WlbFWSGmW9.png
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/300_WlbFWSGmW9.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"zZhZjzVvCvpcxtMAJie3GQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/logo_vcfJJG5TAh.svg.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/svg+xml","user.metadata":null,"md5":"9/B7afas4c3O6vbFcbpOug=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_MaWC6mWyrP.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_MaWC6mWyrP.txt.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":{"original-filename":"test.txt"},"md5":"2Oj8otwPiW/Xy0ywAxuiSQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_QZFjKjXchk.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_QZFjKjXchk.txt.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"2Oj8otwPiW/Xy0ywAxuiSQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_d61b33QdDU.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_d61b33QdDU.txt.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"2Oj8otwPiW/Xy0ywAxuiSQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_tC1Yc87DfC.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/test_tC1Yc87DfC.txt.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":{"original-filename":"test.txt"},"md5":"2Oj8otwPiW/Xy0ywAxuiSQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/thumbs_300_WlbFWSGmW9.png/100x100_300_WlbFWSGmW9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/thumbs_300_WlbFWSGmW9.png/100x100_300_WlbFWSGmW9.png
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/84nmscqy84lsi1t/thumbs_300_WlbFWSGmW9.png/100x100_300_WlbFWSGmW9.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"R7TqfvF8HP3C4+FO2eZ9tg=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/al1h9ijdeojtsjy/300_Jsjq7RdBgA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wsmn24bux7wo113/al1h9ijdeojtsjy/300_Jsjq7RdBgA.png
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/al1h9ijdeojtsjy/300_Jsjq7RdBgA.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"zZhZjzVvCvpcxtMAJie3GQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/al1h9ijdeojtsjy/thumbs_300_Jsjq7RdBgA.png/100x100_300_Jsjq7RdBgA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wsmn24bux7wo113/al1h9ijdeojtsjy/thumbs_300_Jsjq7RdBgA.png/100x100_300_Jsjq7RdBgA.png
--------------------------------------------------------------------------------
/tests/data/storage/wsmn24bux7wo113/al1h9ijdeojtsjy/thumbs_300_Jsjq7RdBgA.png/100x100_300_Jsjq7RdBgA.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"R7TqfvF8HP3C4+FO2eZ9tg=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/7nwo8tuiatetxdm/test_JnXeKEwgwr.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/7nwo8tuiatetxdm/test_JnXeKEwgwr.txt.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"2Oj8otwPiW/Xy0ywAxuiSQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/300_UhLKX91HVb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/300_UhLKX91HVb.png
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/300_UhLKX91HVb.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"zZhZjzVvCvpcxtMAJie3GQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/test_FLurQTgrY8.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/test_FLurQTgrY8.txt.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"2Oj8otwPiW/Xy0ywAxuiSQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/thumbs_300_UhLKX91HVb.png/100x100_300_UhLKX91HVb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/thumbs_300_UhLKX91HVb.png/100x100_300_UhLKX91HVb.png
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/lcl9d87w22ml6jy/thumbs_300_UhLKX91HVb.png/100x100_300_UhLKX91HVb.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"R7TqfvF8HP3C4+FO2eZ9tg=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/mk5fmymtx4wsprk/300_JdfBOieXAW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wzlqyes4orhoygb/mk5fmymtx4wsprk/300_JdfBOieXAW.png
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/mk5fmymtx4wsprk/300_JdfBOieXAW.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"zZhZjzVvCvpcxtMAJie3GQ=="}
2 |
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/mk5fmymtx4wsprk/thumbs_300_JdfBOieXAW.png/100x100_300_JdfBOieXAW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/tests/data/storage/wzlqyes4orhoygb/mk5fmymtx4wsprk/thumbs_300_JdfBOieXAW.png/100x100_300_JdfBOieXAW.png
--------------------------------------------------------------------------------
/tests/data/storage/wzlqyes4orhoygb/mk5fmymtx4wsprk/thumbs_300_JdfBOieXAW.png/100x100_300_JdfBOieXAW.png.attrs:
--------------------------------------------------------------------------------
1 | {"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"R7TqfvF8HP3C4+FO2eZ9tg=="}
2 |
--------------------------------------------------------------------------------
/tests/logs.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | func MockLogsData(app *TestApp) error {
4 | _, err := app.LogsDB().NewQuery(`
5 | delete from {{_logs}};
6 |
7 | insert into {{_logs}} (
8 | [[id]],
9 | [[level]],
10 | [[message]],
11 | [[data]],
12 | [[created]],
13 | [[updated]]
14 | )
15 | values
16 | (
17 | "873f2133-9f38-44fb-bf82-c8f53b310d91",
18 | 0,
19 | "test_message1",
20 | '{"status":200}',
21 | "2022-05-01 10:00:00.123Z",
22 | "2022-05-01 10:00:00.123Z"
23 | ),
24 | (
25 | "f2133873-44fb-9f38-bf82-c918f53b310d",
26 | 8,
27 | "test_message2",
28 | '{"status":400}',
29 | "2022-05-02 10:00:00.123Z",
30 | "2022-05-02 10:00:00.123Z"
31 | );
32 | `).Execute()
33 |
34 | return err
35 | }
36 |
--------------------------------------------------------------------------------
/tests/mailer.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "github.com/hylarucoder/rocketbase/tools/mailer"
5 | "sync"
6 | )
7 |
8 | var _ mailer.Mailer = (*TestMailer)(nil)
9 |
10 | // TestMailer is a mock `mailer.Mailer` implementation.
11 | type TestMailer struct {
12 | mux sync.Mutex
13 |
14 | TotalSend int
15 | LastMessage mailer.Message
16 |
17 | // @todo consider deprecating the above 2 fields?
18 | SentMessages []mailer.Message
19 | }
20 |
21 | // Reset clears any previously test collected data.
22 | func (tm *TestMailer) Reset() {
23 | tm.mux.Lock()
24 | defer tm.mux.Unlock()
25 |
26 | tm.TotalSend = 0
27 | tm.LastMessage = mailer.Message{}
28 | tm.SentMessages = nil
29 | }
30 |
31 | // Send implements `mailer.Mailer` interface.
32 | func (tm *TestMailer) Send(m *mailer.Message) error {
33 | tm.mux.Lock()
34 | defer tm.mux.Unlock()
35 |
36 | tm.TotalSend++
37 | tm.LastMessage = *m
38 | tm.SentMessages = append(tm.SentMessages, tm.LastMessage)
39 |
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/tests/request.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "mime/multipart"
7 | "os"
8 | )
9 |
10 | // MockMultipartData creates a mocked multipart/form-data payload.
11 | //
12 | // Example
13 | //
14 | // data, mp, err := tests.MockMultipartData(
15 | // map[string]string{"title": "new"},
16 | // "file1",
17 | // "file2",
18 | // ...
19 | // )
20 | func MockMultipartData(data map[string]string, fileFields ...string) (*bytes.Buffer, *multipart.Writer, error) {
21 | body := new(bytes.Buffer)
22 | mp := multipart.NewWriter(body)
23 | defer mp.Close()
24 |
25 | // write data fields
26 | for k, v := range data {
27 | mp.WriteField(k, v)
28 | }
29 |
30 | // write file fields
31 | for _, fileField := range fileFields {
32 | // create a test temporary file
33 | err := func() error {
34 | tmpFile, err := os.CreateTemp(os.TempDir(), "tmpfile-*.txt")
35 | if err != nil {
36 | return err
37 | }
38 |
39 | if _, err := tmpFile.Write([]byte("test")); err != nil {
40 | return err
41 | }
42 | tmpFile.Seek(0, 0)
43 | defer tmpFile.Close()
44 | defer os.Remove(tmpFile.Name())
45 |
46 | // stub uploaded file
47 | w, err := mp.CreateFormFile(fileField, tmpFile.Name())
48 | if err != nil {
49 | return err
50 | }
51 | if _, err := io.Copy(w, tmpFile); err != nil {
52 | return err
53 | }
54 |
55 | return nil
56 | }()
57 | if err != nil {
58 | return nil, nil, err
59 | }
60 | }
61 |
62 | return body, mp, nil
63 | }
64 |
--------------------------------------------------------------------------------
/tokens/admin.go:
--------------------------------------------------------------------------------
1 | package tokens
2 |
3 | import (
4 | "github.com/golang-jwt/jwt/v4"
5 | "github.com/hylarucoder/rocketbase/core"
6 | "github.com/hylarucoder/rocketbase/models"
7 | "github.com/hylarucoder/rocketbase/tools/security"
8 | )
9 |
10 | // NewAdminAuthToken generates and returns a new admin authentication token.
11 | func NewAdminAuthToken(app core.App, admin *models.Admin) (string, error) {
12 | return security.NewJWT(
13 | jwt.MapClaims{"id": admin.Id, "type": TypeAdmin},
14 | (admin.TokenKey + app.Settings().AdminAuthToken.Secret),
15 | app.Settings().AdminAuthToken.Duration,
16 | )
17 | }
18 |
19 | // NewAdminResetPasswordToken generates and returns a new admin password reset request token.
20 | func NewAdminResetPasswordToken(app core.App, admin *models.Admin) (string, error) {
21 | return security.NewJWT(
22 | jwt.MapClaims{"id": admin.Id, "type": TypeAdmin, "email": admin.Email},
23 | (admin.TokenKey + app.Settings().AdminPasswordResetToken.Secret),
24 | app.Settings().AdminPasswordResetToken.Duration,
25 | )
26 | }
27 |
28 | // NewAdminFileToken generates and returns a new admin private file access token.
29 | func NewAdminFileToken(app core.App, admin *models.Admin) (string, error) {
30 | return security.NewJWT(
31 | jwt.MapClaims{"id": admin.Id, "type": TypeAdmin},
32 | (admin.TokenKey + app.Settings().AdminFileToken.Secret),
33 | app.Settings().AdminFileToken.Duration,
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/tokens/tokens.go:
--------------------------------------------------------------------------------
1 | // Package tokens implements various user and admin tokens generation methods.
2 | package tokens
3 |
4 | const (
5 | TypeAdmin = "admin"
6 | TypeAuthRecord = "authRecord"
7 | )
8 |
--------------------------------------------------------------------------------
/tools/logger/log.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "log/slog"
5 | "time"
6 |
7 | "github.com/hylarucoder/rocketbase/tools/types"
8 | )
9 |
10 | // Log is similar to [slog.Record] bit contains the log attributes as
11 | // preformatted JSON map.
12 | type Log struct {
13 | Time time.Time
14 | Message string
15 | Level slog.Level
16 | Data types.JsonMap
17 | }
18 |
--------------------------------------------------------------------------------
/tools/mailer/mailer.go:
--------------------------------------------------------------------------------
1 | package mailer
2 |
3 | import (
4 | "io"
5 | "net/mail"
6 | )
7 |
8 | // Message defines a generic email message struct.
9 | type Message struct {
10 | From mail.Address `json:"from"`
11 | To []mail.Address `json:"to"`
12 | Bcc []mail.Address `json:"bcc"`
13 | Cc []mail.Address `json:"cc"`
14 | Subject string `json:"subject"`
15 | HTML string `json:"html"`
16 | Text string `json:"text"`
17 | Headers map[string]string `json:"headers"`
18 | Attachments map[string]io.Reader `json:"attachments"`
19 | }
20 |
21 | // Mailer defines a base mail client interface.
22 | type Mailer interface {
23 | // Send sends an email with the provided Message.
24 | Send(message *Message) error
25 | }
26 |
27 | // addressesToStrings converts the provided address to a list of serialized RFC 5322 strings.
28 | //
29 | // To export only the email part of mail.Address, you can set withName to false.
30 | func addressesToStrings(addresses []mail.Address, withName bool) []string {
31 | result := make([]string, len(addresses))
32 |
33 | for i, addr := range addresses {
34 | if withName && addr.Name != "" {
35 | result[i] = addr.String()
36 | } else {
37 | // keep only the email part to avoid wrapping in angle-brackets
38 | result[i] = addr.Address
39 | }
40 | }
41 |
42 | return result
43 | }
44 |
--------------------------------------------------------------------------------
/tools/migrate/list_test.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestMigrationsList(t *testing.T) {
8 | l := MigrationsList{}
9 |
10 | l.Register(nil, nil, "3_test.go")
11 | l.Register(nil, nil, "1_test.go")
12 | l.Register(nil, nil, "2_test.go")
13 | l.Register(nil, nil /* auto detect file name */)
14 |
15 | expected := []string{
16 | "1_test.go",
17 | "2_test.go",
18 | "3_test.go",
19 | "list_test.go",
20 | }
21 |
22 | items := l.Items()
23 | if len(items) != len(expected) {
24 | t.Fatalf("Expected %d items, got %d: \n%#v", len(expected), len(items), items)
25 | }
26 |
27 | for i, name := range expected {
28 | item := l.Item(i)
29 | if item.File != name {
30 | t.Fatalf("Expected name %s for index %d, got %s", name, i, item.File)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tools/rest/uploaded_file.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/hylarucoder/rocketbase/tools/filesystem"
7 | )
8 |
9 | // DefaultMaxMemory defines the default max memory bytes that
10 | // will be used when parsing a form request body.
11 | const DefaultMaxMemory = 32 << 20 // 32mb
12 |
13 | // FindUploadedFiles extracts all form files of "key" from a http request
14 | // and returns a slice with filesystem.File instances (if any).
15 | func FindUploadedFiles(r *http.Request, key string) ([]*filesystem.File, error) {
16 | if r.MultipartForm == nil {
17 | err := r.ParseMultipartForm(DefaultMaxMemory)
18 | if err != nil {
19 | return nil, err
20 | }
21 | }
22 |
23 | if r.MultipartForm == nil || r.MultipartForm.File == nil || len(r.MultipartForm.File[key]) == 0 {
24 | return nil, http.ErrMissingFile
25 | }
26 |
27 | result := make([]*filesystem.File, 0, len(r.MultipartForm.File[key]))
28 |
29 | for _, fh := range r.MultipartForm.File[key] {
30 | file, err := filesystem.NewFileFromMultipart(fh)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | result = append(result, file)
36 | }
37 |
38 | return result, nil
39 | }
40 |
--------------------------------------------------------------------------------
/tools/rest/url.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "net/url"
5 | "path"
6 | "strings"
7 | )
8 |
9 | // NormalizeUrl removes duplicated slashes from a url path.
10 | func NormalizeUrl(originalUrl string) (string, error) {
11 | u, err := url.Parse(originalUrl)
12 | if err != nil {
13 | return "", err
14 | }
15 |
16 | hasSlash := strings.HasSuffix(u.Path, "/")
17 |
18 | // clean up path by removing duplicated /
19 | u.Path = path.Clean(u.Path)
20 | u.RawPath = path.Clean(u.RawPath)
21 |
22 | // restore original trailing slash
23 | if hasSlash && !strings.HasSuffix(u.Path, "/") {
24 | u.Path += "/"
25 | u.RawPath += "/"
26 | }
27 |
28 | return u.String(), nil
29 | }
30 |
--------------------------------------------------------------------------------
/tools/rest/url_test.go:
--------------------------------------------------------------------------------
1 | package rest_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/tools/rest"
7 | )
8 |
9 | func TestNormalizeUrl(t *testing.T) {
10 | scenarios := []struct {
11 | url string
12 | expectError bool
13 | expectUrl string
14 | }{
15 | {":/", true, ""},
16 | {"./", false, "./"},
17 | {"../../test////", false, "../../test/"},
18 | {"/a/b/c", false, "/a/b/c"},
19 | {"a/////b//c/", false, "a/b/c/"},
20 | {"/a/////b//c", false, "/a/b/c"},
21 | {"///a/b/c", false, "/a/b/c"},
22 | {"//a/b/c", false, "//a/b/c"}, // preserve "auto-schema"
23 | {"http://a/b/c", false, "http://a/b/c"},
24 | {"a//bc?test=1//dd", false, "a/bc?test=1//dd"}, // only the path is normalized
25 | {"a//bc?test=1#12///3", false, "a/bc?test=1#12///3"}, // only the path is normalized
26 | }
27 |
28 | for i, s := range scenarios {
29 | result, err := rest.NormalizeUrl(s.url)
30 |
31 | hasErr := err != nil
32 | if hasErr != s.expectError {
33 | t.Errorf("(%d) Expected hasErr %v, got %v", i, s.expectError, hasErr)
34 | }
35 |
36 | if result != s.expectUrl {
37 | t.Errorf("(%d) Expected url %q, got %q", i, s.expectUrl, result)
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tools/routine/routine.go:
--------------------------------------------------------------------------------
1 | package routine
2 |
3 | import (
4 | "log"
5 | "runtime/debug"
6 | "sync"
7 | )
8 |
9 | // FireAndForget executes `f()` in a new go routine and auto recovers if panic.
10 | //
11 | // **Note:** Use this only if you are not interested in the result of `f()`
12 | // and don't want to block the parent go routine.
13 | func FireAndForget(f func(), wg ...*sync.WaitGroup) {
14 | if len(wg) > 0 && wg[0] != nil {
15 | wg[0].Add(1)
16 | }
17 |
18 | go func() {
19 | if len(wg) > 0 && wg[0] != nil {
20 | defer wg[0].Done()
21 | }
22 |
23 | defer func() {
24 | if err := recover(); err != nil {
25 | log.Printf("RECOVERED FROM PANIC (safe to ignore): %v", err)
26 | log.Println(string(debug.Stack()))
27 | }
28 | }()
29 |
30 | f()
31 | }()
32 | }
33 |
--------------------------------------------------------------------------------
/tools/routine/routine_test.go:
--------------------------------------------------------------------------------
1 | package routine_test
2 |
3 | import (
4 | "sync"
5 | "testing"
6 |
7 | "github.com/hylarucoder/rocketbase/tools/routine"
8 | )
9 |
10 | func TestFireAndForget(t *testing.T) {
11 | called := false
12 |
13 | fn := func() {
14 | called = true
15 | panic("test")
16 | }
17 |
18 | wg := &sync.WaitGroup{}
19 |
20 | routine.FireAndForget(fn, wg)
21 |
22 | wg.Wait()
23 |
24 | if !called {
25 | t.Error("Expected fn to be called.")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tools/search/identifier_macros_test.go:
--------------------------------------------------------------------------------
1 | package search
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func TestIdentifierMacros(t *testing.T) {
9 | originalTimeNow := timeNow
10 |
11 | timeNow = func() time.Time {
12 | return time.Date(2023, 2, 3, 4, 5, 6, 7, time.UTC)
13 | }
14 |
15 | testMacros := map[string]any{
16 | "@now": "2023-02-03 04:05:06.000Z",
17 | "@second": 6,
18 | "@minute": 5,
19 | "@hour": 4,
20 | "@day": 3,
21 | "@month": 2,
22 | "@weekday": 5,
23 | "@year": 2023,
24 | "@todayStart": "2023-02-03 00:00:00.000Z",
25 | "@todayEnd": "2023-02-03 23:59:59.999Z",
26 | "@monthStart": "2023-02-01 00:00:00.000Z",
27 | "@monthEnd": "2023-02-28 23:59:59.999Z",
28 | "@yearStart": "2023-01-01 00:00:00.000Z",
29 | "@yearEnd": "2023-12-31 23:59:59.999Z",
30 | }
31 |
32 | if len(testMacros) != len(identifierMacros) {
33 | t.Fatalf("Expected %d macros, got %d", len(testMacros), len(identifierMacros))
34 | }
35 |
36 | for key, expected := range testMacros {
37 | t.Run(key, func(t *testing.T) {
38 | macro, ok := identifierMacros[key]
39 | if !ok {
40 | t.Fatalf("Missing macro %s", key)
41 | }
42 |
43 | result, err := macro()
44 | if err != nil {
45 | t.Fatal(err)
46 | }
47 |
48 | if result != expected {
49 | t.Fatalf("Expected %q, got %q", expected, result)
50 | }
51 | })
52 | }
53 |
54 | // restore
55 | timeNow = originalTimeNow
56 | }
57 |
--------------------------------------------------------------------------------
/tools/template/renderer.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "html/template"
7 | )
8 |
9 | // Renderer defines a single parsed template.
10 | type Renderer struct {
11 | template *template.Template
12 | parseError error
13 | }
14 |
15 | // Render executes the template with the specified data as the dot object
16 | // and returns the result as plain string.
17 | func (r *Renderer) Render(data any) (string, error) {
18 | if r.parseError != nil {
19 | return "", r.parseError
20 | }
21 |
22 | if r.template == nil {
23 | return "", errors.New("invalid or nil template")
24 | }
25 |
26 | buf := new(bytes.Buffer)
27 |
28 | if err := r.template.Execute(buf, data); err != nil {
29 | return "", err
30 | }
31 |
32 | return buf.String(), nil
33 | }
34 |
--------------------------------------------------------------------------------
/tools/template/renderer_test.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "errors"
5 | "html/template"
6 | "testing"
7 | )
8 |
9 | func TestRendererRender(t *testing.T) {
10 | tpl, _ := template.New("").Parse("Hello {{.Name}}!")
11 | tpl.Option("missingkey=error") // enforce execute errors
12 |
13 | scenarios := map[string]struct {
14 | renderer *Renderer
15 | data any
16 | expectedHasErr bool
17 | expectedResult string
18 | }{
19 | "with nil template": {
20 | &Renderer{},
21 | nil,
22 | true,
23 | "",
24 | },
25 | "with parse error": {
26 | &Renderer{
27 | template: tpl,
28 | parseError: errors.New("test"),
29 | },
30 | nil,
31 | true,
32 | "",
33 | },
34 | "with execute error": {
35 | &Renderer{template: tpl},
36 | nil,
37 | true,
38 | "",
39 | },
40 | "no error": {
41 | &Renderer{template: tpl},
42 | struct{ Name string }{"world"},
43 | false,
44 | "Hello world!",
45 | },
46 | }
47 |
48 | for name, s := range scenarios {
49 | t.Run(name, func(t *testing.T) {
50 | result, err := s.renderer.Render(s.data)
51 |
52 | hasErr := err != nil
53 |
54 | if s.expectedHasErr != hasErr {
55 | t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectedHasErr, hasErr, err)
56 | }
57 |
58 | if s.expectedResult != result {
59 | t.Fatalf("Expected result %v, got %v", s.expectedResult, result)
60 | }
61 | })
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tools/test_utils/test_utils.go:
--------------------------------------------------------------------------------
1 | package test_utils
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "sync"
8 |
9 | "github.com/joho/godotenv"
10 | )
11 |
12 | var loadOnce sync.Once
13 |
14 | func LoadTestEnv(envFileName ...string) {
15 | loadOnce.Do(func() {
16 | fileName := ".env.test"
17 | if len(envFileName) > 0 {
18 | fileName = envFileName[0]
19 | }
20 |
21 | dir, err := os.Getwd()
22 | if err != nil {
23 | fmt.Printf("Error getting current working directory: %v\n", err)
24 | } else {
25 | for {
26 | envFile := filepath.Join(dir, fileName)
27 | if _, err := os.Stat(envFile); err == nil {
28 | if err := godotenv.Load(envFile); err != nil {
29 | fmt.Printf("Error loading %s file: %v\n", fileName, err)
30 | } else {
31 | fmt.Printf("Loaded %s from: %s\n", fileName, envFile)
32 | break
33 | }
34 | }
35 |
36 | parent := filepath.Dir(dir)
37 | if parent == dir {
38 | fmt.Printf("Reached root directory, %s not found\n", fileName)
39 | break
40 | }
41 | dir = parent
42 | }
43 | }
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/tools/types/json_array.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "database/sql/driver"
5 | "encoding/json"
6 | "fmt"
7 | )
8 |
9 | // JsonArray defines a slice that is safe for json and db read/write.
10 | type JsonArray[T any] []T
11 |
12 | // internal alias to prevent recursion during marshalization.
13 | type jsonArrayAlias[T any] JsonArray[T]
14 |
15 | // MarshalJSON implements the [json.Marshaler] interface.
16 | func (m JsonArray[T]) MarshalJSON() ([]byte, error) {
17 | // initialize an empty map to ensure that `[]` is returned as json
18 | if m == nil {
19 | m = JsonArray[T]{}
20 | }
21 |
22 | return json.Marshal(jsonArrayAlias[T](m))
23 | }
24 |
25 | // Value implements the [driver.Valuer] interface.
26 | func (m JsonArray[T]) Value() (driver.Value, error) {
27 | data, err := json.Marshal(m)
28 |
29 | return string(data), err
30 | }
31 |
32 | // Scan implements [sql.Scanner] interface to scan the provided value
33 | // into the current JsonArray[T] instance.
34 | func (m *JsonArray[T]) Scan(value any) error {
35 | var data []byte
36 | switch v := value.(type) {
37 | case nil:
38 | // no cast needed
39 | case []byte:
40 | data = v
41 | case string:
42 | data = []byte(v)
43 | default:
44 | return fmt.Errorf("failed to unmarshal JsonArray value: %q", value)
45 | }
46 |
47 | if len(data) == 0 {
48 | data = []byte("[]")
49 | }
50 |
51 | return json.Unmarshal(data, m)
52 | }
53 |
--------------------------------------------------------------------------------
/tools/types/types.go:
--------------------------------------------------------------------------------
1 | // Package types implements some commonly used db serializable types
2 | // like datetime, json, etc.
3 | package types
4 |
5 | // Pointer is a generic helper that returns val as *T.
6 | func Pointer[T any](val T) *T {
7 | return &val
8 | }
9 |
--------------------------------------------------------------------------------
/tools/types/types_test.go:
--------------------------------------------------------------------------------
1 | package types_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hylarucoder/rocketbase/tools/types"
7 | )
8 |
9 | func TestPointer(t *testing.T) {
10 | s1 := types.Pointer("")
11 | if s1 == nil || *s1 != "" {
12 | t.Fatalf("Expected empty string pointer, got %#v", s1)
13 | }
14 |
15 | s2 := types.Pointer("test")
16 | if s2 == nil || *s2 != "test" {
17 | t.Fatalf("Expected 'test' string pointer, got %#v", s2)
18 | }
19 |
20 | s3 := types.Pointer(123)
21 | if s3 == nil || *s3 != 123 {
22 | t.Fatalf("Expected 123 string pointer, got %#v", s3)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ui/.env:
--------------------------------------------------------------------------------
1 | # all environments should start with 'PB_' prefix
2 | PB_BACKEND_URL = "../"
3 | PB_INSTALLER_PARAM = "installer"
4 | PB_OAUTH2_EXAMPLE = "https://pocketbase.io/docs/authentication/#oauth2-integration"
5 | PB_RULES_SYNTAX_DOCS = "https://pocketbase.io/docs/api-rules-and-filters/"
6 | PB_FILE_UPLOAD_DOCS = "https://pocketbase.io/docs/files-handling/"
7 | PB_PROTECTED_FILE_DOCS = "https://pocketbase.io/docs/files-handling/#protected-files"
8 | PB_DOCS_URL = "https://pocketbase.io/docs/"
9 | PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk"
10 | PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk"
11 | PB_RELEASES = "https://github.com/hylarucoder/rocketbase/releases"
12 | PB_VERSION = "v0.22.22"
13 |
--------------------------------------------------------------------------------
/ui/.env.development:
--------------------------------------------------------------------------------
1 | PB_BACKEND_URL = "http://127.0.0.1:8090"
2 |
--------------------------------------------------------------------------------
/ui/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /.vscode/
3 | .DS_Store
4 |
5 | # exclude local env files
6 | .env.local
7 | .env.*.local
8 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | PocketBase Admin dashboard UI
2 | ======================================================================
3 |
4 | This is the PocketBase Admin dashboard UI (built with Svelte and Vite).
5 |
6 | Although it could be used independently, it is mainly intended to be embedded
7 | as part of a PocketBase app executable (hence the `embed.go` file).
8 |
9 | ## Development
10 |
11 | Download the repo and run the appropriate console commands:
12 |
13 | ```sh
14 | # install dependencies
15 | npm install
16 |
17 | # start a dev server with hot reload at localhost:3000
18 | npm run dev
19 |
20 | # or generate production ready bundle in dist/ directory
21 | npm run build
22 | ```
23 |
--------------------------------------------------------------------------------
/ui/dist/assets/ListApiDocs-ByASLUZu.css:
--------------------------------------------------------------------------------
1 | .filter-op.svelte-1w7s5nw{display:inline-block;vertical-align:top;margin-right:5px;width:30px;text-align:center;padding-left:0;padding-right:0}
2 |
--------------------------------------------------------------------------------
/ui/dist/assets/PageOAuth2RedirectFailure-BS8Cmo1H.js:
--------------------------------------------------------------------------------
1 | import{S as o,i,s as c,e as r,f as l,g as u,y as a,o as d,I as h}from"./index-CH1esvLs.js";function f(n){let t;return{c(){t=r("div"),t.innerHTML='Auth failed. You can close this window and go back to the app to try again. ',l(t,"class","content txt-hint txt-center p-base")},m(e,s){u(e,t,s)},p:a,i:a,o:a,d(e){e&&d(t)}}}function p(n){return h(()=>{window.close()}),[]}class g extends o{constructor(t){super(),i(this,t,p,f,c,{})}}export{g as default};
2 |
--------------------------------------------------------------------------------
/ui/dist/assets/PageOAuth2RedirectSuccess-D1_hRUhp.js:
--------------------------------------------------------------------------------
1 | import{S as o,i as c,s as i,e as r,f as u,g as l,y as s,o as d,I as h}from"./index-CH1esvLs.js";function p(n){let t;return{c(){t=r("div"),t.innerHTML='Auth completed. You can close this window and go back to the app. ',u(t,"class","content txt-hint txt-center p-base")},m(e,a){l(e,t,a)},p:s,i:s,o:s,d(e){e&&d(t)}}}function f(n){return h(()=>{window.close()}),[]}class x extends o{constructor(t){super(),c(this,t,f,p,i,{})}}export{x as default};
2 |
--------------------------------------------------------------------------------
/ui/dist/assets/SdkTabs-CSWLpEgj.css:
--------------------------------------------------------------------------------
1 | .sdk-tabs.svelte-1maocj6 .tabs-header .tab-item.svelte-1maocj6{min-width:100px}
2 |
--------------------------------------------------------------------------------
/ui/dist/fonts/remixicon/remixicon.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/remixicon/remixicon.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-700.woff2
--------------------------------------------------------------------------------
/ui/dist/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-regular.woff2
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar0.svg:
--------------------------------------------------------------------------------
1 | Mary Roebling
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar1.svg:
--------------------------------------------------------------------------------
1 | Nellie Bly
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar2.svg:
--------------------------------------------------------------------------------
1 | Elizabeth Peratrovich
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar3.svg:
--------------------------------------------------------------------------------
1 | Amelia Boynton
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar4.svg:
--------------------------------------------------------------------------------
1 | Victoria Woodhull
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar5.svg:
--------------------------------------------------------------------------------
1 | Chien-Shiung
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar6.svg:
--------------------------------------------------------------------------------
1 | Hetty Green
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar7.svg:
--------------------------------------------------------------------------------
1 | Elizabeth Peratrovich
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar8.svg:
--------------------------------------------------------------------------------
1 | Jane Johnston
2 |
--------------------------------------------------------------------------------
/ui/dist/images/avatars/avatar9.svg:
--------------------------------------------------------------------------------
1 | Virginia Apgar
2 |
--------------------------------------------------------------------------------
/ui/dist/images/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ui/dist/images/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/favicon.ico
--------------------------------------------------------------------------------
/ui/dist/images/favicon/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/mstile-144x144.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/mstile-310x150.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/mstile-310x310.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/images/favicon/mstile-70x70.png
--------------------------------------------------------------------------------
/ui/dist/images/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/_/images/favicon/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/_/images/favicon/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/apple.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/bitbucket.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Facebook
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/gitee.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/google.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/livechat.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/microsoft.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Microsoft
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/oidc.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/patreon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/strava.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/twitch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/vk.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/dist/images/oauth2/yandex.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/plugins/code/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 6.8.4 (2024-06-19)
3 | */
4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}();
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/plugins/visualblocks/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 6.8.4 (2024-06-19)
3 | */
4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const s=(t,s,o)=>{t.dom.toggleClass(t.getBody(),"mce-visualblocks"),o.set(!o.get()),((t,s)=>{t.dispatch("VisualBlocks",{state:s})})(t,o.get())},o=("visualblocks_default_state",t=>t.options.get("visualblocks_default_state"));const e=(t,s)=>o=>{o.setActive(s.get());const e=t=>o.setActive(t.state);return t.on("VisualBlocks",e),()=>t.off("VisualBlocks",e)};t.add("visualblocks",((t,l)=>{(t=>{(0,t.options.register)("visualblocks_default_state",{processor:"boolean",default:!1})})(t);const a=(t=>{let s=!1;return{get:()=>s,set:t=>{s=t}}})();((t,o,e)=>{t.addCommand("mceVisualBlocks",(()=>{s(t,0,e)}))})(t,0,a),((t,s)=>{const o=()=>t.execCommand("mceVisualBlocks");t.ui.registry.addToggleButton("visualblocks",{icon:"visualblocks",tooltip:"Show blocks",onAction:o,onSetup:e(t,s)}),t.ui.registry.addToggleMenuItem("visualblocks",{text:"Show blocks",icon:"visualblocks",onAction:o,onSetup:e(t,s)})})(t,a),((t,e,l)=>{t.on("PreviewFormats AfterPreviewFormats",(s=>{l.get()&&t.dom.toggleClass(t.getBody(),"mce-visualblocks","afterpreviewformats"===s.type)})),t.on("init",(()=>{o(t)&&s(t,0,l)}))})(t,0,a)}))}();
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/content/dark/content.min.css:
--------------------------------------------------------------------------------
1 | body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/content/default/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/default/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}")
2 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/content/default/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/content/document/content.min.css:
--------------------------------------------------------------------------------
1 | @media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/content/pocketbase/content.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
3 | line-height: 1.4;
4 | margin: 1rem;
5 | }
6 | table {
7 | border-collapse: collapse;
8 | }
9 | table th,
10 | table td {
11 | border: 1px solid #ccc;
12 | padding: 0.4rem;
13 | }
14 | figure {
15 | display: table;
16 | margin: 1rem auto;
17 | }
18 | figure figcaption {
19 | color: #999;
20 | display: block;
21 | margin-top: 0.25rem;
22 | text-align: center;
23 | }
24 | hr {
25 | border-color: #ccc;
26 | border-style: solid;
27 | border-width: 1px 0 0 0;
28 | }
29 | code {
30 | background-color: #e8e8e8;
31 | border-radius: 3px;
32 | padding: 0.1rem 0.2rem;
33 | }
34 | .mce-content-body:not([dir=rtl]) blockquote {
35 | border-left: 2px solid #ccc;
36 | margin-left: 1.5rem;
37 | padding-left: 1rem;
38 | }
39 | .mce-content-body[dir=rtl] blockquote {
40 | border-right: 2px solid #ccc;
41 | margin-right: 1.5rem;
42 | padding-right: 1rem;
43 | }
44 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/content/pocketbase/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/content/writer/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/ui/oxide/skin.shadowdom.min.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}
2 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/ui/pocketbase/content.mobile.css:
--------------------------------------------------------------------------------
1 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection {
2 | /* Note: this file is used inside the content, so isn't part of theming */
3 | background-color: green;
4 | display: inline-block;
5 | opacity: 0.5;
6 | position: absolute;
7 | }
8 | body {
9 | -webkit-text-size-adjust: none;
10 | }
11 | body img {
12 | /* this is related to the content margin */
13 | max-width: 96vw;
14 | }
15 | body table img {
16 | max-width: 95%;
17 | }
18 | body {
19 | font-family: sans-serif;
20 | }
21 | table {
22 | border-collapse: collapse;
23 | }
24 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/ui/pocketbase/content.mobile.min.css:
--------------------------------------------------------------------------------
1 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
2 |
--------------------------------------------------------------------------------
/ui/dist/libs/tinymce/skins/ui/pocketbase/fonts/tinymce-mobile.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/dist/libs/tinymce/skins/ui/pocketbase/fonts/tinymce-mobile.woff
--------------------------------------------------------------------------------
/ui/embed.go:
--------------------------------------------------------------------------------
1 | // Package ui handles the PocketBase Admin frontend embedding.
2 | package ui
3 |
4 | import (
5 | "embed"
6 |
7 | "github.com/labstack/echo/v5"
8 | )
9 |
10 | //go:embed all:dist
11 | var distDir embed.FS
12 |
13 | // DistDirFS contains the embedded dist directory files (without the "dist" prefix)
14 | var DistDirFS = echo.MustSubFS(distDir, "dist")
15 |
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pocketbase-admin",
3 | "private": true,
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "type": "module",
10 | "prettier": {
11 | "tabWidth": 4,
12 | "printWidth": 110,
13 | "svelteBracketNewLine": true
14 | },
15 | "devDependencies": {
16 | "@codemirror/autocomplete": "^6.0.0",
17 | "@codemirror/commands": "^6.0.0",
18 | "@codemirror/lang-html": "^6.1.0",
19 | "@codemirror/lang-javascript": "^6.0.2",
20 | "@codemirror/lang-sql": "^6.4.0",
21 | "@codemirror/lang-json": "^6.0.0",
22 | "@codemirror/language": "^6.0.0",
23 | "@codemirror/legacy-modes": "^6.0.0",
24 | "@codemirror/search": "^6.0.0",
25 | "@codemirror/state": "^6.0.0",
26 | "@codemirror/view": "^6.0.0",
27 | "@sveltejs/vite-plugin-svelte": "^3.0.1",
28 | "chart.js": "^4.0.0",
29 | "chartjs-adapter-luxon": "^1.2.0",
30 | "luxon": "^3.4.4",
31 | "pocketbase": "^0.20.0",
32 | "sass": "^1.45.0",
33 | "svelte": "^4.0.0",
34 | "svelte-flatpickr": "^3.3.3",
35 | "svelte-spa-router": "^4.0.0",
36 | "vite": "^5.0.11"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ui/public/fonts/remixicon/remixicon.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/remixicon/remixicon.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-700.woff2
--------------------------------------------------------------------------------
/ui/public/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/fonts/ubuntu-mono/ubuntu-mono-v17-cyrillic_latin-regular.woff2
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar0.svg:
--------------------------------------------------------------------------------
1 | Mary Roebling
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar1.svg:
--------------------------------------------------------------------------------
1 | Nellie Bly
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar2.svg:
--------------------------------------------------------------------------------
1 | Elizabeth Peratrovich
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar3.svg:
--------------------------------------------------------------------------------
1 | Amelia Boynton
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar4.svg:
--------------------------------------------------------------------------------
1 | Victoria Woodhull
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar5.svg:
--------------------------------------------------------------------------------
1 | Chien-Shiung
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar6.svg:
--------------------------------------------------------------------------------
1 | Hetty Green
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar7.svg:
--------------------------------------------------------------------------------
1 | Elizabeth Peratrovich
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar8.svg:
--------------------------------------------------------------------------------
1 | Jane Johnston
2 |
--------------------------------------------------------------------------------
/ui/public/images/avatars/avatar9.svg:
--------------------------------------------------------------------------------
1 | Virginia Apgar
2 |
--------------------------------------------------------------------------------
/ui/public/images/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ui/public/images/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/favicon.ico
--------------------------------------------------------------------------------
/ui/public/images/favicon/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/mstile-144x144.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/mstile-310x150.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/mstile-310x310.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/images/favicon/mstile-70x70.png
--------------------------------------------------------------------------------
/ui/public/images/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/_/images/favicon/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/_/images/favicon/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/apple.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/bitbucket.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Facebook
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/gitee.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/google.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/livechat.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/microsoft.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Microsoft
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/oidc.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/patreon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/strava.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/twitch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/vk.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/public/images/oauth2/yandex.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/plugins/code/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 6.8.4 (2024-06-19)
3 | */
4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}();
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/plugins/visualblocks/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 6.8.4 (2024-06-19)
3 | */
4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const s=(t,s,o)=>{t.dom.toggleClass(t.getBody(),"mce-visualblocks"),o.set(!o.get()),((t,s)=>{t.dispatch("VisualBlocks",{state:s})})(t,o.get())},o=("visualblocks_default_state",t=>t.options.get("visualblocks_default_state"));const e=(t,s)=>o=>{o.setActive(s.get());const e=t=>o.setActive(t.state);return t.on("VisualBlocks",e),()=>t.off("VisualBlocks",e)};t.add("visualblocks",((t,l)=>{(t=>{(0,t.options.register)("visualblocks_default_state",{processor:"boolean",default:!1})})(t);const a=(t=>{let s=!1;return{get:()=>s,set:t=>{s=t}}})();((t,o,e)=>{t.addCommand("mceVisualBlocks",(()=>{s(t,0,e)}))})(t,0,a),((t,s)=>{const o=()=>t.execCommand("mceVisualBlocks");t.ui.registry.addToggleButton("visualblocks",{icon:"visualblocks",tooltip:"Show blocks",onAction:o,onSetup:e(t,s)}),t.ui.registry.addToggleMenuItem("visualblocks",{text:"Show blocks",icon:"visualblocks",onAction:o,onSetup:e(t,s)})})(t,a),((t,e,l)=>{t.on("PreviewFormats AfterPreviewFormats",(s=>{l.get()&&t.dom.toggleClass(t.getBody(),"mce-visualblocks","afterpreviewformats"===s.type)})),t.on("init",(()=>{o(t)&&s(t,0,l)}))})(t,0,a)}))}();
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/content/dark/content.min.css:
--------------------------------------------------------------------------------
1 | body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/content/default/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/default/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}")
2 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/content/default/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/content/document/content.min.css:
--------------------------------------------------------------------------------
1 | @media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/content/pocketbase/content.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
3 | line-height: 1.4;
4 | margin: 1rem;
5 | }
6 | table {
7 | border-collapse: collapse;
8 | }
9 | table th,
10 | table td {
11 | border: 1px solid #ccc;
12 | padding: 0.4rem;
13 | }
14 | figure {
15 | display: table;
16 | margin: 1rem auto;
17 | }
18 | figure figcaption {
19 | color: #999;
20 | display: block;
21 | margin-top: 0.25rem;
22 | text-align: center;
23 | }
24 | hr {
25 | border-color: #ccc;
26 | border-style: solid;
27 | border-width: 1px 0 0 0;
28 | }
29 | code {
30 | background-color: #e8e8e8;
31 | border-radius: 3px;
32 | padding: 0.1rem 0.2rem;
33 | }
34 | .mce-content-body:not([dir=rtl]) blockquote {
35 | border-left: 2px solid #ccc;
36 | margin-left: 1.5rem;
37 | padding-left: 1rem;
38 | }
39 | .mce-content-body[dir=rtl] blockquote {
40 | border-right: 2px solid #ccc;
41 | margin-right: 1.5rem;
42 | padding-right: 1rem;
43 | }
44 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/content/pocketbase/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/content/writer/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/ui/oxide/skin.shadowdom.min.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}
2 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/ui/pocketbase/content.mobile.css:
--------------------------------------------------------------------------------
1 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection {
2 | /* Note: this file is used inside the content, so isn't part of theming */
3 | background-color: green;
4 | display: inline-block;
5 | opacity: 0.5;
6 | position: absolute;
7 | }
8 | body {
9 | -webkit-text-size-adjust: none;
10 | }
11 | body img {
12 | /* this is related to the content margin */
13 | max-width: 96vw;
14 | }
15 | body table img {
16 | max-width: 95%;
17 | }
18 | body {
19 | font-family: sans-serif;
20 | }
21 | table {
22 | border-collapse: collapse;
23 | }
24 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/ui/pocketbase/content.mobile.min.css:
--------------------------------------------------------------------------------
1 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
2 |
--------------------------------------------------------------------------------
/ui/public/libs/tinymce/skins/ui/pocketbase/fonts/tinymce-mobile.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hylarucoder/rocketbase/8c950ad76724163db48da44f24c5fde8d0b734ff/ui/public/libs/tinymce/skins/ui/pocketbase/fonts/tinymce-mobile.woff
--------------------------------------------------------------------------------
/ui/src/autocomplete.worker.js:
--------------------------------------------------------------------------------
1 | import CommonHelper from "@/utils/CommonHelper";
2 |
3 | const maxKeys = 11000;
4 |
5 | onmessage = (e) => {
6 | if (!e.data.collections) {
7 | return;
8 | }
9 |
10 | const result = {};
11 |
12 | result.baseKeys = CommonHelper.getCollectionAutocompleteKeys(e.data.collections, e.data.baseCollection?.name);
13 | result.baseKeys = limitArray(result.baseKeys.sort(keysSort), maxKeys);
14 |
15 | if (!e.data.disableRequestKeys) {
16 | result.requestKeys = CommonHelper.getRequestAutocompleteKeys(e.data.collections, e.data.baseCollection?.name);
17 | result.requestKeys = limitArray(result.requestKeys.sort(keysSort), maxKeys);
18 | }
19 |
20 | if (!e.data.disableCollectionJoinKeys) {
21 | result.collectionJoinKeys = CommonHelper.getCollectionJoinAutocompleteKeys(e.data.collections);
22 | result.collectionJoinKeys = limitArray(result.collectionJoinKeys.sort(keysSort), maxKeys);
23 | }
24 |
25 | postMessage(result);
26 | };
27 |
28 | // sort shorter keys first
29 | function keysSort(a, b) {
30 | return a.length - b.length;
31 | }
32 |
33 | function limitArray(arr, max) {
34 | if (arr.length > max) {
35 | return arr.slice(0, max);
36 | }
37 |
38 | return arr;
39 | }
40 |
--------------------------------------------------------------------------------
/ui/src/components/PageIndex.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 | {#if showInstaller}
31 |
32 | {
34 | showInstaller = false;
35 |
36 | await tick();
37 |
38 | // clear the installer param
39 | window.location.search = "";
40 | }}
41 | />
42 |
43 | {/if}
44 |
--------------------------------------------------------------------------------
/ui/src/components/base/BaseSelectOption.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | {#if item.icon}
7 |
8 | {/if}
9 |
10 | {item.label || item.name || item.title || item.id || item.value}
11 |
--------------------------------------------------------------------------------
/ui/src/components/base/CopyIcon.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
46 |
--------------------------------------------------------------------------------
/ui/src/components/base/FormattedDate.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 | {#if date}
21 |
22 |
{dateOnly}
23 |
{timeOnly} UTC
24 |
25 | {:else}
26 | N/A
27 | {/if}
28 |
29 |
41 |
--------------------------------------------------------------------------------
/ui/src/components/base/FullPage.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | {#if !nobranding}
10 |
11 |
12 |
18 | PocketBase
19 |
20 |
21 |
22 | {/if}
23 |
24 |
25 |
26 |
27 |
28 |
33 |
--------------------------------------------------------------------------------
/ui/src/components/base/InitialsAvatar.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | {CommonHelper.getInitials(value)}
12 |
13 |
14 |
15 |
30 |
--------------------------------------------------------------------------------
/ui/src/components/base/MimeTypeSelectOption.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | {item.ext || "N/A"}
6 | {item.mimeType}
7 |
--------------------------------------------------------------------------------
/ui/src/components/base/ModelDateIcon.svelte:
--------------------------------------------------------------------------------
1 |
31 |
32 |
39 |
--------------------------------------------------------------------------------
/ui/src/components/base/MultipleValueInput.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 | {
18 | value = CommonHelper.splitNonEmpty(e.target.value, separator);
19 | }}
20 | {...$$restProps}
21 | />
22 |
--------------------------------------------------------------------------------
/ui/src/components/base/PageSidebar.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
23 |
24 | {
26 | initialSidebarWidth = sidebarElem.offsetWidth;
27 | }}
28 | on:dragging={(e) => {
29 | sidebarWidth = initialSidebarWidth + e.detail.diffX + "px";
30 | }}
31 | on:dragstop={() => {
32 | CommonHelper.triggerResize();
33 | }}
34 | />
35 |
--------------------------------------------------------------------------------
/ui/src/components/base/PageWrapper.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
25 |
26 |
--------------------------------------------------------------------------------
/ui/src/components/base/RedactedPasswordInput.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 | {#if locked}
22 |
23 | unlock()}
28 | >
29 |
30 |
31 |
32 |
33 |
34 | {:else}
35 |
36 | {/if}
37 |
--------------------------------------------------------------------------------
/ui/src/components/base/RefreshButton.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 |
42 |
43 |
44 |
45 |
55 |
--------------------------------------------------------------------------------
/ui/src/components/base/SortHeader.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 | toggleSort()}
31 | on:keydown={(e) => {
32 | if (e.code === "Enter" || e.code === "Space") {
33 | e.preventDefault();
34 | toggleSort();
35 | }
36 | }}
37 | >
38 |
39 |
40 |
--------------------------------------------------------------------------------
/ui/src/components/base/UploadedFilePreview.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 | {#if previewUrl}
30 |
31 | {:else}
32 |
33 | {/if}
34 |
--------------------------------------------------------------------------------
/ui/src/components/collections/docs/FieldsQueryParam.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | fields
9 |
10 | String
11 |
12 |
13 |
14 | Comma separated string of the fields to return in the JSON response
15 | (by default returns all fields) . Ex.:
16 |
17 |
18 |
19 | *
targets all keys from the specific depth level.
20 |
21 | In addition, the following field modifiers are also supported:
22 |
23 |
24 | :excerpt(maxLength, withEllipsis?)
25 |
26 | Returns a short plain text version of the field string value.
27 |
28 | Ex.:
29 | ?fields=*,{prefix}description:excerpt(200,true)
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/ui/src/components/collections/schema/SchemaFieldBool.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ui/src/components/collections/schema/SchemaFieldEditor.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Strip urls domain
27 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ui/src/components/collections/schema/SchemaFieldUrl.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ui/src/components/logs/LogDate.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 | {date.replace("Z", " UTC")}
18 |
19 |
--------------------------------------------------------------------------------
/ui/src/components/logs/LogLevel.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 | {label || "UNKN"} ({level})
12 |
13 |
14 |
15 |
41 |
--------------------------------------------------------------------------------
/ui/src/components/logs/LogsLevelsInfo.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | Default log levels:
10 |
11 | {#each logLevels as options}
12 | {options.level}:{options.label}
13 | {/each}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ui/src/components/records/PageOAuth2Redirect.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
Auth completed.
11 | You can close this window and go back to the app.
12 |
13 |
--------------------------------------------------------------------------------
/ui/src/components/records/PageOAuth2RedirectFailure.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
Auth failed.
11 | You can close this window and go back to the app to try again.
12 |
13 |
--------------------------------------------------------------------------------
/ui/src/components/records/PageOAuth2RedirectSuccess.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
Auth completed.
11 | You can close this window and go back to the app.
12 |
13 |
--------------------------------------------------------------------------------
/ui/src/components/records/fields/BoolField.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 | {field.name}
11 |
12 |
--------------------------------------------------------------------------------
/ui/src/components/records/fields/EmailField.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | {field.name}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ui/src/components/records/fields/NumberField.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | {field.name}
13 |
14 |
23 |
24 |
--------------------------------------------------------------------------------
/ui/src/components/records/fields/SelectField.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 | {field.name}
24 |
25 | = field.options?.maxSelect}
30 | items={field.options?.values}
31 | searchable={field.options?.values?.length > 5}
32 | bind:selected={value}
33 | />
34 | {#if field.options?.maxSelect > 1}
35 | Select up to {field.options.maxSelect} items.
36 | {/if}
37 |
38 |
--------------------------------------------------------------------------------
/ui/src/components/records/fields/TextField.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | {field.name}
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ui/src/components/records/fields/UrlField.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | {field.name}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ui/src/components/settings/TokenField.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | {label} duration (in seconds)
13 |
14 |
15 |
16 | {
20 | // toggle
21 | if (secret) {
22 | secret = undefined;
23 | } else {
24 | secret = CommonHelper.randomSecret(50);
25 | }
26 | }}
27 | >
28 | Invalidate all previously issued tokens
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/ui/src/components/settings/providers/AppleOptions.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | generatorPopup?.show({ clientId: config.clientId })}
14 | >
15 |
16 | Generate secret
17 |
18 |
19 | {
22 | config.clientSecret = e.detail?.secret || "";
23 | }}
24 | />
25 |
--------------------------------------------------------------------------------
/ui/src/components/settings/providers/MicrosoftOptions.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | Azure AD endpoints
9 |
10 | Auth URL
11 |
12 |
13 | Eg. {`https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/authorize`}
14 |
15 |
16 |
17 | Token URL
18 |
19 |
20 | Eg. {`https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/token`}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ui/src/components/settings/providers/SelfHostedOptions.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 | {title}
13 |
14 | Auth URL
15 |
16 |
17 |
18 | Token URL
19 |
20 |
21 |
22 | User API URL
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ui/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte'
2 |
3 | const app = new App({
4 | target: document.getElementById('app')
5 | });
6 |
7 | export default app
8 |
--------------------------------------------------------------------------------
/ui/src/scss/_bulkbar.scss:
--------------------------------------------------------------------------------
1 | .bulkbar {
2 | position: absolute;
3 | bottom: var(--baseSpacing);
4 | left: 50%;
5 | z-index: 101;
6 | gap: 10px;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | width: var(--smWrapperWidth);
11 | max-width: 100%;
12 | margin-bottom: 10px;
13 | padding: 10px var(--smSpacing);
14 | border-radius: var(--btnHeight);
15 | background: var(--baseColor);
16 | border: 1px solid var(--baseAlt2Color);
17 | @include shadowize();
18 | transform: translateX(-50%);
19 | }
20 |
--------------------------------------------------------------------------------
/ui/src/scss/_reset.scss:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font: inherit;
19 | vertical-align: baseline;
20 | }
21 | /* HTML5 display-role reset for older browsers */
22 | article, aside, details, figcaption, figure,
23 | footer, header, hgroup, menu, nav, section {
24 | display: block;
25 | }
26 | body {
27 | line-height: 1;
28 | }
29 | ol, ul {
30 | list-style: none;
31 | }
32 | blockquote, q {
33 | quotes: none;
34 | }
35 | blockquote:before, blockquote:after,
36 | q:before, q:after {
37 | content: '';
38 | content: none;
39 | }
40 | table {
41 | border-collapse: collapse;
42 | border-spacing: 0;
43 | }
44 |
45 | html {
46 | box-sizing: border-box;
47 | }
48 | *, *:before, *:after {
49 | box-sizing: inherit;
50 | }
51 |
--------------------------------------------------------------------------------
/ui/src/scss/_tooltip.scss:
--------------------------------------------------------------------------------
1 | .app-tooltip {
2 | position: fixed;
3 | z-index: 999999;
4 | top: 0;
5 | left: 0;
6 | display: inline-block;
7 | vertical-align: top;
8 | max-width: 275px;
9 | padding: 3px 5px;
10 | color: #fff;
11 | text-align: center;
12 | font-family: var(--baseFontFamily);
13 | font-size: var(--smFontSize);
14 | line-height: var(--smLineHeight);
15 | border-radius: var(--baseRadius);
16 | background: var(--tooltipColor);
17 | pointer-events: none;
18 | user-select: none;
19 | transition: opacity var(--baseAnimationSpeed),
20 | visibility var(--baseAnimationSpeed),
21 | transform var(--baseAnimationSpeed);
22 | transform: translateY(1px);
23 | backface-visibility: hidden;
24 | white-space: pre-line;
25 | word-break: break-word;
26 | @include hide();
27 |
28 | // styles
29 | &.code {
30 | font-family: monospace;
31 | white-space: pre-wrap;
32 | text-align: left;
33 | min-width: 150px;
34 | max-width: 340px;
35 | }
36 |
37 | // states
38 | &.active {
39 | transform: scale(1);
40 | @include show();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ui/src/scss/main.scss:
--------------------------------------------------------------------------------
1 | @import 'fonts';
2 |
3 | @import 'vars';
4 |
5 | @import 'mixins';
6 |
7 | @import 'reset';
8 |
9 | @import 'icons';
10 |
11 | @import 'animations';
12 |
13 | @import 'base';
14 |
15 | @import 'grid';
16 |
17 | @import 'tooltip';
18 |
19 | @import 'dropdown';
20 |
21 | @import 'overlay_panel';
22 |
23 | @import 'alert';
24 |
25 | @import 'form';
26 |
27 | @import 'layout';
28 |
29 | @import 'tabs';
30 |
31 | @import 'accordion';
32 |
33 | @import 'table';
34 |
35 | @import 'searchbar';
36 |
37 | @import 'bulkbar';
38 |
39 | @import 'flatpickr';
40 |
41 | @import 'docs_panel';
42 |
43 | @import 'schema_field';
44 |
45 | @import 'file_picker';
46 |
47 | @import 'collections_export';
48 |
--------------------------------------------------------------------------------
/ui/src/stores/admin.js:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 |
3 | // logged app admin
4 | export const admin = writable({});
5 |
6 | export function setAdmin(model) {
7 | admin.set(model || {});
8 | }
9 |
--------------------------------------------------------------------------------
/ui/src/stores/app.js:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 |
3 | export const pageTitle = writable('');
4 |
5 | export const appName = writable('');
6 |
7 | export const hideControls = writable(false);
8 |
--------------------------------------------------------------------------------
/ui/src/stores/confirmation.js:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 |
3 | // eg.
4 | // {
5 | // "text": "Do you really want to delete the selectedItem",
6 | // "yesCallback": function() {...},
7 | // "noCallback": function() {...},
8 | // }
9 | export const confirmation = writable({});
10 |
11 | /**
12 | * @param {String} text
13 | * @param {Function} [yesCallback]
14 | * @param {Function} [noCallback]
15 | */
16 | export function confirm(text, yesCallback, noCallback) {
17 | confirmation.set({
18 | text: text,
19 | yesCallback: yesCallback,
20 | noCallback: noCallback,
21 | });
22 | }
23 |
24 | export function resetConfirmation() {
25 | confirmation.set({});
26 | }
27 |
--------------------------------------------------------------------------------
/ui/src/stores/errors.js:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 | import CommonHelper from "@/utils/CommonHelper";
3 |
4 | export const errors = writable({});
5 |
6 | /**
7 | * @param {Object} newErrors
8 | */
9 | export function setErrors(newErrors) {
10 | errors.set(newErrors || {});
11 | }
12 |
13 | /**
14 | * @param {String} name
15 | * @param {String|Array} message
16 | */
17 | export function addError(name, message) {
18 | errors.update((e) => {
19 | CommonHelper.setByPath(e, name, CommonHelper.sentenize(message))
20 | return e;
21 | });
22 | }
23 |
24 | /**
25 | * @param {String} name
26 | */
27 | export function removeError(name) {
28 | errors.update((e) => {
29 | CommonHelper.deleteByPath(e, name);
30 | return e;
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/ui/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | // see https://vitejs.dev/config
5 | export default defineConfig({
6 | server: {
7 | port: 3000,
8 | },
9 | envPrefix: 'PB',
10 | base: './',
11 | build: {
12 | chunkSizeWarningLimit: 1000,
13 | reportCompressedSize: false,
14 | },
15 | plugins: [
16 | svelte({
17 | preprocess: [vitePreprocess()],
18 | onwarn: (warning, handler) => {
19 | if (warning.code.startsWith('a11y-')) {
20 | return; // silence a11y warnings
21 | }
22 | handler(warning);
23 | },
24 | }),
25 | ],
26 | resolve: {
27 | alias: {
28 | '@': __dirname + '/src',
29 | }
30 | },
31 | })
32 |
--------------------------------------------------------------------------------
/volumes/.gitignore:
--------------------------------------------------------------------------------
1 | ./*
2 | !.gitignore
3 |
--------------------------------------------------------------------------------