celerp 1.2.4__tar.gz → 1.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {celerp-1.2.4 → celerp-1.2.5}/PKG-INFO +1 -1
- {celerp-1.2.4 → celerp-1.2.5}/celerp/cli.py +26 -3
- {celerp-1.2.4 → celerp-1.2.5}/celerp/events/engine.py +6 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/middleware.py +7 -4
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/health.py +2 -5
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/stars.py +7 -3
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/backup.py +3 -94
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/backup_export.py +3 -26
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/backup_import.py +3 -3
- celerp-1.2.5/celerp/services/backup_repo.py +241 -0
- celerp-1.2.5/celerp/services/backup_scheduler.py +107 -0
- celerp-1.2.5/celerp/services/backup_state.py +31 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/supporter_badge.py +5 -3
- {celerp-1.2.4 → celerp-1.2.5}/celerp.egg-info/PKG-INFO +1 -1
- {celerp-1.2.4 → celerp-1.2.5}/celerp.egg-info/SOURCES.txt +3 -1
- {celerp-1.2.4 → celerp-1.2.5}/celerp.egg-info/scm_file_list.json +5 -2
- celerp-1.2.5/celerp.egg-info/scm_version.json +8 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-backup/celerp_backup/routes.py +26 -37
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/routes.py +16 -5
- celerp-1.2.5/default_modules/celerp-docs/tests/test_line_delete_persist.py +85 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/api_client.py +49 -29
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/activity.py +60 -2
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/backup.py +5 -15
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/shell.py +42 -4
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/ar.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/de.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/en.json +2 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/es.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/fr.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/id.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/it.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/ja.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/pt.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/th.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/locales/vi.json +0 -7
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/dashboard.py +1 -1
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/documents.py +39 -19
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings.py +74 -71
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_cloud.py +0 -2
- celerp-1.2.4/celerp/services/backup_files.py +0 -181
- celerp-1.2.4/celerp/services/backup_scheduler.py +0 -171
- celerp-1.2.4/celerp.egg-info/scm_version.json +0 -8
- {celerp-1.2.4 → celerp-1.2.5}/CLA.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/CONTRIBUTING.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/LICENSING.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/MANIFEST.in +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/NOTICE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/README.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/alembic.ini +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/batch.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/cleanup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/commands.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/conversations.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/files.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/intent.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/llm.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/memory.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/models.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/page_count.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/quota.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/service.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/ai/tools.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/alembic.ini +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/alembic_config.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/compute/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/compute/aggregations.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/compute/valuation.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/config.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/base.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/daily_scheduler.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/http.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/images.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/outbound_queue.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/quickbooks.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/registry.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/shopify.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/sync_runner.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/upsert.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/webhooks.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/woocommerce.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/connectors/xero.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/constants.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/db.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/events/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/events/schemas.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/events/types.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/gateway/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/gateway/client.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/gateway/state.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/importers/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/importers/importer.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/importers/schema.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/main.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/README.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/_auto_stamp.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/env.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/script.py.mako +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/a0b1c2d3e4f5_seed_landed_cost_clearing_accounts.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/a1b2c3d4e5f6_add_doc_share_tokens.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/b1c2d3e4f5a6_add_supporter_badges.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/b2c3d4e5f6a7_add_import_batches.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/c1d2e3f4a5b6_add_marketplace.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/d2e3f4a5b6c7_add_password_reset_token.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/e3f4a5b6c7d8_add_company_is_active.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/f1a2b3c4d5e6_move_accounts_marketplace_to_modules.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/fd5de461e14e_initial_schema.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/g1h2i3j4k5l6_add_consignment_flag.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/h2i3j4k5l6m7_add_bank_accounts_and_reconciliation.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/j4k5l6m7n8o9_labels_db_persistence.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/k5l6m7n8o9p0_add_reconciliation_v2.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/l6m7n8o9p0q1_add_sync_runs_table.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/m7n8o9p0q1r2_add_connector_configs_and_outbound_queue.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/n8o9p0q1r2s3_backfill_payment_date.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/o9p0q1r2s3t4_backfill_item_price_fields.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/p1q2r3s4t5u6_add_session_registry_and_user_auth_state.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/q2r3s4t5u6v7_add_system_runtime_state.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/r3s4t5u6v7w8_add_projections_company_entity_type_index.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/s4t5u6v7w8x9_drop_user_company_id_role.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/t5u6v7w8x9y0_seed_inventory_sub_accounts.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/u6v7w8x9y0z1_backfill_item_prices_from_attributes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/v7w8x9y0z1a2_backfill_purchase_unit_defaults.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/w8x9y0z1a2b3_backfill_category_unit_defaults.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/x7y8z9a0b1c2_add_projection_created_at.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/y8z9a0b1c2d3_per_company_ledger_idempotency.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/migrations/versions/z9a0b1c2d3e4_work_centers.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/accounting.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/ai.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/auth.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/base.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/company.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/connector_config.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/import_batch.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/ledger.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/marketplace.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/notification.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/projections.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/share.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/supporter.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/models/sync_run.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/api.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/audit.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/license.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/loader.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/references.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/registry.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/modules/slots.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/notifications/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/notifications/service.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/notifications/sse.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/output/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/output/pdf.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/output/share_render.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/projections/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/projections/engine.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/projections/handlers/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/projections/handlers/marketplace.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/projections/handlers/scanning.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/projections/handlers/system.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/auth.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/companies.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/debug.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/doctor.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/events.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/ledger.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/notifications.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/routers/system.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/schemas/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/activity_redaction.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/attachments.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/auth.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/auto_je.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/demo.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/email.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/field_schema.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/fulfill.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/je_keys.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/landed_cost.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/line_measures.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/list_behavior.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/money.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/pick.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/runtime_state.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/session_tracker.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/star_cta.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/system_health.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/services/units.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/session_gate.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp/tax_regimes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp.egg-info/dependency_links.txt +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp.egg-info/entry_points.txt +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp.egg-info/requires.txt +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/celerp.egg-info/top_level.txt +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/conftest.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/api_setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/csv_parser.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/matcher.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/models.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/projections.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/celerp_accounting/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/tests/test_accounting_banking.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/tests/test_accounting_banking_routers.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/tests/test_accounting_gaps.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/tests/test_accounting_import_idempotency.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-accounting/tests/test_reconciliation_v2.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-admin/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-admin/celerp_admin/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-admin/celerp_admin/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-admin/celerp_admin/setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/celerp_ai/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/celerp_ai/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/celerp_ai/setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/celerp_ai/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/conftest.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_batch.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_cleanup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_conversations.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_coverage_gaps.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_intent.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_llm.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_memory.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_models.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_page_count.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_quota.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_router.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_routes_coverage.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_service.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-ai/tests/test_tools.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-backup/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-backup/celerp_backup/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-backup/celerp_backup/setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/celerp_connectors/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/celerp_connectors/models.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/celerp_connectors/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/conftest.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_compliance.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_connectors.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_outbound.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_quickbooks.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_rate_limiting.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_shopify.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_woocommerce.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-connectors/tests/test_xero.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/celerp_contacts/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/celerp_contacts/migrations.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/celerp_contacts/projections.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/celerp_contacts/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/celerp_contacts/services.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-contacts/celerp_contacts/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-dashboard/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-dashboard/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-dashboard/celerp_dashboard/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-dashboard/celerp_dashboard/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-dashboard/celerp_dashboard/setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/api_setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/doc_constants.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/doc_projections.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/doc_service.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/models_share.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/routes_share.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/sequences.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/taxes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/celerp_docs/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/conftest.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_audits.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_freight_je.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_freight_lines.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_fulfill_no_split.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_fulfillment_activity.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_inbound_je_lifecycle.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_inventory_account_reconciliation.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_landed_receive.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_payment_void_activity.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_send_visibility.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-docs/tests/test_service_lines.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/models_import_batch.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/projections.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/routes_attachments.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/routes_scanning.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/services.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/celerp_inventory/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/tests/test_item_recipe_projection.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/tests/test_item_workflow_projection.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-inventory/tests/test_landed_cost_projection.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/migrations/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/migrations/labels_001_create_label_templates.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/models.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/presets.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/service.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/celerp_labels/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/requirements.txt +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/tests/test_labels.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-labels/tests/test_labels_module.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/costing.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/expansion.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/labor.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/migrations.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/projection_handler.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/celerp_manufacturing/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/requirements.txt +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_bom_removed.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_build_api.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_item_recipe_api.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_item_workflow_api.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_labor_providers.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_manufacture_from_doc.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_manufacturing.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_manufacturing_import_idempotency.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_manufacturing_module.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_mfg_events.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_phase2_manufacturing_validations.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_production_order.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_recipe_costing.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_recipe_expansion.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_recost_api.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_to_make_allocation.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_where_used.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_work_centers_and_settings.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-manufacturing/tests/test_workflow_time.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/celerp_reports/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/celerp_reports/api_setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/celerp_reports/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/celerp_reports/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/tests/test_reports_full.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-reports/tests/test_reports_unit.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-subscriptions/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-subscriptions/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-subscriptions/celerp_subscriptions/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-subscriptions/celerp_subscriptions/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-subscriptions/celerp_subscriptions/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-subscriptions/tests/test_subscriptions.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-subscriptions/tests/test_subscriptions_ui.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/LICENSE +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/accessory_fashion.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/activewear.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/art_photography.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/audio_equipment.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/bag_handbag.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/banknote.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/beauty_tool.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/bed_bedroom.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/beer.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/beverage_nonalc.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/body_part_auto.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/book.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/bottoms.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/brake_suspension.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/bullion_coin.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/camera.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/colored_stone.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/comic.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/commercial_space.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/component_part.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/confectionery.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/consulting_service.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/decorative_art.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/diamond.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/drawing.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/dress_jumpsuit.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_auto.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_component.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/emerald.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/engine_part.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/fastener.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/fertilizer_chemical.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/film_video.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/fine_writing_instrument.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/fluid_lubricant.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/footwear.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/fragrance.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_food.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_produce.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/frozen_food.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/gaming_console.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/gold_bullion.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/grain_cereal.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/haircare.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/hand_tool.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/ingredient_bulk.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/interior_part.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/jewelry.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/kitchen_dining.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/laptop.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/lighting.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/livestock_feed.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/makeup.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/measuring_instrument.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/medal_token.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/mineral_specimen.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/mobile_phone.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/music_cd.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/music_vinyl.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/nail.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/numismatic_coin.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/outdoor_furniture.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/outerwear.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/packaged_food.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/painting.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/parking_bay.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/pearl.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/personal_care.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/pipe_plumbing.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/platinum_bullion.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/power_tool.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/print_edition.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/residential_unit.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/rough_gemstone.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/ruby.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/saas_plan.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/safety_ppe.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/sapphire.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/sculpture.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/seating.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/seeds.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/silver_bullion.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/skincare.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/soft_furnishing.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/software_addon.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/software_license.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/spirit.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/storage_furniture.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/swimwear.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/table_desk.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/tablet.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/tire_wheel.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/tops.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/trading_card.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/tv_display.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/video_game.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/watch.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/watch_strap.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/categories/wine.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/agricultural.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/artwork.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/automotive.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/blank.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/books_media.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/coins_precious_metals.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/consulting.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/cosmetics.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/electronics.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/fashion.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/food_beverage.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/furniture.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/gemstones.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/hardware.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/property_rental.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/saas.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/watches.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/presets/wine_spirits.json +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/celerp_verticals/ui_routes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/tests/test_verticals.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/default_modules/celerp-verticals/tests/test_verticals_routers.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/legal/OFFICIAL-COMPONENTS.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/legal/TRADEMARK.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/memory/fix-receive-goods-bugs.md +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/pyproject.toml +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/setup.cfg +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/test_helpers.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/app.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/cloud_gate.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/currency.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/files.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/notes.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/phone.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/components/table.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/config.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/i18n.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/__init__.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/accounting.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/accounting_import.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/auth.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/contacts.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/csv_import.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/docs_import.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/events.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/inventory.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/lists_import.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/manufacturing.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/notifications.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/reconciliation.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/reports.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/search.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_accounting.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_connectors.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_contacts.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_general.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_import.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_inventory.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_manufacturing.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_purchasing.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/settings_sales.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/setup.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/stars.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/subscriptions.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/routes/subscriptions_import.py +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/app.css +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/htmx.min.js +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/icon.png +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/logo.png +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/vendor/intl-tel-input/img/flags.webp +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/vendor/intl-tel-input/img/flags@2x.webp +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/vendor/intl-tel-input/intlTelInput.min.css +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/vendor/intl-tel-input/intlTelInput.min.js +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/vendor/intl-tel-input/intlTelInputWithUtils.min.js +0 -0
- {celerp-1.2.4 → celerp-1.2.5}/ui/static/vendor/intl-tel-input/utils.js +0 -0
|
@@ -430,8 +430,9 @@ def main() -> None:
|
|
|
430
430
|
@click.option("--api-port", default=None, type=int, help="API server port (default 8000).")
|
|
431
431
|
@click.option("--ui-port", default=None, type=int, help="UI server port (default 8080).")
|
|
432
432
|
@click.option("--cloud-token", default=None, help="Celerp Cloud token (optional).")
|
|
433
|
-
@click.option("--force", is_flag=True, help="
|
|
434
|
-
|
|
433
|
+
@click.option("--force", is_flag=True, help="Reconfigure: WIPES the database and all attached files.")
|
|
434
|
+
@click.option("--yes", "-y", "assume_yes", is_flag=True, help="Skip the --force wipe confirmation (non-interactive use).")
|
|
435
|
+
def init(db_url, api_port, ui_port, cloud_token, force, assume_yes):
|
|
435
436
|
"""Initialize Celerp: write config and run database migrations."""
|
|
436
437
|
config_path = _config_path()
|
|
437
438
|
|
|
@@ -441,6 +442,21 @@ def init(db_url, api_port, ui_port, cloud_token, force):
|
|
|
441
442
|
return
|
|
442
443
|
|
|
443
444
|
if force:
|
|
445
|
+
# --force is destructive: it drops the database AND removes attached files.
|
|
446
|
+
# init can run under sudo, so warn (with full paths) and confirm before wiping.
|
|
447
|
+
from celerp.config import settings
|
|
448
|
+
_purge_dirs = [
|
|
449
|
+
settings.data_dir / "static" / "attachments",
|
|
450
|
+
settings.data_dir / "ai_uploads",
|
|
451
|
+
]
|
|
452
|
+
click.echo("⚠ `init --force` permanently WIPES this instance:")
|
|
453
|
+
click.echo(f" - database: {db_url or _DEFAULT_DB_URL}")
|
|
454
|
+
for _d in _purge_dirs:
|
|
455
|
+
click.echo(f" - files: {_d.resolve()}")
|
|
456
|
+
click.echo(" Make sure you have a backup.")
|
|
457
|
+
if not assume_yes and not click.confirm("Continue?", default=False):
|
|
458
|
+
click.echo("Aborted.")
|
|
459
|
+
return
|
|
444
460
|
_stop_servers()
|
|
445
461
|
|
|
446
462
|
enabled_modules: list[str] = []
|
|
@@ -469,6 +485,13 @@ def init(db_url, api_port, ui_port, cloud_token, force):
|
|
|
469
485
|
)
|
|
470
486
|
sys.exit(1)
|
|
471
487
|
click.echo(" ✓ Database ready (fresh)")
|
|
488
|
+
# Wipe attached files too, so a forced re-init is a truly fresh instance and no
|
|
489
|
+
# orphaned files remain. Recreated automatically on next boot.
|
|
490
|
+
import shutil
|
|
491
|
+
for _d in _purge_dirs:
|
|
492
|
+
if _d.exists():
|
|
493
|
+
shutil.rmtree(_d, ignore_errors=True)
|
|
494
|
+
click.echo(f" ✓ Removed {_d.resolve()}")
|
|
472
495
|
else:
|
|
473
496
|
# Test DB connection — auto-provision if running as root
|
|
474
497
|
click.echo("Connecting to database...")
|
|
@@ -549,7 +572,7 @@ def init(db_url, api_port, ui_port, cloud_token, force):
|
|
|
549
572
|
from celerp.gateway.state import build_handoff_url as _handoff
|
|
550
573
|
if _settings.star_cta_enabled:
|
|
551
574
|
click.echo(
|
|
552
|
-
"New and independent -
|
|
575
|
+
"New and independent - early stargazers are how other teams find us.\n"
|
|
553
576
|
f" Back us early: {_handoff('/github', medium='cli')}\n"
|
|
554
577
|
)
|
|
555
578
|
|
|
@@ -49,6 +49,12 @@ async def _check_period_lock(session, company_id, data: dict) -> None:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
async def emit_event(session, **kwargs) -> LedgerEntry:
|
|
52
|
+
# A backup represents a clean point in time: while one is building, pause writes
|
|
53
|
+
# (reads, which never emit, are unaffected) so nothing changes mid-backup.
|
|
54
|
+
from celerp.services.backup_state import is_active as _backup_active
|
|
55
|
+
if _backup_active():
|
|
56
|
+
raise HTTPException(status_code=503, detail="Backup in progress, try again shortly.")
|
|
57
|
+
|
|
52
58
|
schema = EVENT_SCHEMA_MAP.get(kwargs["event_type"])
|
|
53
59
|
if schema is None:
|
|
54
60
|
raise ValueError(f"Unknown event_type: {kwargs['event_type']}")
|
|
@@ -73,10 +73,13 @@ class MaxBodySizeMiddleware:
|
|
|
73
73
|
await self.app(scope, receive, send)
|
|
74
74
|
return
|
|
75
75
|
|
|
76
|
-
# Bulk file/cert import
|
|
77
|
-
# exempt
|
|
78
|
-
# size and the per-file 50 MB limit in store_upload().
|
|
79
|
-
|
|
76
|
+
# Bulk file/cert import and backup restore legitimately upload large archives;
|
|
77
|
+
# exempt them from the body cap. Bulk imports are bounded by the WS tunnel frame
|
|
78
|
+
# size and the per-file 50 MB limit in store_upload(); a .celerp-backup restore is
|
|
79
|
+
# a trusted whole-instance archive (DB + files) that is inherently large.
|
|
80
|
+
if scope.get("path", "").endswith(
|
|
81
|
+
("/items/files/bulk", "/items/attachments/bulk", "/backup/import", "/backup/import-bootstrap")
|
|
82
|
+
):
|
|
80
83
|
await self.app(scope, receive, send)
|
|
81
84
|
return
|
|
82
85
|
|
|
@@ -131,22 +131,19 @@ async def backup_status() -> dict:
|
|
|
131
131
|
"""Return backup scheduler state: last results and next scheduled run times."""
|
|
132
132
|
from celerp.config import settings
|
|
133
133
|
from celerp.services import backup_scheduler
|
|
134
|
+
from celerp.services.backup_state import is_active
|
|
134
135
|
db = backup_scheduler.last_db_result()
|
|
135
|
-
fl = backup_scheduler.last_file_result()
|
|
136
136
|
next_db = backup_scheduler.next_db_run_utc()
|
|
137
|
-
next_fl = backup_scheduler.next_file_run_utc()
|
|
138
137
|
running = bool(backup_scheduler._db_task and not backup_scheduler._db_task.done())
|
|
139
138
|
return {
|
|
140
139
|
"running": running,
|
|
140
|
+
"active": is_active(),
|
|
141
141
|
"gateway_token_set": bool(settings.gateway_token),
|
|
142
142
|
"enc_ok": bool(settings.backup_encryption_key),
|
|
143
143
|
"enc_key": settings.backup_encryption_key or "",
|
|
144
144
|
"db": {"ok": db.ok, "error": db.error, "size_bytes": db.size_bytes,
|
|
145
145
|
"last_run": db.last_run.isoformat() if db.last_run else None},
|
|
146
|
-
"file": {"ok": fl.ok, "error": fl.error, "size_bytes": fl.size_bytes,
|
|
147
|
-
"last_run": fl.last_run.isoformat() if fl.last_run else None},
|
|
148
146
|
"next_db_utc": next_db.isoformat() if next_db else None,
|
|
149
|
-
"next_file_utc": next_fl.isoformat() if next_fl else None,
|
|
150
147
|
}
|
|
151
148
|
|
|
152
149
|
|
|
@@ -53,7 +53,8 @@ async def dismiss(
|
|
|
53
53
|
@router.get("/badge/verify-url")
|
|
54
54
|
async def badge_verify_url(return_url: str, _user=Depends(get_current_user)) -> dict:
|
|
55
55
|
"""The relay verify-start URL to open in the browser. ``return_url`` is where the
|
|
56
|
-
relay redirects back with the credential (the install's own page).
|
|
56
|
+
relay redirects back with the credential (the install's own page). Claiming a badge
|
|
57
|
+
lists the founder on the public wall - there is no separate opt-in."""
|
|
57
58
|
from urllib.parse import urlencode
|
|
58
59
|
|
|
59
60
|
iid = ensure_instance_id()
|
|
@@ -66,8 +67,11 @@ async def get_badge(
|
|
|
66
67
|
user=Depends(get_current_user),
|
|
67
68
|
session: AsyncSession = Depends(get_session),
|
|
68
69
|
) -> dict:
|
|
69
|
-
"""The current user's supporter badge
|
|
70
|
-
return {
|
|
70
|
+
"""The current user's supporter badge (or null) + the public founders wall URL."""
|
|
71
|
+
return {
|
|
72
|
+
"badge": _badge.to_dict(await _badge.get(session, user.id)),
|
|
73
|
+
"wall_url": f"{relay_http_url()}/github/founders",
|
|
74
|
+
}
|
|
71
75
|
|
|
72
76
|
|
|
73
77
|
@router.post("/badge")
|
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
# Copyright (c) 2026 Noah Severs
|
|
2
2
|
# SPDX-License-Identifier: BUSL-1.1
|
|
3
3
|
|
|
4
|
-
"""
|
|
4
|
+
"""Backup primitives — pg_dump/pg_restore and AES-256-GCM encrypt/decrypt.
|
|
5
5
|
|
|
6
6
|
Encryption:
|
|
7
7
|
- Key: 32-byte random, base64-encoded, stored in config.toml [cloud] section
|
|
8
8
|
- Nonce: 12-byte random, prepended to ciphertext
|
|
9
9
|
- Wire format: nonce (12 bytes) + ciphertext (variable) + tag (16 bytes, appended by GCM)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- Auth: X-Session-Token + X-Instance-ID headers (from live gateway session)
|
|
11
|
+
These primitives are shared by the local export/import path and the content-addressed
|
|
12
|
+
cloud snapshot client (``backup_repo``).
|
|
14
13
|
"""
|
|
15
14
|
|
|
16
15
|
from __future__ import annotations
|
|
17
16
|
|
|
18
17
|
import base64
|
|
19
|
-
import io
|
|
20
|
-
import os
|
|
21
18
|
import subprocess
|
|
22
19
|
import sys
|
|
23
20
|
from dataclasses import dataclass, field
|
|
24
21
|
from pathlib import Path
|
|
25
22
|
|
|
26
23
|
from celerp.config import settings
|
|
27
|
-
from celerp.gateway.state import get_session_token, relay_http_url, relay_session_headers
|
|
28
24
|
|
|
29
25
|
_NONCE_BYTES = 12
|
|
30
26
|
|
|
@@ -173,49 +169,6 @@ def decrypt(blob: bytes, key: bytes) -> bytes:
|
|
|
173
169
|
return aesgcm.decrypt(nonce, ciphertext, associated_data=None)
|
|
174
170
|
|
|
175
171
|
|
|
176
|
-
async def upload_to_relay(
|
|
177
|
-
blob: bytes,
|
|
178
|
-
backup_type: str = "database",
|
|
179
|
-
label: str | None = None,
|
|
180
|
-
) -> dict:
|
|
181
|
-
"""Upload encrypted blob to relay REST API. Returns response dict."""
|
|
182
|
-
import httpx
|
|
183
|
-
url = f"{relay_http_url()}/backup/upload"
|
|
184
|
-
params: dict[str, str] = {"backup_type": backup_type}
|
|
185
|
-
if label:
|
|
186
|
-
params["label"] = label
|
|
187
|
-
|
|
188
|
-
async with httpx.AsyncClient(timeout=120) as client:
|
|
189
|
-
response = await client.post(
|
|
190
|
-
url,
|
|
191
|
-
headers=relay_session_headers(),
|
|
192
|
-
params=params,
|
|
193
|
-
files={"file": ("backup.bin", io.BytesIO(blob), "application/octet-stream")},
|
|
194
|
-
)
|
|
195
|
-
if response.status_code != 200:
|
|
196
|
-
raise RuntimeError(
|
|
197
|
-
f"Relay upload failed with HTTP {response.status_code}: {response.text[:200]}"
|
|
198
|
-
)
|
|
199
|
-
return response.json()
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
async def download_from_relay(backup_id: str) -> bytes:
|
|
203
|
-
"""Download encrypted backup blob from relay via presigned URL."""
|
|
204
|
-
import httpx
|
|
205
|
-
url = f"{relay_http_url()}/backup/{backup_id}"
|
|
206
|
-
async with httpx.AsyncClient(timeout=30) as client:
|
|
207
|
-
response = await client.get(url, headers=relay_session_headers())
|
|
208
|
-
if response.status_code != 200:
|
|
209
|
-
raise RuntimeError(f"Failed to get download URL: HTTP {response.status_code}")
|
|
210
|
-
|
|
211
|
-
presigned_url = response.json()["url"]
|
|
212
|
-
async with httpx.AsyncClient(timeout=120) as client:
|
|
213
|
-
dl = await client.get(presigned_url)
|
|
214
|
-
if dl.status_code != 200:
|
|
215
|
-
raise RuntimeError(f"Download failed: HTTP {dl.status_code}")
|
|
216
|
-
return dl.content
|
|
217
|
-
|
|
218
|
-
|
|
219
172
|
def restore_database(dump_bytes: bytes, database_url: str) -> None:
|
|
220
173
|
"""Run pg_restore from dump bytes into database_url.
|
|
221
174
|
|
|
@@ -239,47 +192,3 @@ def restore_database(dump_bytes: bytes, database_url: str) -> None:
|
|
|
239
192
|
# pg_restore returns non-zero for warnings too; only raise on real errors
|
|
240
193
|
if "ERROR" in stderr.upper():
|
|
241
194
|
raise RuntimeError(f"pg_restore failed (exit {result.returncode}): {stderr}")
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
async def run_backup(label: str | None = None) -> BackupResult:
|
|
245
|
-
"""Orchestrate a full DB backup: dump → encrypt → upload to relay.
|
|
246
|
-
|
|
247
|
-
Returns BackupResult. Never raises — errors are captured in result.error.
|
|
248
|
-
"""
|
|
249
|
-
if not settings.backup_encryption_key:
|
|
250
|
-
return BackupResult(ok=False, size_bytes=0, error="BACKUP_ENCRYPTION_KEY is not configured")
|
|
251
|
-
|
|
252
|
-
if not get_session_token():
|
|
253
|
-
return BackupResult(ok=False, size_bytes=0, error="Relay not connected - skipping backup")
|
|
254
|
-
|
|
255
|
-
try:
|
|
256
|
-
key = _parse_key(settings.backup_encryption_key)
|
|
257
|
-
dump = dump_database(settings.database_url)
|
|
258
|
-
blob = encrypt(dump, key)
|
|
259
|
-
await upload_to_relay(blob, backup_type="database", label=label)
|
|
260
|
-
return BackupResult(ok=True, size_bytes=len(blob))
|
|
261
|
-
except Exception as exc:
|
|
262
|
-
return BackupResult(ok=False, size_bytes=0, error=str(exc))
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
async def run_restore(backup_id: str) -> BackupResult:
|
|
266
|
-
"""Download, decrypt, and restore a database backup.
|
|
267
|
-
|
|
268
|
-
Creates a safety backup first. Returns BackupResult.
|
|
269
|
-
"""
|
|
270
|
-
if not settings.backup_encryption_key:
|
|
271
|
-
return BackupResult(ok=False, size_bytes=0, error="BACKUP_ENCRYPTION_KEY is not configured")
|
|
272
|
-
|
|
273
|
-
try:
|
|
274
|
-
# Safety backup first
|
|
275
|
-
safety = await run_backup(label="pre-restore-safety")
|
|
276
|
-
if not safety.ok:
|
|
277
|
-
return BackupResult(ok=False, size_bytes=0, error=f"Safety backup failed: {safety.error}")
|
|
278
|
-
|
|
279
|
-
key = _parse_key(settings.backup_encryption_key)
|
|
280
|
-
encrypted = await download_from_relay(backup_id)
|
|
281
|
-
dump = decrypt(encrypted, key)
|
|
282
|
-
restore_database(dump, settings.database_url)
|
|
283
|
-
return BackupResult(ok=True, size_bytes=len(dump))
|
|
284
|
-
except Exception as exc:
|
|
285
|
-
return BackupResult(ok=False, size_bytes=0, error=str(exc))
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# Copyright (c) 2026 Noah Severs
|
|
2
2
|
# SPDX-License-Identifier: BUSL-1.1
|
|
3
3
|
|
|
4
|
-
"""Export full backup as .celerp-backup (unencrypted tar.gz).
|
|
4
|
+
"""Export a full local backup as .celerp-backup (unencrypted tar.gz).
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- export_from_cloud(backup_id): download encrypted → decrypt → repackage
|
|
6
|
+
``export_full()`` bundles a local pg_dump + attachments + meta.json. Cloud snapshots
|
|
7
|
+
are rebuilt into the same format by ``backup_repo.reassemble_snapshot``.
|
|
9
8
|
"""
|
|
10
9
|
|
|
11
10
|
from __future__ import annotations
|
|
@@ -135,25 +134,3 @@ async def export_full() -> Path:
|
|
|
135
134
|
[settings.data_dir / "static" / "attachments", settings.data_dir / "ai_uploads"],
|
|
136
135
|
meta,
|
|
137
136
|
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
async def export_from_cloud(backup_id: str) -> Path:
|
|
141
|
-
"""Download encrypted backup from relay, decrypt, repackage as .celerp-backup.
|
|
142
|
-
|
|
143
|
-
The decrypted blob is itself a complete .celerp-backup (built by
|
|
144
|
-
export_full when the cloud backup was first uploaded), so it already
|
|
145
|
-
carries the original meta.json with enabled_modules. We pass it through
|
|
146
|
-
unchanged — the import side reads meta.json directly.
|
|
147
|
-
"""
|
|
148
|
-
from celerp.config import settings
|
|
149
|
-
from celerp.services.backup import _parse_key, decrypt, download_from_relay
|
|
150
|
-
|
|
151
|
-
key = _parse_key(settings.backup_encryption_key)
|
|
152
|
-
encrypted = await download_from_relay(backup_id)
|
|
153
|
-
decrypted = decrypt(encrypted, key)
|
|
154
|
-
|
|
155
|
-
# Write decrypted blob as .celerp-backup
|
|
156
|
-
tmp = tempfile.NamedTemporaryFile(suffix=".celerp-backup", delete=False)
|
|
157
|
-
tmp.write(decrypted)
|
|
158
|
-
tmp.close()
|
|
159
|
-
return Path(tmp.name)
|
|
@@ -194,11 +194,11 @@ async def _safety_backup(label: str):
|
|
|
194
194
|
Module-level helper so tests can stub it cleanly.
|
|
195
195
|
"""
|
|
196
196
|
from celerp.config import settings
|
|
197
|
-
from celerp.services.backup import
|
|
197
|
+
from celerp.services.backup import BackupResult
|
|
198
198
|
if not settings.backup_encryption_key:
|
|
199
|
-
from celerp.services.backup import BackupResult
|
|
200
199
|
return BackupResult(ok=True, size_bytes=0)
|
|
201
|
-
|
|
200
|
+
from celerp.services.backup_repo import run_snapshot
|
|
201
|
+
return await run_snapshot(label=label)
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
async def _read_modules_from_restored_db() -> list[str]:
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
"""Content-addressed cloud snapshot client.
|
|
5
|
+
|
|
6
|
+
One cloud backup path for both database and files. Every member of a backup (the
|
|
7
|
+
pg_dump and each attachment/upload file) is a content-addressed *blob* keyed by the
|
|
8
|
+
SHA-256 of its plaintext, encrypted client-side and stored once on the relay. A
|
|
9
|
+
*snapshot* is a small manifest referencing the blobs needed to rebuild a
|
|
10
|
+
``.celerp-backup``. Unchanged files share a hash and are never re-uploaded, so a
|
|
11
|
+
daily backup sends only what changed.
|
|
12
|
+
|
|
13
|
+
Restore downloads the manifest, fetches its blobs, reassembles a ``.celerp-backup``
|
|
14
|
+
and feeds the canonical ``run_import`` engine — the same importer as a local restore.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import hashlib
|
|
21
|
+
import io
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import tarfile
|
|
25
|
+
import tempfile
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
import httpx
|
|
29
|
+
|
|
30
|
+
from celerp.config import settings
|
|
31
|
+
from celerp.gateway.state import get_session_token, relay_http_url, relay_session_headers
|
|
32
|
+
from celerp.services.backup import BackupResult, _parse_key, decrypt, dump_database, encrypt
|
|
33
|
+
|
|
34
|
+
log = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
_CHUNK = 1024 * 1024 # 1 MiB read window for streaming hashes
|
|
37
|
+
# Each AES-256-GCM blob carries a 12-byte nonce + 16-byte tag over the plaintext.
|
|
38
|
+
_ENC_OVERHEAD = 28
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _attachment_dirs() -> list[Path]:
|
|
42
|
+
return [settings.data_dir / "static" / "attachments", settings.data_dir / "ai_uploads"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _members() -> list[tuple[str, Path]]:
|
|
46
|
+
"""(arcname, path) for every backed-up file, arcname relative to the dir's parent
|
|
47
|
+
so it round-trips through the same ``.celerp-backup`` layout as a local export."""
|
|
48
|
+
out: list[tuple[str, Path]] = []
|
|
49
|
+
for d in _attachment_dirs():
|
|
50
|
+
if not d.exists():
|
|
51
|
+
continue
|
|
52
|
+
for p in sorted(d.rglob("*")):
|
|
53
|
+
if p.is_file():
|
|
54
|
+
try:
|
|
55
|
+
rel = str(p.relative_to(d.parent))
|
|
56
|
+
except ValueError:
|
|
57
|
+
rel = p.name
|
|
58
|
+
out.append((rel, p))
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _hash_file(path: Path) -> tuple[str, int]:
|
|
63
|
+
h = hashlib.sha256()
|
|
64
|
+
size = 0
|
|
65
|
+
with path.open("rb") as fh:
|
|
66
|
+
while chunk := fh.read(_CHUNK):
|
|
67
|
+
h.update(chunk)
|
|
68
|
+
size += len(chunk)
|
|
69
|
+
return h.hexdigest(), size
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def _build_meta() -> dict:
|
|
73
|
+
from celerp.services.backup_export import _pg_version, _read_company_enabled_modules, _version
|
|
74
|
+
import datetime as _dt
|
|
75
|
+
|
|
76
|
+
from celerp.config import read_config
|
|
77
|
+
cfg = read_config()
|
|
78
|
+
return {
|
|
79
|
+
"celerp_version": _version(),
|
|
80
|
+
"pg_version": _pg_version(),
|
|
81
|
+
"created_at": _dt.datetime.now(_dt.timezone.utc).isoformat(),
|
|
82
|
+
"company_name": cfg.get("company", {}).get("name", "unknown"),
|
|
83
|
+
"enabled_modules": await _read_company_enabled_modules(),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ── Relay API ─────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
def _relay() -> httpx.AsyncClient:
|
|
90
|
+
return httpx.AsyncClient(
|
|
91
|
+
base_url=relay_http_url(), headers=relay_session_headers(), timeout=60
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def _missing(client: httpx.AsyncClient, hashes: set[str]) -> set[str]:
|
|
96
|
+
r = await client.post("/repo/missing", json={"hashes": sorted(hashes)})
|
|
97
|
+
r.raise_for_status()
|
|
98
|
+
return set(r.json()["missing"])
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def _put_blob(client: httpx.AsyncClient, blob_hash: str, plaintext: bytes, key: bytes) -> int:
|
|
102
|
+
"""Encrypt and upload one blob via a presigned PUT. Returns stored (encrypted) size."""
|
|
103
|
+
r = await client.post(f"/repo/blob/{blob_hash}/upload-url")
|
|
104
|
+
r.raise_for_status()
|
|
105
|
+
encrypted = await asyncio.to_thread(encrypt, plaintext, key)
|
|
106
|
+
async with httpx.AsyncClient(timeout=300) as s3:
|
|
107
|
+
put = await s3.put(r.json()["url"], content=encrypted)
|
|
108
|
+
put.raise_for_status()
|
|
109
|
+
return len(encrypted)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def _get_blob(client: httpx.AsyncClient, blob_hash: str, key: bytes) -> bytes:
|
|
113
|
+
"""Download, decrypt and integrity-check one blob against its content hash."""
|
|
114
|
+
r = await client.get(f"/repo/blob/{blob_hash}/download-url")
|
|
115
|
+
r.raise_for_status()
|
|
116
|
+
async with httpx.AsyncClient(timeout=300) as s3:
|
|
117
|
+
dl = await s3.get(r.json()["url"])
|
|
118
|
+
dl.raise_for_status()
|
|
119
|
+
plaintext = await asyncio.to_thread(decrypt, dl.content, key)
|
|
120
|
+
if hashlib.sha256(plaintext).hexdigest() != blob_hash:
|
|
121
|
+
raise RuntimeError(f"Blob {blob_hash[:12]} failed integrity check")
|
|
122
|
+
return plaintext
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ── Public API ────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
async def run_snapshot(label: str | None = None) -> BackupResult:
|
|
128
|
+
"""Back up the database + files as a deduplicated snapshot. Never raises."""
|
|
129
|
+
if not settings.backup_encryption_key:
|
|
130
|
+
return BackupResult(ok=False, size_bytes=0, error="BACKUP_ENCRYPTION_KEY is not configured")
|
|
131
|
+
if not get_session_token():
|
|
132
|
+
return BackupResult(ok=False, size_bytes=0, error="Relay not connected - skipping backup")
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
from celerp.services.backup_state import writes_paused
|
|
136
|
+
|
|
137
|
+
key = _parse_key(settings.backup_encryption_key)
|
|
138
|
+
|
|
139
|
+
# Pause writes for the clean point; blocking work runs off the event loop so
|
|
140
|
+
# reads stay served throughout.
|
|
141
|
+
with writes_paused():
|
|
142
|
+
# Hash every member first (streamed) so we only ever upload what's missing.
|
|
143
|
+
dump = await asyncio.to_thread(dump_database, settings.database_url)
|
|
144
|
+
db_hash = hashlib.sha256(dump).hexdigest()
|
|
145
|
+
files = [] # manifest entries
|
|
146
|
+
by_hash: dict[str, tuple[str, Path] | None] = {db_hash: None} # None ⇒ db dump
|
|
147
|
+
for arcname, path in _members():
|
|
148
|
+
fhash, size = await asyncio.to_thread(_hash_file, path)
|
|
149
|
+
files.append({"path": arcname, "hash": fhash, "size": size})
|
|
150
|
+
by_hash.setdefault(fhash, (arcname, path))
|
|
151
|
+
|
|
152
|
+
manifest = {"meta": await _build_meta(), "db_dump": db_hash, "files": files}
|
|
153
|
+
manifest_bytes = json.dumps(manifest, indent=2).encode()
|
|
154
|
+
manifest_hash = hashlib.sha256(manifest_bytes).hexdigest()
|
|
155
|
+
|
|
156
|
+
referenced = set(by_hash) | {manifest_hash}
|
|
157
|
+
uploaded = 0
|
|
158
|
+
async with _relay() as client:
|
|
159
|
+
missing = await _missing(client, referenced)
|
|
160
|
+
sizes: dict[str, int] = {}
|
|
161
|
+
for h in missing:
|
|
162
|
+
if h == manifest_hash:
|
|
163
|
+
plaintext = manifest_bytes
|
|
164
|
+
elif h == db_hash:
|
|
165
|
+
plaintext = dump
|
|
166
|
+
else:
|
|
167
|
+
plaintext = await asyncio.to_thread(by_hash[h][1].read_bytes) # type: ignore[index]
|
|
168
|
+
sizes[h] = await _put_blob(client, h, plaintext, key)
|
|
169
|
+
uploaded += sizes[h]
|
|
170
|
+
|
|
171
|
+
# size matters only for newly-uploaded blobs (quota); the relay keeps
|
|
172
|
+
# the stored size for blobs it already has, so 1 is a harmless placeholder.
|
|
173
|
+
blobs = [{"hash": h, "size": sizes.get(h, 1)} for h in referenced]
|
|
174
|
+
r = await client.post(
|
|
175
|
+
"/repo/snapshot",
|
|
176
|
+
json={"manifest_hash": manifest_hash, "blobs": blobs, "label": label},
|
|
177
|
+
)
|
|
178
|
+
r.raise_for_status()
|
|
179
|
+
|
|
180
|
+
log.info("Snapshot committed: %d blobs referenced, %d bytes uploaded", len(referenced), uploaded)
|
|
181
|
+
return BackupResult(ok=True, size_bytes=uploaded)
|
|
182
|
+
except Exception as exc: # never raise — surfaced in result
|
|
183
|
+
return BackupResult(ok=False, size_bytes=0, error=str(exc))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def list_snapshots() -> dict:
|
|
187
|
+
"""Return the relay's snapshot list payload (items + totals). Raises on error."""
|
|
188
|
+
async with _relay() as client:
|
|
189
|
+
r = await client.get("/repo/snapshots")
|
|
190
|
+
r.raise_for_status()
|
|
191
|
+
return r.json()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def reassemble_snapshot(snapshot_id: str) -> Path:
|
|
195
|
+
"""Download a snapshot and rebuild it as a ``.celerp-backup`` temp file."""
|
|
196
|
+
key = _parse_key(settings.backup_encryption_key)
|
|
197
|
+
async with _relay() as client:
|
|
198
|
+
r = await client.get("/repo/snapshots")
|
|
199
|
+
r.raise_for_status()
|
|
200
|
+
snap = next((s for s in r.json()["items"] if s["id"] == snapshot_id), None)
|
|
201
|
+
if snap is None:
|
|
202
|
+
raise RuntimeError(f"Snapshot {snapshot_id} not found")
|
|
203
|
+
|
|
204
|
+
manifest = json.loads(await _get_blob(client, snap["manifest_hash"], key))
|
|
205
|
+
|
|
206
|
+
tmp = tempfile.NamedTemporaryFile(suffix=".celerp-backup", delete=False)
|
|
207
|
+
tmp.close()
|
|
208
|
+
with tarfile.open(tmp.name, "w:gz") as tar:
|
|
209
|
+
dump = await _get_blob(client, manifest["db_dump"], key)
|
|
210
|
+
info = tarfile.TarInfo(name="database.dump")
|
|
211
|
+
info.size = len(dump)
|
|
212
|
+
tar.addfile(info, io.BytesIO(dump))
|
|
213
|
+
|
|
214
|
+
meta_bytes = json.dumps(manifest["meta"], indent=2).encode()
|
|
215
|
+
info = tarfile.TarInfo(name="meta.json")
|
|
216
|
+
info.size = len(meta_bytes)
|
|
217
|
+
tar.addfile(info, io.BytesIO(meta_bytes))
|
|
218
|
+
|
|
219
|
+
for entry in manifest["files"]:
|
|
220
|
+
data = await _get_blob(client, entry["hash"], key)
|
|
221
|
+
info = tarfile.TarInfo(name=entry["path"])
|
|
222
|
+
info.size = len(data)
|
|
223
|
+
tar.addfile(info, io.BytesIO(data))
|
|
224
|
+
|
|
225
|
+
return Path(tmp.name)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
async def restore_snapshot(snapshot_id: str) -> BackupResult:
|
|
229
|
+
"""Reassemble a cloud snapshot and restore it through the canonical importer."""
|
|
230
|
+
if not settings.backup_encryption_key:
|
|
231
|
+
return BackupResult(ok=False, size_bytes=0, error="BACKUP_ENCRYPTION_KEY is not configured")
|
|
232
|
+
try:
|
|
233
|
+
from celerp.services.backup_import import run_import
|
|
234
|
+
path = await reassemble_snapshot(snapshot_id)
|
|
235
|
+
try:
|
|
236
|
+
await run_import(path)
|
|
237
|
+
finally:
|
|
238
|
+
path.unlink(missing_ok=True)
|
|
239
|
+
return BackupResult(ok=True, size_bytes=0)
|
|
240
|
+
except Exception as exc:
|
|
241
|
+
return BackupResult(ok=False, size_bytes=0, error=str(exc))
|