create-snapify-app 0.0.0-stage → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +24 -0
- package/README.md +55 -2
- package/bin/create-snapify-app.mjs +174 -0
- package/lib/args.mjs +59 -0
- package/lib/copy.mjs +44 -0
- package/lib/env.mjs +84 -0
- package/lib/prompt.mjs +63 -0
- package/package.json +35 -4
- package/template/.env.example +79 -0
- package/template/.github/workflows/ci.yml +23 -0
- package/template/.github/workflows/deploy.yml +26 -0
- package/template/.github/workflows/ping.yml +8 -0
- package/template/.github/workflows/release.yml +67 -0
- package/template/.snapify-install.json +5 -0
- package/template/CHANGELOG.md +28 -0
- package/template/LICENSE +14 -0
- package/template/README.md +287 -0
- package/template/administrator.html +23 -0
- package/template/apps/client/src/App.tsx +765 -0
- package/template/apps/client/src/auth.css +400 -0
- package/template/apps/client/src/lib/api.ts +22 -0
- package/template/apps/client/src/main.tsx +20 -0
- package/template/apps/community/src/App.tsx +592 -0
- package/template/apps/community/src/main.tsx +20 -0
- package/template/apps/community/src/styles.css +24 -0
- package/template/apps/developers/src/App.tsx +643 -0
- package/template/apps/developers/src/main.tsx +17 -0
- package/template/apps/developers/src/pages.tsx +1587 -0
- package/template/apps/developers/src/styles.css +98 -0
- package/template/apps/developers/src/ui.tsx +179 -0
- package/template/apps/landing/src/App.tsx +598 -0
- package/template/apps/landing/src/LegalPages.tsx +158 -0
- package/template/apps/landing/src/OrderPage.tsx +381 -0
- package/template/apps/landing/src/SeoPages.tsx +923 -0
- package/template/apps/landing/src/copy.ts +195 -0
- package/template/apps/landing/src/main.tsx +20 -0
- package/template/apps/landing/src/styles.css +803 -0
- package/template/apps/landing/src/types.ts +19 -0
- package/template/apps/marketplace/src/App.tsx +1229 -0
- package/template/apps/marketplace/src/developer/DeveloperApp.tsx +1266 -0
- package/template/apps/marketplace/src/main.tsx +20 -0
- package/template/apps/marketplace/src/styles.css +1001 -0
- package/template/apps/shared/AuthContext.tsx +65 -0
- package/template/apps/shared/Logo.tsx +33 -0
- package/template/apps/shared/api.ts +47 -0
- package/template/apps/shared/ui.tsx +128 -0
- package/template/apps/superadmin/src/App.tsx +1264 -0
- package/template/apps/superadmin/src/lib/api.ts +20 -0
- package/template/apps/superadmin/src/lib/hooks.ts +20 -0
- package/template/apps/superadmin/src/main.tsx +16 -0
- package/template/apps/superadmin/src/pages/ClientsPage.tsx +185 -0
- package/template/apps/superadmin/src/pages/InvoicesPage.tsx +157 -0
- package/template/apps/superadmin/src/pages/NewsletterPage.tsx +212 -0
- package/template/apps/superadmin/src/pages/OrdersPage.tsx +195 -0
- package/template/apps/superadmin/src/pages/PaymentsPage.tsx +150 -0
- package/template/apps/superadmin/src/pages/PayoutsPage.tsx +133 -0
- package/template/apps/superadmin/src/pages/PublishersPage.tsx +151 -0
- package/template/apps/superadmin/src/pages/SeoControlPage.tsx +692 -0
- package/template/apps/superadmin/src/pages/SettingsPage.tsx +199 -0
- package/template/apps/superadmin/src/pages/SystemPage.tsx +109 -0
- package/template/apps/superadmin/src/pages/TicketsPage.tsx +201 -0
- package/template/apps/superadmin/src/pages/UsersPage.tsx +167 -0
- package/template/client.html +21 -0
- package/template/community/hamdiayari/akhbar/README.md +23 -0
- package/template/community/hamdiayari/akhbar/screenshot.jpg +0 -0
- package/template/community/hamdiayari/akhbar/snapify.theme.json +76 -0
- package/template/community/hamdiayari/akhbar/src/Brand.tsx +26 -0
- package/template/community/hamdiayari/akhbar/src/Footer.tsx +182 -0
- package/template/community/hamdiayari/akhbar/src/Header.tsx +191 -0
- package/template/community/hamdiayari/akhbar/src/Layout.tsx +49 -0
- package/template/community/hamdiayari/akhbar/src/Social.tsx +33 -0
- package/template/community/hamdiayari/akhbar/src/Ticker.tsx +86 -0
- package/template/community/hamdiayari/akhbar/src/Views.tsx +285 -0
- package/template/community/hamdiayari/akhbar/src/blocks/CollectionBlocks.tsx +672 -0
- package/template/community/hamdiayari/akhbar/src/blocks/ContentBlocks.tsx +305 -0
- package/template/community/hamdiayari/akhbar/src/blocks/ConversionBlocks.tsx +569 -0
- package/template/community/hamdiayari/akhbar/src/blocks/HeaderBlocks.tsx +287 -0
- package/template/community/hamdiayari/akhbar/src/index.tsx +51 -0
- package/template/community/hamdiayari/akhbar/src/kit.tsx +357 -0
- package/template/community/hamdiayari/akhbar/src/site.ts +96 -0
- package/template/community/hamdiayari/akhbar/src/styles.css +566 -0
- package/template/community/hamdiayari/akhbar-actualites/README.md +28 -0
- package/template/community/hamdiayari/akhbar-actualites/screenshot.jpg +0 -0
- package/template/community/hamdiayari/akhbar-actualites/snapify.module.json +107 -0
- package/template/community/hamdiayari/akhbar-actualites/src/admin/AudiencePage.tsx +185 -0
- package/template/community/hamdiayari/akhbar-actualites/src/admin/index.tsx +11 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/Archive.tsx +142 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/cards.tsx +152 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/index.tsx +71 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/posts.ts +103 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/styles.css +568 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/widgets.tsx +226 -0
- package/template/community/hamdiayari/akhbar-actualites/src/server.ts +119 -0
- package/template/community/hamdiayari/akhbar-actualites/src/types.ts +25 -0
- package/template/community/hamdiayari/akhbar-urgent/README.md +36 -0
- package/template/community/hamdiayari/akhbar-urgent/screenshot.jpg +0 -0
- package/template/community/hamdiayari/akhbar-urgent/snapify.module.json +64 -0
- package/template/community/hamdiayari/akhbar-urgent/src/admin/FlashesPage.tsx +229 -0
- package/template/community/hamdiayari/akhbar-urgent/src/admin/index.tsx +11 -0
- package/template/community/hamdiayari/akhbar-urgent/src/public/Ticker.tsx +81 -0
- package/template/community/hamdiayari/akhbar-urgent/src/public/index.tsx +63 -0
- package/template/community/hamdiayari/akhbar-urgent/src/public/styles.css +142 -0
- package/template/community/hamdiayari/akhbar-urgent/src/server.ts +101 -0
- package/template/community/hamdiayari/akhbar-urgent/src/types.ts +35 -0
- package/template/community.html +21 -0
- package/template/developers.html +24 -0
- package/template/docker-compose.db.yml +24 -0
- package/template/docs/BACKOFFICE_SNAPIFY.md +130 -0
- package/template/docs/CI_CD.md +66 -0
- package/template/docs/MODULES.md +346 -0
- package/template/docs/PLATFORM_DEPLOY.md +54 -0
- package/template/docs/logo.svg +10 -0
- package/template/drizzle/0000_init.sql +147 -0
- package/template/drizzle/0001_page_project.sql +5 -0
- package/template/drizzle/0002_modules.sql +23 -0
- package/template/drizzle/0003_themes.sql +4 -0
- package/template/drizzle/0004_security.sql +82 -0
- package/template/drizzle/0005_module_records.sql +16 -0
- package/template/drizzle/0006_avatars.sql +4 -0
- package/template/drizzle/0007_platform.sql +264 -0
- package/template/drizzle/0008_mp_validation.sql +3 -0
- package/template/drizzle/0009_saas_ops.sql +77 -0
- package/template/drizzle/0010_cms_chrome.sql +13 -0
- package/template/drizzle/0011_client_portal.sql +26 -0
- package/template/drizzle/meta/0000_snapshot.json +998 -0
- package/template/drizzle/meta/0001_snapshot.json +1004 -0
- package/template/drizzle/meta/_journal.json +90 -0
- package/template/drizzle.config.ts +16 -0
- package/template/ecosystem.snapify.cjs +24 -0
- package/template/for.html +1 -0
- package/template/index.html +29 -0
- package/template/landing.html +37 -0
- package/template/marketplace/catalog.json +373 -0
- package/template/marketplace/gallery/hamdiayari.preveris/01-home.jpg +0 -0
- package/template/marketplace/gallery/hamdiayari.recaptcha/01-reglages.jpg +0 -0
- package/template/marketplace/gallery/hamdiayari.recaptcha/02-logo.jpg +0 -0
- package/template/marketplace/gallery/novalabs.ai-articles/01-generer.jpg +0 -0
- package/template/marketplace/gallery/novalabs.ai-articles/02-fournisseur-ia.jpg +0 -0
- package/template/marketplace/gallery/novalabs.calendar/01-agenda-site.jpg +0 -0
- package/template/marketplace/gallery/novalabs.calendar/02-evenements-admin.jpg +0 -0
- package/template/marketplace/gallery/novalabs.calendar/03-reglages.jpg +0 -0
- package/template/marketplace/screenshots/hamdiayari.preveris-1.0.6.jpg +0 -0
- package/template/marketplace/screenshots/hamdiayari.recaptcha-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/hamdiayari.wp-seo-migrator-1.1.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.ai-articles-1.0.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.ai-chat-1.0.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.calendar-1.0.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.cart-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.checkout-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.customers-1.1.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.dogga-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.mahdia-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.shop-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.sidibou-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.whatsapp-1.0.1.jpg +0 -0
- package/template/marketplace.html +21 -0
- package/template/metadata.json +7 -0
- package/template/modules/ai-articles/screenshot.jpg +0 -0
- package/template/modules/ai-articles/snapify.module.json +74 -0
- package/template/modules/ai-articles/src/admin/ArticlesPage.tsx +162 -0
- package/template/modules/ai-articles/src/admin/GeneratePage.tsx +118 -0
- package/template/modules/ai-articles/src/admin/index.tsx +12 -0
- package/template/modules/ai-articles/src/public/index.tsx +119 -0
- package/template/modules/ai-articles/src/server.ts +287 -0
- package/template/modules/ai-articles/src/types.ts +30 -0
- package/template/modules/ai-chat/screenshot.jpg +0 -0
- package/template/modules/ai-chat/snapify.module.json +217 -0
- package/template/modules/ai-chat/src/public.tsx +198 -0
- package/template/modules/ai-chat/src/server.ts +142 -0
- package/template/modules/ai-theme-studio/README.md +15 -0
- package/template/modules/ai-theme-studio/screenshot.jpg +0 -0
- package/template/modules/ai-theme-studio/snapify.module.json +175 -0
- package/template/modules/ai-theme-studio/src/admin/HistoryPage.tsx +91 -0
- package/template/modules/ai-theme-studio/src/admin/StudioPage.tsx +414 -0
- package/template/modules/ai-theme-studio/src/admin/index.tsx +11 -0
- package/template/modules/ai-theme-studio/src/lib/images.ts +240 -0
- package/template/modules/ai-theme-studio/src/lib/llm.ts +123 -0
- package/template/modules/ai-theme-studio/src/lib/normalize.ts +714 -0
- package/template/modules/ai-theme-studio/src/lib/prompts.ts +198 -0
- package/template/modules/ai-theme-studio/src/server.ts +137 -0
- package/template/modules/ai-theme-studio/src/types.ts +112 -0
- package/template/modules/calendar/screenshot.jpg +0 -0
- package/template/modules/calendar/snapify.module.json +45 -0
- package/template/modules/calendar/src/admin/EventsPage.tsx +217 -0
- package/template/modules/calendar/src/admin/index.tsx +11 -0
- package/template/modules/calendar/src/public/index.tsx +231 -0
- package/template/modules/calendar/src/server.ts +143 -0
- package/template/modules/calendar/src/types.ts +25 -0
- package/template/modules/cart/screenshot.jpg +0 -0
- package/template/modules/cart/snapify.module.json +90 -0
- package/template/modules/cart/src/admin/index.tsx +265 -0
- package/template/modules/cart/src/public/CartPage.tsx +198 -0
- package/template/modules/cart/src/public/index.tsx +261 -0
- package/template/modules/cart/src/public/useQuote.ts +67 -0
- package/template/modules/cart/src/server.ts +148 -0
- package/template/modules/cart/src/types.ts +31 -0
- package/template/modules/checkout/screenshot.jpg +0 -0
- package/template/modules/checkout/snapify.module.json +264 -0
- package/template/modules/checkout/src/admin/OrderDetailPage.tsx +304 -0
- package/template/modules/checkout/src/admin/badges.tsx +28 -0
- package/template/modules/checkout/src/admin/index.tsx +236 -0
- package/template/modules/checkout/src/public/CheckoutPage.tsx +461 -0
- package/template/modules/checkout/src/public/OrderPages.tsx +153 -0
- package/template/modules/checkout/src/public/index.tsx +23 -0
- package/template/modules/checkout/src/server.ts +383 -0
- package/template/modules/checkout/src/types.ts +70 -0
- package/template/modules/customers/screenshot.jpg +0 -0
- package/template/modules/customers/snapify.module.json +115 -0
- package/template/modules/customers/src/admin/CustomerDetailPage.tsx +274 -0
- package/template/modules/customers/src/admin/index.tsx +194 -0
- package/template/modules/customers/src/public/AccountPages.tsx +470 -0
- package/template/modules/customers/src/public/AddressForm.tsx +115 -0
- package/template/modules/customers/src/public/AuthPages.tsx +349 -0
- package/template/modules/customers/src/public/Avatar.tsx +29 -0
- package/template/modules/customers/src/public/ProfileSection.tsx +291 -0
- package/template/modules/customers/src/public/index.tsx +137 -0
- package/template/modules/customers/src/public/session.ts +51 -0
- package/template/modules/customers/src/server.ts +421 -0
- package/template/modules/customers/src/types.ts +53 -0
- package/template/modules/recaptcha/logo.jpg +0 -0
- package/template/modules/recaptcha/screenshot.jpg +0 -0
- package/template/modules/recaptcha/snapify.module.json +99 -0
- package/template/modules/recaptcha/src/public/index.tsx +94 -0
- package/template/modules/recaptcha/src/server.ts +65 -0
- package/template/modules/recaptcha/src/types.ts +18 -0
- package/template/modules/shop/screenshot.jpg +0 -0
- package/template/modules/shop/snapify.module.json +340 -0
- package/template/modules/shop/src/admin/CategoriesPage.tsx +224 -0
- package/template/modules/shop/src/admin/ProductEditor.tsx +313 -0
- package/template/modules/shop/src/admin/ProductsPage.tsx +307 -0
- package/template/modules/shop/src/admin/index.tsx +14 -0
- package/template/modules/shop/src/public/Catalog.tsx +244 -0
- package/template/modules/shop/src/public/NotFound.tsx +22 -0
- package/template/modules/shop/src/public/ProductPage.tsx +195 -0
- package/template/modules/shop/src/public/index.tsx +134 -0
- package/template/modules/shop/src/public/useFetch.ts +26 -0
- package/template/modules/shop/src/server.ts +493 -0
- package/template/modules/shop/src/shared.ts +41 -0
- package/template/modules/shop/src/types.ts +60 -0
- package/template/modules/whatsapp/screenshot.jpg +0 -0
- package/template/modules/whatsapp/snapify.module.json +121 -0
- package/template/modules/whatsapp/src/public.tsx +119 -0
- package/template/modules/wp-seo-migrator/ARCHITECTURE.md +113 -0
- package/template/modules/wp-seo-migrator/README.md +21 -0
- package/template/modules/wp-seo-migrator/fixtures/aioseo-sample.csv +3 -0
- package/template/modules/wp-seo-migrator/fixtures/sample-exporter.json +39 -0
- package/template/modules/wp-seo-migrator/fixtures/sample-page.html +12 -0
- package/template/modules/wp-seo-migrator/fixtures/sample.htaccess +3 -0
- package/template/modules/wp-seo-migrator/screenshot.jpg +0 -0
- package/template/modules/wp-seo-migrator/snapify.module.json +87 -0
- package/template/modules/wp-seo-migrator/src/admin/HistoryPage.tsx +64 -0
- package/template/modules/wp-seo-migrator/src/admin/WizardPage.tsx +788 -0
- package/template/modules/wp-seo-migrator/src/admin/i18n.ts +156 -0
- package/template/modules/wp-seo-migrator/src/admin/index.tsx +11 -0
- package/template/modules/wp-seo-migrator/src/connectors/CUSTOM_CONNECTOR.md +31 -0
- package/template/modules/wp-seo-migrator/src/connectors/csvSeoFactory.ts +106 -0
- package/template/modules/wp-seo-migrator/src/connectors/exporter.ts +96 -0
- package/template/modules/wp-seo-migrator/src/connectors/gscCsv.ts +53 -0
- package/template/modules/wp-seo-migrator/src/connectors/htaccess.ts +65 -0
- package/template/modules/wp-seo-migrator/src/connectors/htmlCrawler.ts +90 -0
- package/template/modules/wp-seo-migrator/src/connectors/index.ts +113 -0
- package/template/modules/wp-seo-migrator/src/connectors/pluginCsv.ts +77 -0
- package/template/modules/wp-seo-migrator/src/connectors/rankMathCsv.ts +77 -0
- package/template/modules/wp-seo-migrator/src/connectors/redirection.ts +62 -0
- package/template/modules/wp-seo-migrator/src/connectors/sitemap.ts +44 -0
- package/template/modules/wp-seo-migrator/src/connectors/utils.ts +111 -0
- package/template/modules/wp-seo-migrator/src/connectors/wxr.ts +103 -0
- package/template/modules/wp-seo-migrator/src/connectors/yoastCsv.ts +56 -0
- package/template/modules/wp-seo-migrator/src/pipeline/analyze.ts +158 -0
- package/template/modules/wp-seo-migrator/src/pipeline/htmlParse.ts +58 -0
- package/template/modules/wp-seo-migrator/src/pipeline/remoteAuto.ts +328 -0
- package/template/modules/wp-seo-migrator/src/server.ts +492 -0
- package/template/modules/wp-seo-migrator/src/types.ts +151 -0
- package/template/modules/wp-seo-migrator/tests/connectors-extra.test.ts +39 -0
- package/template/modules/wp-seo-migrator/tests/merge.test.ts +25 -0
- package/template/package-lock.json +6016 -0
- package/template/package.json +74 -0
- package/template/public/images/photos/caravane-chameaux-desert.webp +0 -0
- package/template/public/images/photos/hammamet-medina-plage.jpg +0 -0
- package/template/public/images/photos/maison-campagne-collines.webp +0 -0
- package/template/public/images/photos/maisons-troglodytes-desert.jpg +0 -0
- package/template/public/images/photos/panorama-ville.jpg +0 -0
- package/template/public/images/photos/preveris-accessibilite-erp.jpg +0 -0
- package/template/public/images/photos/preveris-collectivites.jpg +0 -0
- package/template/public/images/photos/preveris-etudes-plans.jpg +0 -0
- package/template/public/images/photos/preveris-hero-erp.jpg +0 -0
- package/template/public/images/photos/preveris-ssi-incendie.jpg +0 -0
- package/template/public/images/photos/preveris-suivi-hotel.jpg +0 -0
- package/template/public/images/photos/ruelle-bleue-medina.jpg +0 -0
- package/template/public/images/photos/sidi-bou-said-port.webp +0 -0
- package/template/public/images/photos/terrasse-blanche-mer-voilier.jpg +0 -0
- package/template/public/landing/hero-ai-cms.jpg +0 -0
- package/template/public/landing/shot-dogga.jpg +0 -0
- package/template/public/landing/shot-mahdia.jpg +0 -0
- package/template/public/landing/shot-sidibou.jpg +0 -0
- package/template/public/llms.txt +35 -0
- package/template/public/logo.svg +10 -0
- package/template/scripts/app-pack.ts +155 -0
- package/template/scripts/app-publish.ts +123 -0
- package/template/scripts/apply-ecommerce-home.ts +196 -0
- package/template/scripts/auto-deploy-poll.sh +13 -0
- package/template/scripts/db-backup.sh +16 -0
- package/template/scripts/db-restore.sh +11 -0
- package/template/scripts/deploy.sh +34 -0
- package/template/scripts/github-webhook-deploy.sh +7 -0
- package/template/scripts/install-new-modules.ts +96 -0
- package/template/scripts/refresh-demo-baseline.ts +9 -0
- package/template/scripts/reinstall-marketplace-packs.ts +51 -0
- package/template/scripts/restore-classic-home.ts +13 -0
- package/template/scripts/sdk-pack.ts +289 -0
- package/template/scripts/sdk-publish.ts +75 -0
- package/template/scripts/seed-demo-products.ts +120 -0
- package/template/scripts/seed-theme-content-packs.ts +42 -0
- package/template/scripts/snapify-module.ts +207 -0
- package/template/scripts/test-theme-switch.ts +24 -0
- package/template/sdk/admin.css +18 -0
- package/template/sdk/admin.ts +95 -0
- package/template/sdk/build.ts +249 -0
- package/template/sdk/commerce.ts +512 -0
- package/template/sdk/content.ts +76 -0
- package/template/sdk/icons.ts +101 -0
- package/template/sdk/image.ts +60 -0
- package/template/sdk/index.ts +500 -0
- package/template/sdk/package.ts +298 -0
- package/template/sdk/recaptcha.tsx +122 -0
- package/template/sdk/settings.ts +84 -0
- package/template/sdk/theme.css +29 -0
- package/template/sdk/theme.ts +100 -0
- package/template/sdk/ui/commerce.tsx +385 -0
- package/template/sdk/ui/orders.tsx +136 -0
- package/template/sdk-kit/CHANGELOG.md +17 -0
- package/template/sdk-kit/LICENSE.md +47 -0
- package/template/sdk-kit/README.md +81 -0
- package/template/sdk-kit/cli.ts +339 -0
- package/template/sdk-kit/create-snapify/README.md +28 -0
- package/template/sdk-kit/create-snapify/create-snapify.mjs +11 -0
- package/template/sdk-kit/create-snapify/package.template.json +16 -0
- package/template/sdk-kit/package.template.json +95 -0
- package/template/sdk-kit/templates/common/README.md.tpl +27 -0
- package/template/sdk-kit/templates/common/_gitignore +3 -0
- package/template/sdk-kit/templates/common/package.json.tpl +19 -0
- package/template/sdk-kit/templates/common/src/snapify-env.d.ts +2 -0
- package/template/sdk-kit/templates/common/tsconfig.json +15 -0
- package/template/sdk-kit/templates/module/snapify.module.json.tpl +33 -0
- package/template/sdk-kit/templates/module/src/admin.tsx +53 -0
- package/template/sdk-kit/templates/module/src/public.tsx +49 -0
- package/template/sdk-kit/templates/module/src/server.ts +30 -0
- package/template/sdk-kit/templates/module/src/types.ts +19 -0
- package/template/sdk-kit/templates/theme/snapify.theme.json.tpl +22 -0
- package/template/sdk-kit/templates/theme/src/Footer.tsx +40 -0
- package/template/sdk-kit/templates/theme/src/Header.tsx +47 -0
- package/template/sdk-kit/templates/theme/src/Views.tsx +36 -0
- package/template/sdk-kit/templates/theme/src/blocks.tsx +93 -0
- package/template/sdk-kit/templates/theme/src/index.tsx +30 -0
- package/template/sdk-kit/templates/theme/src/site.tsx +59 -0
- package/template/sdk-kit/templates/theme/src/styles.css +71 -0
- package/template/server/client/routes.ts +252 -0
- package/template/server/cms/analyticsStore.ts +180 -0
- package/template/server/cms/auth.ts +155 -0
- package/template/server/cms/blockCatalog.ts +342 -0
- package/template/server/cms/blocks.ts +521 -0
- package/template/server/cms/contentMigration.ts +1119 -0
- package/template/server/cms/defaults.ts +303 -0
- package/template/server/cms/permissions.ts +154 -0
- package/template/server/cms/restoreClassicHome.ts +183 -0
- package/template/server/cms/revisions.ts +99 -0
- package/template/server/cms/scheduler.ts +78 -0
- package/template/server/cms/seed.ts +72 -0
- package/template/server/cms/seoAnalyzer.ts +213 -0
- package/template/server/cms/store.ts +122 -0
- package/template/server/cms/themeContent.ts +1386 -0
- package/template/server/cms/themePacks/sidibou-branding.json +1 -0
- package/template/server/cms/themePacks/sidibou-home.json +1 -0
- package/template/server/cms/themePacks/sidibou-menus.json +1 -0
- package/template/server/cms/types.ts +432 -0
- package/template/server/community/routes.ts +410 -0
- package/template/server/db/client.ts +43 -0
- package/template/server/db/repository.ts +389 -0
- package/template/server/db/schema.ts +726 -0
- package/template/server/demo/cmsReset.ts +196 -0
- package/template/server/developers/sdk.ts +85 -0
- package/template/server/geoip.ts +15 -0
- package/template/server/http.ts +53 -0
- package/template/server/mail.ts +216 -0
- package/template/server/marketplace/catalog.ts +74 -0
- package/template/server/marketplace/packageValidate.ts +475 -0
- package/template/server/marketplace/payments.ts +193 -0
- package/template/server/marketplace/routes.ts +850 -0
- package/template/server/marketplace/signing.ts +116 -0
- package/template/server/marketplace/syncOfficial.ts +282 -0
- package/template/server/media/images.ts +63 -0
- package/template/server/middleware/adminAuth.ts +134 -0
- package/template/server/modules/legacy.ts +44 -0
- package/template/server/modules/manager.ts +1122 -0
- package/template/server/modules/marketplace.ts +178 -0
- package/template/server/modules/outbound.ts +178 -0
- package/template/server/modules/purchases.ts +149 -0
- package/template/server/modules/records.ts +168 -0
- package/template/server/modules/themeHtml.ts +32 -0
- package/template/server/modules/trust.ts +16 -0
- package/template/server/modules/types.ts +147 -0
- package/template/server/platform/auth.ts +223 -0
- package/template/server/platform/clientWelcome.ts +168 -0
- package/template/server/platform/clients.ts +139 -0
- package/template/server/platform/dailyImprove.ts +356 -0
- package/template/server/platform/flouci.ts +126 -0
- package/template/server/platform/host.ts +69 -0
- package/template/server/platform/hostingProviders.ts +143 -0
- package/template/server/platform/licenseOrders.ts +291 -0
- package/template/server/platform/money.ts +42 -0
- package/template/server/platform/passwords.ts +41 -0
- package/template/server/platform/region.ts +86 -0
- package/template/server/platform/routes.ts +225 -0
- package/template/server/platform/seed.ts +82 -0
- package/template/server/platform/settings.ts +72 -0
- package/template/server/routes/adminAccount.ts +403 -0
- package/template/server/routes/adminCms.ts +808 -0
- package/template/server/routes/adminSystem.ts +243 -0
- package/template/server/routes/adminUsers.ts +449 -0
- package/template/server/routes/googleOAuth.ts +327 -0
- package/template/server/routes/modules.ts +323 -0
- package/template/server/routes/publicCms.ts +313 -0
- package/template/server/routes/seoPublic.ts +158 -0
- package/template/server/security/accounts.ts +121 -0
- package/template/server/security/audit.ts +143 -0
- package/template/server/security/authTokens.ts +74 -0
- package/template/server/security/ip.ts +68 -0
- package/template/server/security/rateLimit.ts +85 -0
- package/template/server/security/retention.ts +46 -0
- package/template/server/security/secrets.ts +44 -0
- package/template/server/security/sessions.ts +140 -0
- package/template/server/security/totp.ts +101 -0
- package/template/server/superadmin/analytics.ts +109 -0
- package/template/server/superadmin/auth.ts +92 -0
- package/template/server/superadmin/backlinksCatalog.ts +210 -0
- package/template/server/superadmin/frSeoContent.ts +165 -0
- package/template/server/superadmin/landingSeo.ts +675 -0
- package/template/server/superadmin/ops.ts +238 -0
- package/template/server/superadmin/routes.ts +1095 -0
- package/template/server/superadmin/tnSeoContent.ts +130 -0
- package/template/server.ts +522 -0
- package/template/src/App.tsx +263 -0
- package/template/src/admin/AdminApp.tsx +162 -0
- package/template/src/admin/account/AuthLayout.tsx +57 -0
- package/template/src/admin/account/SecurityForms.tsx +276 -0
- package/template/src/admin/admin.css +127 -0
- package/template/src/admin/context/AuthContext.tsx +147 -0
- package/template/src/admin/context/SiteContext.tsx +50 -0
- package/template/src/admin/editor/BlockLibrary.tsx +173 -0
- package/template/src/admin/editor/BlocksPanel.tsx +396 -0
- package/template/src/admin/editor/EditorBits.tsx +106 -0
- package/template/src/admin/editor/PreviewFrame.tsx +145 -0
- package/template/src/admin/editor/RichTextEditor.tsx +276 -0
- package/template/src/admin/editor/SeoPanel.tsx +349 -0
- package/template/src/admin/editor/StyleEditor.tsx +114 -0
- package/template/src/admin/editor/blockSchemas.ts +699 -0
- package/template/src/admin/editor/fields.tsx +486 -0
- package/template/src/admin/layout/AdminShell.tsx +418 -0
- package/template/src/admin/layout/nav.ts +95 -0
- package/template/src/admin/lib/api.ts +408 -0
- package/template/src/admin/lib/audit.ts +141 -0
- package/template/src/admin/lib/format.ts +84 -0
- package/template/src/admin/lib/hooks.ts +125 -0
- package/template/src/admin/main.tsx +16 -0
- package/template/src/admin/modules/InstallDialog.tsx +160 -0
- package/template/src/admin/modules/SettingsForm.tsx +145 -0
- package/template/src/admin/modules/kit.tsx +127 -0
- package/template/src/admin/modules/labels.tsx +226 -0
- package/template/src/admin/pages/AccountSetupPage.tsx +62 -0
- package/template/src/admin/pages/AnalyticsPage.tsx +206 -0
- package/template/src/admin/pages/BrandingPage.tsx +154 -0
- package/template/src/admin/pages/DashboardPage.tsx +239 -0
- package/template/src/admin/pages/EngagementPage.tsx +168 -0
- package/template/src/admin/pages/FormsPage.tsx +435 -0
- package/template/src/admin/pages/GeneralPage.tsx +120 -0
- package/template/src/admin/pages/LoginPage.tsx +249 -0
- package/template/src/admin/pages/MaintenancePage.tsx +125 -0
- package/template/src/admin/pages/MarketplacePage.tsx +426 -0
- package/template/src/admin/pages/MediaPage.tsx +251 -0
- package/template/src/admin/pages/MenusPage.tsx +446 -0
- package/template/src/admin/pages/ModuleAdminPage.tsx +113 -0
- package/template/src/admin/pages/ModuleDetailPage.tsx +293 -0
- package/template/src/admin/pages/ModulesPage.tsx +135 -0
- package/template/src/admin/pages/NewsletterPage.tsx +193 -0
- package/template/src/admin/pages/PageEditorPage.tsx +322 -0
- package/template/src/admin/pages/PagesListPage.tsx +277 -0
- package/template/src/admin/pages/PostEditorPage.tsx +292 -0
- package/template/src/admin/pages/PostsListPage.tsx +187 -0
- package/template/src/admin/pages/ProfilePage.tsx +489 -0
- package/template/src/admin/pages/ResetPasswordPage.tsx +110 -0
- package/template/src/admin/pages/SeoPage.tsx +670 -0
- package/template/src/admin/pages/SettingsPage.tsx +189 -0
- package/template/src/admin/pages/ThemeCustomizePage.tsx +170 -0
- package/template/src/admin/pages/ThemesPage.tsx +292 -0
- package/template/src/admin/pages/UsersPage.tsx +57 -0
- package/template/src/admin/pages/WidgetsPage.tsx +200 -0
- package/template/src/admin/seo/GoogleSetup.tsx +529 -0
- package/template/src/admin/settings/EmailTab.tsx +293 -0
- package/template/src/admin/settings/OtherTabs.tsx +270 -0
- package/template/src/admin/settings/SecurityTab.tsx +172 -0
- package/template/src/admin/settings/controls.tsx +133 -0
- package/template/src/admin/ui/Pagination.tsx +170 -0
- package/template/src/admin/ui/SaveBar.tsx +77 -0
- package/template/src/admin/ui/TagInput.tsx +54 -0
- package/template/src/admin/ui/charts.tsx +108 -0
- package/template/src/admin/ui/media.tsx +265 -0
- package/template/src/admin/ui/overlays.tsx +239 -0
- package/template/src/admin/ui/primitives.tsx +544 -0
- package/template/src/admin/users/AuditTab.tsx +104 -0
- package/template/src/admin/users/RolesTab.tsx +368 -0
- package/template/src/admin/users/UsersTab.tsx +723 -0
- package/template/src/admin/users/shared.tsx +278 -0
- package/template/src/cms/icons.ts +7 -0
- package/template/src/components/CookieBanner.tsx +166 -0
- package/template/src/components/MaintenanceGate.tsx +66 -0
- package/template/src/components/SiteAnalytics.tsx +64 -0
- package/template/src/components/SnapifyLogo.tsx +44 -0
- package/template/src/components/cms/BlockRenderer.tsx +154 -0
- package/template/src/components/cms/WidgetArea.tsx +193 -0
- package/template/src/context/SiteConfigContext.tsx +130 -0
- package/template/src/demoBase.ts +23 -0
- package/template/src/index.css +90 -0
- package/template/src/main.tsx +16 -0
- package/template/src/modules/ModuleHost.tsx +197 -0
- package/template/src/preview.ts +63 -0
- package/template/src/runtime.ts +38 -0
- package/template/src/themes/ThemeHost.tsx +168 -0
- package/template/src/themes/fallback.tsx +71 -0
- package/template/src/types.ts +94 -0
- package/template/src/views/BlogPostView.tsx +68 -0
- package/template/src/views/CmsPageView.tsx +34 -0
- package/template/start-pm2.sh +3 -0
- package/template/superadmin.html +20 -0
- package/template/themes/ai-canvas/screenshot.jpg +0 -0
- package/template/themes/ai-canvas/snapify.theme.json +96 -0
- package/template/themes/ai-canvas/src/CeeSimulator.tsx +247 -0
- package/template/themes/ai-canvas/src/Footer.tsx +190 -0
- package/template/themes/ai-canvas/src/Header.tsx +324 -0
- package/template/themes/ai-canvas/src/Layout.tsx +56 -0
- package/template/themes/ai-canvas/src/NotFound.tsx +58 -0
- package/template/themes/ai-canvas/src/Post.tsx +26 -0
- package/template/themes/ai-canvas/src/SnapifyLogo.tsx +44 -0
- package/template/themes/ai-canvas/src/blocks/CollectionBlocks.tsx +711 -0
- package/template/themes/ai-canvas/src/blocks/ContentBlocks.tsx +318 -0
- package/template/themes/ai-canvas/src/blocks/ConversionBlocks.tsx +559 -0
- package/template/themes/ai-canvas/src/blocks/HeaderBlocks.tsx +341 -0
- package/template/themes/ai-canvas/src/index.tsx +54 -0
- package/template/themes/ai-canvas/src/kit.tsx +398 -0
- package/template/themes/ai-canvas/src/site.ts +29 -0
- package/template/themes/ai-canvas/src/styles.css +317 -0
- package/template/themes/dogga/screenshot.jpg +0 -0
- package/template/themes/dogga/snapify.theme.json +55 -0
- package/template/themes/dogga/src/Brand.tsx +41 -0
- package/template/themes/dogga/src/Footer.tsx +202 -0
- package/template/themes/dogga/src/Header.tsx +239 -0
- package/template/themes/dogga/src/Layout.tsx +43 -0
- package/template/themes/dogga/src/Views.tsx +71 -0
- package/template/themes/dogga/src/blocks/CollectionBlocks.tsx +674 -0
- package/template/themes/dogga/src/blocks/ContentBlocks.tsx +307 -0
- package/template/themes/dogga/src/blocks/ConversionBlocks.tsx +571 -0
- package/template/themes/dogga/src/blocks/HeaderBlocks.tsx +289 -0
- package/template/themes/dogga/src/index.tsx +53 -0
- package/template/themes/dogga/src/kit.tsx +362 -0
- package/template/themes/dogga/src/site.ts +56 -0
- package/template/themes/dogga/src/styles.css +396 -0
- package/template/themes/mahdia/screenshot.jpg +0 -0
- package/template/themes/mahdia/snapify.theme.json +65 -0
- package/template/themes/mahdia/src/Brand.tsx +39 -0
- package/template/themes/mahdia/src/Footer.tsx +210 -0
- package/template/themes/mahdia/src/Header.tsx +291 -0
- package/template/themes/mahdia/src/Layout.tsx +44 -0
- package/template/themes/mahdia/src/Views.tsx +79 -0
- package/template/themes/mahdia/src/blocks/CollectionBlocks.tsx +674 -0
- package/template/themes/mahdia/src/blocks/ContentBlocks.tsx +307 -0
- package/template/themes/mahdia/src/blocks/ConversionBlocks.tsx +571 -0
- package/template/themes/mahdia/src/blocks/HeaderBlocks.tsx +289 -0
- package/template/themes/mahdia/src/index.tsx +53 -0
- package/template/themes/mahdia/src/kit.tsx +362 -0
- package/template/themes/mahdia/src/site.ts +75 -0
- package/template/themes/mahdia/src/styles.css +446 -0
- package/template/themes/preveris/screenshot.jpg +0 -0
- package/template/themes/preveris/snapify.theme.json +126 -0
- package/template/themes/preveris/src/Brand.tsx +44 -0
- package/template/themes/preveris/src/Footer.tsx +202 -0
- package/template/themes/preveris/src/Header.tsx +239 -0
- package/template/themes/preveris/src/Layout.tsx +42 -0
- package/template/themes/preveris/src/Views.tsx +71 -0
- package/template/themes/preveris/src/blocks/CollectionBlocks.tsx +678 -0
- package/template/themes/preveris/src/blocks/ContentBlocks.tsx +307 -0
- package/template/themes/preveris/src/blocks/ConversionBlocks.tsx +571 -0
- package/template/themes/preveris/src/blocks/HeaderBlocks.tsx +299 -0
- package/template/themes/preveris/src/index.tsx +53 -0
- package/template/themes/preveris/src/kit.tsx +365 -0
- package/template/themes/preveris/src/site.ts +79 -0
- package/template/themes/preveris/src/styles.css +446 -0
- package/template/themes/sidibou/screenshot.jpg +0 -0
- package/template/themes/sidibou/snapify.theme.json +21 -0
- package/template/themes/sidibou/src/CeeSimulator.tsx +247 -0
- package/template/themes/sidibou/src/Footer.tsx +203 -0
- package/template/themes/sidibou/src/Header.tsx +324 -0
- package/template/themes/sidibou/src/Layout.tsx +53 -0
- package/template/themes/sidibou/src/NotFound.tsx +58 -0
- package/template/themes/sidibou/src/Post.tsx +26 -0
- package/template/themes/sidibou/src/SnapifyLogo.tsx +44 -0
- package/template/themes/sidibou/src/blocks/CollectionBlocks.tsx +711 -0
- package/template/themes/sidibou/src/blocks/ContentBlocks.tsx +318 -0
- package/template/themes/sidibou/src/blocks/ConversionBlocks.tsx +559 -0
- package/template/themes/sidibou/src/blocks/HeaderBlocks.tsx +341 -0
- package/template/themes/sidibou/src/index.tsx +54 -0
- package/template/themes/sidibou/src/kit.tsx +398 -0
- package/template/themes/sidibou/src/site.ts +28 -0
- package/template/themes/sidibou/src/styles.css +317 -0
- package/template/tsconfig.json +33 -0
- package/template/vite.config.ts +42 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapify
|
|
3
|
+
* © 2026 NOVALABS WEB DESIGN — Tunisie
|
|
4
|
+
* Contact : contact@novalabs.tn · +216 24 734 647
|
|
5
|
+
* Tous droits réservés. Toute reproduction, modification ou diffusion sans autorisation écrite est interdite.
|
|
6
|
+
*/
|
|
7
|
+
import { publicTheme } from './manager';
|
|
8
|
+
|
|
9
|
+
const attr = (v: string) => v.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Feuille de style et script du thème actif annoncés dès le HTML : pas de flash sans style.
|
|
13
|
+
* Le descripteur est aussi inclus, car le HTML n'est jamais mis en cache alors que
|
|
14
|
+
* /api/public/site-config peut l'être quelques secondes après un changement de thème.
|
|
15
|
+
*/
|
|
16
|
+
export async function injectTheme(html: string): Promise<string> {
|
|
17
|
+
try {
|
|
18
|
+
const theme = await publicTheme();
|
|
19
|
+
if (!theme) return html;
|
|
20
|
+
const json = JSON.stringify(theme).replace(/</g, '\\u003c');
|
|
21
|
+
const tags = [
|
|
22
|
+
theme.style ? `<link rel="stylesheet" href="${attr(theme.style)}" data-snapify-theme="${attr(theme.id)}" />` : '',
|
|
23
|
+
`<link rel="modulepreload" href="${attr(theme.script)}" />`,
|
|
24
|
+
`<script type="application/json" id="snapify-theme">${json}</script>`,
|
|
25
|
+
]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join('\n ');
|
|
28
|
+
return html.replace('</head>', ` ${tags}\n </head>`);
|
|
29
|
+
} catch {
|
|
30
|
+
return html;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapify
|
|
3
|
+
* © 2026 NOVALABS WEB DESIGN — Tunisie
|
|
4
|
+
* Contact : contact@novalabs.tn · +216 24 734 647
|
|
5
|
+
* Tous droits réservés. Toute reproduction, modification ou diffusion sans autorisation écrite est interdite.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Clés publiques NOVALABS acceptées pour la signature des modules et des licences
|
|
9
|
+
* (identifiant de clé → clé SPKI Ed25519 en base64). Ajouter une clé lors d'une rotation,
|
|
10
|
+
* retirer l'ancienne une fois tous les paquets re-signés.
|
|
11
|
+
*/
|
|
12
|
+
export const TRUSTED_KEYS: Readonly<Record<string, string>> = Object.freeze({
|
|
13
|
+
'novalabs-2026': 'MCowBQYDK2VwAyEAJIWXFiaxzBQe042IMZUo/IGiKLGtTf7XBywgS6cpHWs=',
|
|
14
|
+
/** Clé locale de développement (signature de nouveaux paquets sur cet environnement). */
|
|
15
|
+
'novalabs-local-2026': 'MCowBQYDK2VwAyEAAp4iufU2icDH3Lh7+dm5aQIxm+/jH26ztpg27EZPOLo=',
|
|
16
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapify
|
|
3
|
+
* © 2026 NOVALABS WEB DESIGN — Tunisie
|
|
4
|
+
* Contact : contact@novalabs.tn · +216 24 734 647
|
|
5
|
+
* Tous droits réservés. Toute reproduction, modification ou diffusion sans autorisation écrite est interdite.
|
|
6
|
+
*/
|
|
7
|
+
/** Données échangées entre le gestionnaire de modules et l'administration. */
|
|
8
|
+
import type { AdminPageDefinition, ModuleManifest, ModuleSlot, ModuleStatusReport, PackageKind, SettingField, WidgetDefinition } from '../../sdk/index';
|
|
9
|
+
|
|
10
|
+
/** Module actif tel que transmis au site public (réglages publics uniquement). */
|
|
11
|
+
export interface PublicModuleDescriptor {
|
|
12
|
+
id: string;
|
|
13
|
+
version: string;
|
|
14
|
+
slots: ModuleSlot[];
|
|
15
|
+
/** Adresses affichées par le composant `page` du module. */
|
|
16
|
+
routes: string[];
|
|
17
|
+
/** Identifiants des widgets du bloc « Module ». */
|
|
18
|
+
widgets: string[];
|
|
19
|
+
script: string;
|
|
20
|
+
style?: string;
|
|
21
|
+
settings: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Thème appliqué au site (ou prévisualisé dans l'administration). */
|
|
25
|
+
export interface PublicThemeDescriptor {
|
|
26
|
+
id: string;
|
|
27
|
+
version: string;
|
|
28
|
+
script: string;
|
|
29
|
+
style?: string;
|
|
30
|
+
settings: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ModuleStatusCode =
|
|
34
|
+
| 'active'
|
|
35
|
+
| 'disabled'
|
|
36
|
+
| 'unsigned'
|
|
37
|
+
| 'incompatible'
|
|
38
|
+
| 'tampered'
|
|
39
|
+
| 'license_missing'
|
|
40
|
+
| 'license_invalid'
|
|
41
|
+
| 'license_expired'
|
|
42
|
+
| 'license_domain'
|
|
43
|
+
| 'dependency'
|
|
44
|
+
| 'error';
|
|
45
|
+
|
|
46
|
+
export type LicenseState = 'valid' | 'missing' | 'invalid' | 'expired' | 'domain' | 'dev';
|
|
47
|
+
|
|
48
|
+
export interface ModuleLicenseInfo {
|
|
49
|
+
state: LicenseState;
|
|
50
|
+
message: string;
|
|
51
|
+
licenseId?: string;
|
|
52
|
+
licensee?: string;
|
|
53
|
+
plan?: string;
|
|
54
|
+
domains?: string[];
|
|
55
|
+
expiresAt?: string | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ModuleSignatureInfo {
|
|
59
|
+
valid: boolean;
|
|
60
|
+
kid?: string;
|
|
61
|
+
/** Module non signé accepté parce que le mode développeur est actif. */
|
|
62
|
+
devUnsigned: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ModuleSummary = Pick<
|
|
66
|
+
ModuleManifest,
|
|
67
|
+
'id' | 'name' | 'version' | 'description' | 'author' | 'pricing' | 'category' | 'icon' | 'permissions' | 'slots'
|
|
68
|
+
>;
|
|
69
|
+
|
|
70
|
+
export interface InstalledModuleInfo extends ModuleSummary {
|
|
71
|
+
kind: PackageKind;
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
source: 'marketplace' | 'upload';
|
|
74
|
+
installedAt: string;
|
|
75
|
+
updatedAt: string;
|
|
76
|
+
status: { code: ModuleStatusCode; message: string };
|
|
77
|
+
signature: ModuleSignatureInfo;
|
|
78
|
+
license: ModuleLicenseInfo;
|
|
79
|
+
hasSettings: boolean;
|
|
80
|
+
hasServer: boolean;
|
|
81
|
+
routes: string[];
|
|
82
|
+
widgets: WidgetDefinition[];
|
|
83
|
+
adminPages: AdminPageDefinition[];
|
|
84
|
+
requires: Record<string, string>;
|
|
85
|
+
/** Bundle des pages d'administration, si le module est actif. */
|
|
86
|
+
admin: { script: string; style?: string } | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface InstalledModuleDetail extends InstalledModuleInfo {
|
|
90
|
+
settingsSchema: SettingField[];
|
|
91
|
+
settings: Record<string, unknown>;
|
|
92
|
+
report: ModuleStatusReport | null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface InstalledThemeInfo extends InstalledModuleInfo {
|
|
96
|
+
active: boolean;
|
|
97
|
+
screenshot?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface InstalledThemeDetail extends InstalledThemeInfo {
|
|
101
|
+
settingsSchema: SettingField[];
|
|
102
|
+
settings: Record<string, unknown>;
|
|
103
|
+
/** Fichiers du thème pour l'aperçu, si ses vérifications sont satisfaites. */
|
|
104
|
+
descriptor: PublicThemeDescriptor | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface CatalogEntry extends ModuleSummary {
|
|
108
|
+
kind?: PackageKind;
|
|
109
|
+
/** Chemin relatif au catalogue ; résolu en URL dans la liste renvoyée à l'administration. */
|
|
110
|
+
screenshot?: string;
|
|
111
|
+
/** Captures supplémentaires (chemins relatifs), résolues en URL côté listing. */
|
|
112
|
+
gallery?: string[];
|
|
113
|
+
/** Prix en millimes pour les modules `paid` (ex. 49000 = 49 DT). */
|
|
114
|
+
priceMillimes?: number;
|
|
115
|
+
engines: { snapify: string };
|
|
116
|
+
package: string;
|
|
117
|
+
sha256: string;
|
|
118
|
+
size: number;
|
|
119
|
+
signed: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface MarketplaceListing {
|
|
123
|
+
source: 'local' | 'remote';
|
|
124
|
+
url?: string;
|
|
125
|
+
error?: string;
|
|
126
|
+
devMode: boolean;
|
|
127
|
+
modules: (CatalogEntry & {
|
|
128
|
+
installedVersion: string | null;
|
|
129
|
+
updateAvailable: boolean;
|
|
130
|
+
compatible: boolean;
|
|
131
|
+
priceLabel?: string;
|
|
132
|
+
purchased?: boolean;
|
|
133
|
+
})[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Paquet vérifié en attente de confirmation par l'administrateur. */
|
|
137
|
+
export interface StagedPackageInfo {
|
|
138
|
+
token: string;
|
|
139
|
+
manifest: ModuleSummary & { kind: PackageKind; engines: { snapify: string }; hasServer: boolean; settingsCount: number; screenshot?: string };
|
|
140
|
+
signature: ModuleSignatureInfo;
|
|
141
|
+
license: ModuleLicenseInfo;
|
|
142
|
+
compatible: boolean;
|
|
143
|
+
installedVersion: string | null;
|
|
144
|
+
/** `install`, `update`, `reinstall` ou `downgrade`. */
|
|
145
|
+
action: 'install' | 'update' | 'reinstall' | 'downgrade';
|
|
146
|
+
blockers: string[];
|
|
147
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapify
|
|
3
|
+
* © 2026 NOVALABS WEB DESIGN — Tunisie
|
|
4
|
+
* Contact : contact@novalabs.tn · +216 24 734 647
|
|
5
|
+
* Tous droits réservés. Toute reproduction, modification ou diffusion sans autorisation écrite est interdite.
|
|
6
|
+
*/
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
9
|
+
import { and, eq, gt, isNull } from 'drizzle-orm';
|
|
10
|
+
import { db } from '../db/client';
|
|
11
|
+
import { platformSessions, platformUsers } from '../db/schema';
|
|
12
|
+
import { HttpError } from '../http';
|
|
13
|
+
import { clientIp } from '../security/ip';
|
|
14
|
+
import { randomToken, sha256 } from '../security/secrets';
|
|
15
|
+
import { hashPassword, validatePlatformPassword, verifyPassword } from './passwords';
|
|
16
|
+
import { platformCookieDomainForHost } from './region';
|
|
17
|
+
|
|
18
|
+
export type PlatformUser = typeof platformUsers.$inferSelect;
|
|
19
|
+
export type PublicPlatformUser = {
|
|
20
|
+
id: string;
|
|
21
|
+
email: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
avatarUrl: string | null;
|
|
24
|
+
bio: string;
|
|
25
|
+
role: PlatformUser['role'];
|
|
26
|
+
createdAt: Date;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const COOKIE = 'sf_platform';
|
|
30
|
+
const SESSION_DAYS = 30;
|
|
31
|
+
const IDLE_MS = 14 * 24 * 3600_000;
|
|
32
|
+
|
|
33
|
+
declare global {
|
|
34
|
+
namespace Express {
|
|
35
|
+
interface Request {
|
|
36
|
+
platformUser?: PlatformUser;
|
|
37
|
+
platformSessionId?: string;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function toPublicUser(u: PlatformUser): PublicPlatformUser {
|
|
43
|
+
return {
|
|
44
|
+
id: u.id,
|
|
45
|
+
email: u.email,
|
|
46
|
+
displayName: u.displayName,
|
|
47
|
+
avatarUrl: u.avatarUrl,
|
|
48
|
+
bio: u.bio,
|
|
49
|
+
role: u.role,
|
|
50
|
+
createdAt: u.createdAt,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cookieDomain(req: Request): string | undefined {
|
|
55
|
+
const xf = String(req.headers['x-forwarded-host'] || '')
|
|
56
|
+
.split(',')[0]
|
|
57
|
+
.trim();
|
|
58
|
+
const host = (xf || String(req.headers.host || '')).split(':')[0].toLowerCase();
|
|
59
|
+
return platformCookieDomainForHost(host) || process.env.PLATFORM_COOKIE_DOMAIN?.trim() || undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setSessionCookie(req: Request, res: Response, sessionId: string, expiresAt: Date) {
|
|
63
|
+
res.cookie(COOKIE, sessionId, {
|
|
64
|
+
httpOnly: true,
|
|
65
|
+
secure: process.env.NODE_ENV === 'production',
|
|
66
|
+
sameSite: 'lax',
|
|
67
|
+
domain: cookieDomain(req),
|
|
68
|
+
path: '/',
|
|
69
|
+
expires: expiresAt,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function clearSessionCookie(req: Request, res: Response) {
|
|
74
|
+
res.clearCookie(COOKIE, { path: '/', domain: cookieDomain(req) });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readCookie(req: Request): string | null {
|
|
78
|
+
const header = req.headers.cookie || '';
|
|
79
|
+
const match = header.match(/(?:^|;\s*)sf_platform=([^;]+)/);
|
|
80
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function createPlatformUser(input: {
|
|
84
|
+
email: string;
|
|
85
|
+
password: string;
|
|
86
|
+
displayName: string;
|
|
87
|
+
}): Promise<PlatformUser> {
|
|
88
|
+
const email = input.email.trim().toLowerCase();
|
|
89
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) throw new HttpError(422, 'Adresse e-mail invalide.');
|
|
90
|
+
const err = validatePlatformPassword(input.password);
|
|
91
|
+
if (err) throw new HttpError(422, err);
|
|
92
|
+
const displayName = input.displayName.trim().slice(0, 80);
|
|
93
|
+
if (displayName.length < 2) throw new HttpError(422, 'Le nom affiché est trop court.');
|
|
94
|
+
|
|
95
|
+
const existing = await db.select({ id: platformUsers.id }).from(platformUsers).where(eq(platformUsers.email, email)).limit(1);
|
|
96
|
+
if (existing.length) throw new HttpError(409, 'Un compte existe déjà avec cet e-mail.');
|
|
97
|
+
|
|
98
|
+
const { hash, salt } = hashPassword(input.password);
|
|
99
|
+
const id = crypto.randomUUID();
|
|
100
|
+
const [user] = await db
|
|
101
|
+
.insert(platformUsers)
|
|
102
|
+
.values({
|
|
103
|
+
id,
|
|
104
|
+
email,
|
|
105
|
+
displayName,
|
|
106
|
+
passwordHash: hash,
|
|
107
|
+
salt,
|
|
108
|
+
role: 'member',
|
|
109
|
+
})
|
|
110
|
+
.returning();
|
|
111
|
+
return user;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function loginPlatformUser(
|
|
115
|
+
req: Request,
|
|
116
|
+
res: Response,
|
|
117
|
+
email: string,
|
|
118
|
+
password: string
|
|
119
|
+
): Promise<PlatformUser> {
|
|
120
|
+
const normalized = email.trim().toLowerCase();
|
|
121
|
+
const [user] = await db.select().from(platformUsers).where(eq(platformUsers.email, normalized)).limit(1);
|
|
122
|
+
if (!user || !verifyPassword(password, user.passwordHash, user.salt)) {
|
|
123
|
+
throw new HttpError(401, 'E-mail ou mot de passe incorrect.');
|
|
124
|
+
}
|
|
125
|
+
const sessionId = randomToken(32);
|
|
126
|
+
const expiresAt = new Date(Date.now() + SESSION_DAYS * 86400_000);
|
|
127
|
+
await db.insert(platformSessions).values({
|
|
128
|
+
id: sessionId,
|
|
129
|
+
userId: user.id,
|
|
130
|
+
expiresAt,
|
|
131
|
+
ip: clientIp(req),
|
|
132
|
+
userAgent: String(req.headers['user-agent'] || '').slice(0, 300),
|
|
133
|
+
});
|
|
134
|
+
setSessionCookie(req, res, sessionId, expiresAt);
|
|
135
|
+
return user;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function logoutPlatformUser(req: Request, res: Response) {
|
|
139
|
+
const sid = readCookie(req);
|
|
140
|
+
if (sid) {
|
|
141
|
+
await db.update(platformSessions).set({ revokedAt: new Date() }).where(eq(platformSessions.id, sid));
|
|
142
|
+
}
|
|
143
|
+
clearSessionCookie(req, res);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function loadPlatformUser(req: Request): Promise<PlatformUser | null> {
|
|
147
|
+
const sid = readCookie(req);
|
|
148
|
+
if (!sid) return null;
|
|
149
|
+
const [session] = await db
|
|
150
|
+
.select()
|
|
151
|
+
.from(platformSessions)
|
|
152
|
+
.where(and(eq(platformSessions.id, sid), isNull(platformSessions.revokedAt), gt(platformSessions.expiresAt, new Date())))
|
|
153
|
+
.limit(1);
|
|
154
|
+
if (!session) return null;
|
|
155
|
+
if (Date.now() - session.lastSeenAt.getTime() > IDLE_MS) {
|
|
156
|
+
await db.update(platformSessions).set({ revokedAt: new Date() }).where(eq(platformSessions.id, sid));
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const [user] = await db.select().from(platformUsers).where(eq(platformUsers.id, session.userId)).limit(1);
|
|
160
|
+
if (!user) return null;
|
|
161
|
+
await db.update(platformSessions).set({ lastSeenAt: new Date() }).where(eq(platformSessions.id, sid));
|
|
162
|
+
req.platformSessionId = sid;
|
|
163
|
+
return user;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function platformAuthOptional(req: Request, _res: Response, next: NextFunction) {
|
|
167
|
+
try {
|
|
168
|
+
req.platformUser = (await loadPlatformUser(req)) || undefined;
|
|
169
|
+
next();
|
|
170
|
+
} catch (e) {
|
|
171
|
+
next(e);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function requirePlatformUser(req: Request, res: Response, next: NextFunction) {
|
|
176
|
+
try {
|
|
177
|
+
const user = await loadPlatformUser(req);
|
|
178
|
+
if (!user) throw new HttpError(401, 'Connexion requise.');
|
|
179
|
+
req.platformUser = user;
|
|
180
|
+
next();
|
|
181
|
+
} catch (e) {
|
|
182
|
+
next(e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function requireRole(...roles: PlatformUser['role'][]) {
|
|
187
|
+
return (req: Request, _res: Response, next: NextFunction) => {
|
|
188
|
+
const u = req.platformUser;
|
|
189
|
+
if (!u) return next(new HttpError(401, 'Connexion requise.'));
|
|
190
|
+
if (!roles.includes(u.role) && u.role !== 'admin') return next(new HttpError(403, 'Accès refusé.'));
|
|
191
|
+
next();
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function createResetToken(userId: string): Promise<string> {
|
|
196
|
+
const raw = randomToken(32);
|
|
197
|
+
const { platformAuthTokens } = await import('../db/schema');
|
|
198
|
+
await db.insert(platformAuthTokens).values({
|
|
199
|
+
id: crypto.randomUUID(),
|
|
200
|
+
userId,
|
|
201
|
+
kind: 'reset',
|
|
202
|
+
tokenHash: sha256(raw),
|
|
203
|
+
expiresAt: new Date(Date.now() + 3600_000),
|
|
204
|
+
});
|
|
205
|
+
return raw;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function consumeResetToken(raw: string, newPassword: string): Promise<void> {
|
|
209
|
+
const err = validatePlatformPassword(newPassword);
|
|
210
|
+
if (err) throw new HttpError(422, err);
|
|
211
|
+
const { platformAuthTokens } = await import('../db/schema');
|
|
212
|
+
const hash = sha256(raw);
|
|
213
|
+
const [tok] = await db
|
|
214
|
+
.select()
|
|
215
|
+
.from(platformAuthTokens)
|
|
216
|
+
.where(and(eq(platformAuthTokens.tokenHash, hash), eq(platformAuthTokens.kind, 'reset'), isNull(platformAuthTokens.usedAt), gt(platformAuthTokens.expiresAt, new Date())))
|
|
217
|
+
.limit(1);
|
|
218
|
+
if (!tok) throw new HttpError(400, 'Lien de réinitialisation invalide ou expiré.');
|
|
219
|
+
const { hash: passwordHash, salt } = hashPassword(newPassword);
|
|
220
|
+
await db.update(platformUsers).set({ passwordHash, salt, updatedAt: new Date() }).where(eq(platformUsers.id, tok.userId));
|
|
221
|
+
await db.update(platformAuthTokens).set({ usedAt: new Date() }).where(eq(platformAuthTokens.id, tok.id));
|
|
222
|
+
await db.update(platformSessions).set({ revokedAt: new Date() }).where(eq(platformSessions.userId, tok.userId));
|
|
223
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapify — création compte client + e-mail de bienvenue (charte Snapify)
|
|
3
|
+
* © 2026 NOVALABS WEB DESIGN — Tunisie
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import { eq } from 'drizzle-orm';
|
|
7
|
+
import { db } from '../db/client';
|
|
8
|
+
import { platformUsers } from '../db/schema';
|
|
9
|
+
import { escapeHtml, renderEmail, sendMail } from '../mail';
|
|
10
|
+
import { loadSnapshot } from '../cms/store';
|
|
11
|
+
import { createPlatformUser } from './auth';
|
|
12
|
+
import { hashPassword } from './passwords';
|
|
13
|
+
import type { LicenseOrder } from './licenseOrders';
|
|
14
|
+
import type { ClientProfile } from './clients';
|
|
15
|
+
|
|
16
|
+
function generateClientPassword(): string {
|
|
17
|
+
const raw = crypto.randomBytes(9).toString('base64url').replace(/[^a-zA-Z0-9]/g, '');
|
|
18
|
+
// Lettre + chiffre obligatoires (règle plateforme)
|
|
19
|
+
return `Sf${raw.slice(0, 8)}7`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function clientPortalUrl(market: 'tn' | 'fr'): string {
|
|
23
|
+
if (process.env.CLIENT_PUBLIC_URL) return process.env.CLIENT_PUBLIC_URL.replace(/\/$/, '');
|
|
24
|
+
// Pas de sous-domaine : espace client sur le domaine principal
|
|
25
|
+
return market === 'fr' ? 'https://snapify.fr/espace-client' : 'https://snapify.tn/espace-client';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function snapifyMailBrand() {
|
|
29
|
+
const s = await loadSnapshot();
|
|
30
|
+
return {
|
|
31
|
+
...s,
|
|
32
|
+
branding: {
|
|
33
|
+
...s.branding,
|
|
34
|
+
siteName: 'Snapify',
|
|
35
|
+
colors: { ...(s.branding.colors || {}), primary: '#00a89d' },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Crée le compte plateforme si besoin ; renvoie le mot de passe en clair uniquement à la création. */
|
|
41
|
+
export async function ensureClientPlatformAccount(order: LicenseOrder): Promise<{
|
|
42
|
+
userId: string;
|
|
43
|
+
email: string;
|
|
44
|
+
password: string | null;
|
|
45
|
+
created: boolean;
|
|
46
|
+
}> {
|
|
47
|
+
const email = order.email.trim().toLowerCase();
|
|
48
|
+
const [existing] = await db.select().from(platformUsers).where(eq(platformUsers.email, email)).limit(1);
|
|
49
|
+
if (existing) {
|
|
50
|
+
return { userId: existing.id, email, password: null, created: false };
|
|
51
|
+
}
|
|
52
|
+
const password = generateClientPassword();
|
|
53
|
+
try {
|
|
54
|
+
const user = await createPlatformUser({
|
|
55
|
+
email,
|
|
56
|
+
password,
|
|
57
|
+
displayName: order.name.slice(0, 80) || email.split('@')[0],
|
|
58
|
+
});
|
|
59
|
+
return { userId: user.id, email, password, created: true };
|
|
60
|
+
} catch (e: unknown) {
|
|
61
|
+
// Course : compte créé entre-temps
|
|
62
|
+
const [again] = await db.select().from(platformUsers).where(eq(platformUsers.email, email)).limit(1);
|
|
63
|
+
if (again) return { userId: again.id, email, password: null, created: false };
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function sendClientWelcomeEmail(opts: {
|
|
69
|
+
name: string;
|
|
70
|
+
email: string;
|
|
71
|
+
password: string | null;
|
|
72
|
+
domain: string;
|
|
73
|
+
market: 'tn' | 'fr';
|
|
74
|
+
created: boolean;
|
|
75
|
+
}): Promise<{ sent: boolean; reason?: string }> {
|
|
76
|
+
const portal = clientPortalUrl(opts.market);
|
|
77
|
+
const brand = await snapifyMailBrand();
|
|
78
|
+
const first = opts.name.trim().split(/\s+/)[0] || 'bonjour';
|
|
79
|
+
|
|
80
|
+
const credHtml = opts.created && opts.password
|
|
81
|
+
? `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:18px 0;background:#f0fdfa;border:1px solid #99f6e4;border-radius:12px">
|
|
82
|
+
<tr><td style="padding:16px 18px">
|
|
83
|
+
<p style="margin:0 0 8px;font-size:13px;color:#0f766e;font-weight:700;text-transform:uppercase;letter-spacing:.04em">Vos identifiants</p>
|
|
84
|
+
<p style="margin:0 0 6px;font-size:15px;color:#0f172a"><strong>E-mail :</strong> ${escapeHtml(opts.email)}</p>
|
|
85
|
+
<p style="margin:0;font-size:15px;color:#0f172a"><strong>Mot de passe :</strong> <code style="font-size:15px;background:#fff;padding:4px 8px;border-radius:6px;border:1px solid #ccfbf1">${escapeHtml(opts.password)}</code></p>
|
|
86
|
+
</td></tr></table>`
|
|
87
|
+
: `<p style="margin:0 0 14px;font-size:15px;line-height:1.6;color:#334155">Connectez-vous avec votre e-mail <strong>${escapeHtml(opts.email)}</strong> et le mot de passe de votre compte Snapify existant.</p>`;
|
|
88
|
+
|
|
89
|
+
const { html, text } = renderEmail(brand, {
|
|
90
|
+
title: opts.created ? 'Bienvenue sur Snapify' : 'Votre demande Snapify est enregistrée',
|
|
91
|
+
intro: `Bonjour ${first}, merci pour votre demande concernant ${opts.domain}. Votre espace client est prêt : suivez l’avancement du projet, les paiements, les factures et le support.`,
|
|
92
|
+
lines: opts.created
|
|
93
|
+
? [
|
|
94
|
+
'Conservez cet e-mail : il contient vos identifiants de connexion.',
|
|
95
|
+
'Vous pourrez changer le mot de passe depuis Mon profil une fois connecté(e).',
|
|
96
|
+
]
|
|
97
|
+
: ['Votre demande a été ajoutée à votre espace client existant.'],
|
|
98
|
+
html: credHtml,
|
|
99
|
+
button: { label: 'Accéder à mon espace client', url: portal },
|
|
100
|
+
outro: 'Besoin d’aide ? Répondez à cet e-mail ou ouvrez un ticket depuis l’espace client. — L’équipe Snapify · NOVALABS',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const subject = opts.created
|
|
104
|
+
? `Bienvenue sur Snapify — vos accès (${opts.domain})`
|
|
105
|
+
: `Snapify — demande enregistrée (${opts.domain})`;
|
|
106
|
+
|
|
107
|
+
return sendMail({ to: opts.email, subject, html, text, kind: 'invite' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Après création / màj fiche : provisionne le compte + envoie le mail de bienvenue
|
|
112
|
+
* (mot de passe en clair uniquement si le compte vient d’être créé).
|
|
113
|
+
*/
|
|
114
|
+
export async function provisionClientAndWelcome(
|
|
115
|
+
profile: ClientProfile,
|
|
116
|
+
order: LicenseOrder,
|
|
117
|
+
opts: { market?: 'tn' | 'fr'; forceWelcome?: boolean } = {}
|
|
118
|
+
): Promise<{ mailed: boolean; createdAccount: boolean }> {
|
|
119
|
+
const market = opts.market || profile.market || 'tn';
|
|
120
|
+
const account = await ensureClientPlatformAccount(order);
|
|
121
|
+
|
|
122
|
+
// Lier la fiche si besoin
|
|
123
|
+
if (profile.platformUserId !== account.userId) {
|
|
124
|
+
const { clientProfiles } = await import('../db/schema');
|
|
125
|
+
await db
|
|
126
|
+
.update(clientProfiles)
|
|
127
|
+
.set({ platformUserId: account.userId, updatedAt: new Date() })
|
|
128
|
+
.where(eq(clientProfiles.id, profile.id));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Éviter le spam : mail avec MDP seulement à la création ; sinon ack une fois par « première » demande
|
|
132
|
+
const shouldMail = account.created || opts.forceWelcome || (profile.licenseOrderIds?.length || 0) <= 1;
|
|
133
|
+
if (!shouldMail) return { mailed: false, createdAccount: account.created };
|
|
134
|
+
|
|
135
|
+
// Si compte existant et déjà plus d’une demande, pas de re-mail sauf force
|
|
136
|
+
if (!account.created && (profile.licenseOrderIds?.length || 0) > 1 && !opts.forceWelcome) {
|
|
137
|
+
return { mailed: false, createdAccount: false };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const result = await sendClientWelcomeEmail({
|
|
141
|
+
name: order.name || profile.name,
|
|
142
|
+
email: account.email,
|
|
143
|
+
password: account.password,
|
|
144
|
+
domain: order.domain || profile.domain,
|
|
145
|
+
market,
|
|
146
|
+
created: account.created,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return { mailed: result.sent, createdAccount: account.created };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Utilisé si on doit régénérer un MDP (superadmin relance) — non exposé public. */
|
|
153
|
+
export async function resetClientPasswordAndMail(email: string, market: 'tn' | 'fr' = 'tn') {
|
|
154
|
+
const e = email.trim().toLowerCase();
|
|
155
|
+
const [user] = await db.select().from(platformUsers).where(eq(platformUsers.email, e)).limit(1);
|
|
156
|
+
if (!user) throw new Error('Compte introuvable');
|
|
157
|
+
const password = generateClientPassword();
|
|
158
|
+
const { hash, salt } = hashPassword(password);
|
|
159
|
+
await db.update(platformUsers).set({ passwordHash: hash, salt, updatedAt: new Date() }).where(eq(platformUsers.id, user.id));
|
|
160
|
+
return sendClientWelcomeEmail({
|
|
161
|
+
name: user.displayName,
|
|
162
|
+
email: e,
|
|
163
|
+
password,
|
|
164
|
+
domain: 'votre projet',
|
|
165
|
+
market,
|
|
166
|
+
created: true,
|
|
167
|
+
});
|
|
168
|
+
}
|