├── .gitattributes ├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ ├── blog-deployment.yml │ └── e2e-tests.yml ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── opensaas-sh ├── .gitignore ├── README.md ├── app_diff │ ├── .env.client.diff │ ├── .env.vault.diff │ ├── .gitignore.diff │ ├── README.md.diff │ ├── deletions │ ├── fly-client.toml.diff │ ├── fly-server.toml.diff │ ├── main.wasp.diff │ ├── migrations │ │ ├── 20231213174854_init │ │ │ └── migration.sql.diff │ │ ├── 20240105224550_tasks │ │ │ └── migration.sql.diff │ │ ├── 20240207164719_files │ │ │ └── migration.sql.diff │ │ ├── 20240226123357_new_auth_structure │ │ │ └── migration.sql.diff │ │ ├── 20240226130234_remove_old_auth_structure │ │ │ └── migration.sql.diff │ │ ├── 20240226145340_remove_unique_email_username │ │ │ └── migration.sql.diff │ │ ├── 20240605151848_remove_has_paid │ │ │ └── migration.sql.diff │ │ ├── 20240702143707_update_user_entity │ │ │ └── migration.sql.diff │ │ ├── 20240715142249_version_14 │ │ │ └── migration.sql.diff │ │ ├── 20241126132514_remove_checkout_session_id │ │ │ └── migration.sql.diff │ │ ├── 20250220095333_remove_last_active_timestamp │ │ │ └── migration.sql.diff │ │ └── migration_lock.toml.diff │ ├── package-lock.json.diff │ ├── package.json.diff │ ├── public │ │ └── piggy.js.diff │ ├── schema.prisma.diff │ ├── src │ │ ├── admin │ │ │ └── dashboards │ │ │ │ ├── analytics │ │ │ │ └── AnalyticsDashboardPage.tsx.diff │ │ │ │ └── users │ │ │ │ ├── UsersDashboardPage.tsx.diff │ │ │ │ └── UsersTable.tsx.diff │ │ ├── analytics │ │ │ ├── providers │ │ │ │ └── plausibleAnalyticsUtils.ts.diff │ │ │ └── stats.ts.diff │ │ ├── auth │ │ │ ├── LoginPage.tsx.diff │ │ │ └── userSignupFields.ts.diff │ │ ├── client │ │ │ └── components │ │ │ │ ├── NavBar │ │ │ │ └── NavBar.tsx.diff │ │ │ │ └── cookie-consent │ │ │ │ └── Config.ts.diff │ │ ├── file-upload │ │ │ └── operations.ts.diff │ │ ├── landing-page │ │ │ ├── components │ │ │ │ ├── Clients.tsx.diff │ │ │ │ ├── FAQ.tsx.diff │ │ │ │ ├── Features.tsx.diff │ │ │ │ ├── Footer.tsx.diff │ │ │ │ └── Hero.tsx.diff │ │ │ └── contentSections.ts.diff │ │ ├── payment │ │ │ ├── PricingPage.tsx.diff │ │ │ ├── paymentProcessor.ts.diff │ │ │ └── stripe │ │ │ │ ├── paymentDetails.ts.diff │ │ │ │ └── paymentProcessor.ts.diff │ │ ├── server │ │ │ └── scripts │ │ │ │ └── dbSeeds.ts.diff │ │ ├── shared │ │ │ └── common.ts.diff │ │ └── user │ │ │ └── operations.ts.diff │ └── tailwind.config.cjs.diff ├── blog │ ├── .gitignore │ ├── .vscode │ │ ├── extensions.json │ │ └── launch.json │ ├── README.md │ ├── astro.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ │ ├── CRAIG_ROCK.png │ │ ├── banner-images │ │ │ ├── 2023-11-21-coverlettergpt.webp │ │ │ ├── 2024-10-10-most-annoying-cookie-banner-contest.webp │ │ │ ├── 2024-11-22-best-annoying-cookie-consent-banners.webp │ │ │ ├── 2024-12-04-open-source-saas-boilerplate-vs-paid.webp │ │ │ ├── 2024-12-10-turboreel-os-ai-video-generator-built-with-open-saas.webp │ │ │ ├── 2024-12-16-my-gpt-wrapper.webp │ │ │ ├── 2025-01-30-from-0-to-400-customers-saas-growth-hacks.webp │ │ │ ├── 2025-02-26-incident-report-vulnerability-in-open-saas.webp │ │ │ ├── 2025-02-27-meet-marko-saric-co-founder-of-privacy-friendly-plausible-analytics.webp │ │ │ ├── 2025-03-12-going-from-an-idea-to-mvp-in-weeks-promptpandas-launches.webp │ │ │ ├── 2025-05-07-you-should-still-launch-your-product-on-ph.webp │ │ │ ├── 2025-05-21-saas-cost-marketing-breakdown.webp │ │ │ ├── README.md │ │ │ └── opensaas.webp │ │ ├── favicon.svg │ │ ├── llms-full.txt │ │ ├── llms.txt │ │ ├── martin.jpg │ │ ├── matija.jpeg │ │ ├── milica.jpg │ │ └── piggy.js │ ├── scripts │ │ └── generate-llm-files.mjs │ ├── src │ │ ├── assets │ │ │ ├── admin │ │ │ │ └── admin-dashboard.png │ │ │ ├── boilerplate-starters │ │ │ │ ├── boilerplate-licenses.png │ │ │ │ ├── community-contributions.png │ │ │ │ ├── dave-shipfast-tweet.png │ │ │ │ ├── free-updates-vs-not.png │ │ │ │ ├── marc1.png │ │ │ │ ├── marc2.png │ │ │ │ ├── opensaas.png │ │ │ │ ├── os-commits.png │ │ │ │ └── os-gh-stats.png │ │ │ ├── cookie-banner-hackathon │ │ │ │ ├── 285-3umaGH-gangnam.mp4 │ │ │ │ ├── 286-wardbox.mp4 │ │ │ │ ├── 295-camblackwood.mp4 │ │ │ │ ├── 296-henryboyd.mp4 │ │ │ │ ├── 300-lezzz-sound.mp4 │ │ │ │ └── 302-fecony-whereda.mp4 │ │ │ ├── cookie-consent │ │ │ │ ├── annoying-cookie-banners.jpg │ │ │ │ ├── cookiebanner.png │ │ │ │ ├── enter.gif │ │ │ │ ├── image.png │ │ │ │ ├── keyboard.jpg │ │ │ │ ├── preferences.png │ │ │ │ └── wheel.gif │ │ │ ├── cover-letter-gpt │ │ │ │ ├── coverlettergpt-indiehackers.png │ │ │ │ ├── coverlettergpt-may2025.png │ │ │ │ ├── coverlettergpt-ph-launch.png │ │ │ │ ├── coverlettergpt-plans.png │ │ │ │ ├── coverlettergpt-reddit.png │ │ │ │ ├── coverlettergpt.webp │ │ │ │ ├── demo-video.mp4 │ │ │ │ ├── jeep.png │ │ │ │ ├── mrr-graph.webp │ │ │ │ ├── mrr-growth.png │ │ │ │ ├── mrr-revenue.png │ │ │ │ ├── openai-cost.png │ │ │ │ └── openai-requests.png │ │ │ ├── file-uploads │ │ │ │ ├── cors.png │ │ │ │ ├── create-bucket.png │ │ │ │ ├── default-settings.png │ │ │ │ ├── find-s3.png │ │ │ │ ├── keys.png │ │ │ │ ├── new-bucket.png │ │ │ │ ├── permissions.png │ │ │ │ └── username.png │ │ │ ├── lemon-squeezy │ │ │ │ ├── add-product.png │ │ │ │ ├── add-variant.png │ │ │ │ ├── ngrok.png │ │ │ │ ├── store-id.png │ │ │ │ ├── subscription-variant-ids.png │ │ │ │ └── variant-id.png │ │ │ ├── logo.webp │ │ │ ├── ph │ │ │ │ ├── 1.png │ │ │ │ ├── 10.png │ │ │ │ ├── 11.png │ │ │ │ ├── 12.png │ │ │ │ ├── 13.png │ │ │ │ ├── 14.png │ │ │ │ ├── 15.png │ │ │ │ ├── 16.png │ │ │ │ ├── 17.png │ │ │ │ ├── 18.png │ │ │ │ ├── 19.png │ │ │ │ ├── 2.png │ │ │ │ ├── 2025-03-27-you-should-still-launch-your-product-on-ph.webp │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.png │ │ │ │ ├── 6.gif │ │ │ │ ├── 7.png │ │ │ │ ├── 8.png │ │ │ │ ├── 9.png │ │ │ │ └── compass.png │ │ │ ├── plausible │ │ │ │ └── plausible-community.png │ │ │ ├── promptpanda │ │ │ │ ├── interface.png │ │ │ │ ├── list.png │ │ │ │ ├── meme1.jpg │ │ │ │ ├── meme2.jpg │ │ │ │ ├── ph1.png │ │ │ │ ├── ph2.png │ │ │ │ ├── prompt-panda.png │ │ │ │ └── prompt-panda.webp │ │ │ ├── ricardo-growth-hacks │ │ │ │ ├── article-generation.png │ │ │ │ ├── google-addons.png │ │ │ │ └── meeting-reminders.png │ │ │ ├── seo │ │ │ │ └── open-saas-google.png │ │ │ ├── stripe │ │ │ │ ├── api-keys.png │ │ │ │ ├── db-studio.png │ │ │ │ ├── listen-to-stripe-events.png │ │ │ │ ├── npm-version.png │ │ │ │ ├── price-ids.png │ │ │ │ ├── stripe-webhook-signing-secret.png │ │ │ │ ├── switch-plans.png │ │ │ │ └── test-product.png │ │ │ └── turboreel │ │ │ │ ├── landing.webp │ │ │ │ ├── opensaas.mp4 │ │ │ │ ├── reddit-100-users.webp │ │ │ │ ├── reddit-200-upvotes.webp │ │ │ │ └── studio-interface.mp4 │ │ ├── components │ │ │ ├── HeadWithOGImage.astro │ │ │ ├── HiddenLLMHelper.astro │ │ │ ├── MyHeader.astro │ │ │ ├── MyThemeSelect.astro │ │ │ ├── StarOpenSaaSCTA.astro │ │ │ ├── TitleWithBannerImage.astro │ │ │ ├── Tweet.astro │ │ │ ├── VideoPlayer.astro │ │ │ └── imagePaths.ts │ │ ├── content │ │ │ ├── config.ts │ │ │ └── docs │ │ │ │ ├── blog │ │ │ │ ├── 2023-11-21-coverlettergpt.mdx │ │ │ │ ├── 2024-10-10-most-annoying-cookie-banner-contest.mdx │ │ │ │ ├── 2024-11-22-best-annoying-cookie-consent-banners.mdx │ │ │ │ ├── 2024-12-04-open-source-saas-boilerplate-vs-paid.mdx │ │ │ │ ├── 2024-12-10-turboreel-os-ai-video-generator-built-with-open-saas.mdx │ │ │ │ ├── 2024-12-16-my-gpt-wrapper.mdx │ │ │ │ ├── 2025-01-30-from-0-to-400-customers-saas-growth-hacks.mdx │ │ │ │ ├── 2025-02-26-incident-report-vulnerability-in-open-saas.mdx │ │ │ │ ├── 2025-02-27-meet-marko-saric-co-founder-of-privacy-friendly-plausible-analytics.mdx │ │ │ │ ├── 2025-03-12-going-from-an-idea-to-mvp-in-weeks-promptpandas-launches.mdx │ │ │ │ ├── 2025-05-07-you-should-still-launch-your-product-on-ph.mdx │ │ │ │ └── 2025-05-21-saas-cost-marketing-breakdown.mdx │ │ │ │ ├── general │ │ │ │ ├── admin-dashboard.mdx │ │ │ │ └── user-overview.md │ │ │ │ ├── guides │ │ │ │ ├── analytics.md │ │ │ │ ├── authentication.md │ │ │ │ ├── authorization.md │ │ │ │ ├── cookie-consent.mdx │ │ │ │ ├── deploying.mdx │ │ │ │ ├── email-sending.mdx │ │ │ │ ├── file-uploading.mdx │ │ │ │ ├── payments-integration.mdx │ │ │ │ ├── seo.mdx │ │ │ │ ├── tests.md │ │ │ │ └── updating-opensaas.md │ │ │ │ ├── index.mdx │ │ │ │ └── start │ │ │ │ ├── getting-started.mdx │ │ │ │ └── guided-tour.md │ │ ├── env.d.ts │ │ ├── styles │ │ │ └── tailwind.css │ │ └── virtual.d.ts │ ├── tailwind.config.mjs │ └── tsconfig.json └── tools │ ├── diff.sh │ ├── dope.sh │ └── patch.sh └── template ├── README.md ├── app ├── .cursorrules ├── .env.client.example ├── .env.server.example ├── .gitignore ├── .waspignore ├── .wasproot ├── README.md ├── main.wasp ├── package.json ├── postcss.config.cjs ├── public │ ├── .gitkeep │ ├── favicon.ico │ ├── fonts │ │ ├── Satoshi-Black.eot │ │ ├── Satoshi-Black.ttf │ │ ├── Satoshi-Black.woff │ │ ├── Satoshi-Black.woff2 │ │ ├── Satoshi-BlackItalic.eot │ │ ├── Satoshi-BlackItalic.ttf │ │ ├── Satoshi-BlackItalic.woff │ │ ├── Satoshi-BlackItalic.woff2 │ │ ├── Satoshi-Bold.eot │ │ ├── Satoshi-Bold.ttf │ │ ├── Satoshi-Bold.woff │ │ ├── Satoshi-Bold.woff2 │ │ ├── Satoshi-BoldItalic.eot │ │ ├── Satoshi-BoldItalic.ttf │ │ ├── Satoshi-BoldItalic.woff │ │ ├── Satoshi-BoldItalic.woff2 │ │ ├── Satoshi-Italic.eot │ │ ├── Satoshi-Italic.ttf │ │ ├── Satoshi-Italic.woff │ │ ├── Satoshi-Italic.woff2 │ │ ├── Satoshi-Light.eot │ │ ├── Satoshi-Light.ttf │ │ ├── Satoshi-Light.woff │ │ ├── Satoshi-Light.woff2 │ │ ├── Satoshi-LightItalic.eot │ │ ├── Satoshi-LightItalic.ttf │ │ ├── Satoshi-LightItalic.woff │ │ ├── Satoshi-LightItalic.woff2 │ │ ├── Satoshi-Medium.eot │ │ ├── Satoshi-Medium.ttf │ │ ├── Satoshi-Medium.woff │ │ ├── Satoshi-Medium.woff2 │ │ ├── Satoshi-MediumItalic.eot │ │ ├── Satoshi-MediumItalic.ttf │ │ ├── Satoshi-MediumItalic.woff │ │ ├── Satoshi-MediumItalic.woff2 │ │ ├── Satoshi-Regular.eot │ │ ├── Satoshi-Regular.ttf │ │ ├── Satoshi-Regular.woff │ │ ├── Satoshi-Regular.woff2 │ │ ├── Satoshi-Variable.eot │ │ ├── Satoshi-Variable.ttf │ │ ├── Satoshi-Variable.woff │ │ ├── Satoshi-Variable.woff2 │ │ ├── Satoshi-VariableItalic.eot │ │ ├── Satoshi-VariableItalic.ttf │ │ ├── Satoshi-VariableItalic.woff │ │ └── Satoshi-VariableItalic.woff2 │ └── public-banner.webp ├── schema.prisma ├── src │ ├── admin │ │ ├── dashboards │ │ │ ├── analytics │ │ │ │ ├── AnalyticsDashboardPage.tsx │ │ │ │ ├── RevenueAndProfitChart.tsx │ │ │ │ ├── SourcesTable.tsx │ │ │ │ ├── TotalPageViewsCard.tsx │ │ │ │ ├── TotalPayingUsersCard.tsx │ │ │ │ ├── TotalRevenueCard.tsx │ │ │ │ └── TotalSignupsCard.tsx │ │ │ └── users │ │ │ │ ├── DropdownEditDelete.tsx │ │ │ │ ├── UsersDashboardPage.tsx │ │ │ │ └── UsersTable.tsx │ │ ├── elements │ │ │ ├── calendar │ │ │ │ └── CalendarPage.tsx │ │ │ ├── charts │ │ │ │ ├── BarChart.tsx │ │ │ │ ├── ChartsPage.tsx │ │ │ │ ├── DataStatsChart.tsx │ │ │ │ └── PieChart.tsx │ │ │ ├── forms │ │ │ │ ├── CheckboxOne.tsx │ │ │ │ ├── CheckboxTwo.tsx │ │ │ │ ├── FormElementsPage.tsx │ │ │ │ ├── FormLayoutsPage.tsx │ │ │ │ ├── SwitcherOne.tsx │ │ │ │ └── SwitcherTwo.tsx │ │ │ ├── settings │ │ │ │ └── SettingsPage.tsx │ │ │ └── ui-elements │ │ │ │ ├── AlertsPage.tsx │ │ │ │ └── ButtonsPage.tsx │ │ ├── layout │ │ │ ├── Breadcrumb.tsx │ │ │ ├── DefaultLayout.tsx │ │ │ ├── Header.tsx │ │ │ ├── LoadingSpinner.tsx │ │ │ ├── Sidebar.tsx │ │ │ └── SidebarLinkGroup.tsx │ │ └── useRedirectHomeUnlessUserIsAdmin.ts │ ├── analytics │ │ ├── operations.ts │ │ ├── providers │ │ │ ├── googleAnalyticsUtils.ts │ │ │ └── plausibleAnalyticsUtils.ts │ │ └── stats.ts │ ├── auth │ │ ├── AuthPageLayout.tsx │ │ ├── LoginPage.tsx │ │ ├── SignupPage.tsx │ │ ├── email-and-pass │ │ │ ├── EmailVerificationPage.tsx │ │ │ ├── PasswordResetPage.tsx │ │ │ ├── RequestPasswordResetPage.tsx │ │ │ └── emails.ts │ │ └── userSignupFields.ts │ ├── client │ │ ├── App.tsx │ │ ├── Main.css │ │ ├── cn.ts │ │ ├── components │ │ │ ├── DarkModeSwitcher.tsx │ │ │ ├── NavBar │ │ │ │ ├── NavBar.tsx │ │ │ │ └── contentSections.ts │ │ │ ├── NotFoundPage.tsx │ │ │ └── cookie-consent │ │ │ │ ├── Banner.tsx │ │ │ │ └── Config.ts │ │ ├── hooks │ │ │ ├── useColorMode.tsx │ │ │ ├── useIsLandingPage.tsx │ │ │ └── useLocalStorage.tsx │ │ ├── icons │ │ │ ├── icon-arrow-down.svg │ │ │ ├── icon-calendar.svg │ │ │ ├── icon-copy-alt.svg │ │ │ ├── icon-moon.svg │ │ │ ├── icon-sun.svg │ │ │ └── icons-arrows.tsx │ │ └── static │ │ │ ├── avatar-placeholder.webp │ │ │ ├── da-boi.webp │ │ │ ├── logo.webp │ │ │ └── open-saas-banner.webp │ ├── demo-ai-app │ │ ├── DemoAppPage.tsx │ │ ├── operations.ts │ │ └── schedule.ts │ ├── file-upload │ │ ├── FileUploadPage.tsx │ │ ├── fileUploading.ts │ │ ├── operations.ts │ │ ├── s3Utils.ts │ │ └── validation.ts │ ├── landing-page │ │ ├── LandingPage.tsx │ │ ├── components │ │ │ ├── Clients.tsx │ │ │ ├── FAQ.tsx │ │ │ ├── Features.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Hero.tsx │ │ │ └── Testimonials.tsx │ │ ├── contentSections.ts │ │ └── logos │ │ │ ├── AstroLogo.tsx │ │ │ ├── OpenAILogo.tsx │ │ │ ├── PrismaLogo.tsx │ │ │ └── SalesforceLogo.tsx │ ├── messages │ │ ├── MessageButton.tsx │ │ └── MessagesPage.tsx │ ├── payment │ │ ├── CheckoutPage.tsx │ │ ├── PricingPage.tsx │ │ ├── errors.ts │ │ ├── lemonSqueezy │ │ │ ├── checkoutUtils.ts │ │ │ ├── paymentDetails.ts │ │ │ ├── paymentProcessor.ts │ │ │ ├── webhook.ts │ │ │ └── webhookPayload.ts │ │ ├── operations.ts │ │ ├── paymentProcessor.ts │ │ ├── plans.ts │ │ ├── stripe │ │ │ ├── checkoutUtils.ts │ │ │ ├── paymentDetails.ts │ │ │ ├── paymentProcessor.ts │ │ │ ├── stripeClient.ts │ │ │ ├── webhook.ts │ │ │ └── webhookPayload.ts │ │ └── webhook.ts │ ├── server │ │ ├── scripts │ │ │ └── dbSeeds.ts │ │ ├── utils.ts │ │ └── validation.ts │ ├── shared │ │ ├── common.ts │ │ └── utils.ts │ ├── user │ │ ├── AccountPage.tsx │ │ ├── DropdownUser.tsx │ │ ├── UserMenuItems.tsx │ │ └── operations.ts │ └── vite-env.d.ts ├── tailwind.config.cjs ├── tsconfig.json └── vite.config.ts ├── blog ├── .gitignore ├── .vscode │ ├── extensions.json │ └── launch.json ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public │ ├── CRAIG_ROCK.png │ ├── banner-images │ │ ├── 2023-11-21-coverlettergpt.webp │ │ ├── README.md │ │ └── default-banner.webp │ └── favicon.svg ├── src │ ├── assets │ │ └── logo.webp │ ├── components │ │ ├── HeadWithOGImage.astro │ │ ├── MyHeader.astro │ │ ├── MyThemeSelect.astro │ │ ├── TitleWithBannerImage.astro │ │ └── imagePaths.ts │ ├── content │ │ ├── config.ts │ │ └── docs │ │ │ ├── blog │ │ │ ├── 2023-11-21-coverlettergpt.md │ │ │ └── 2023-11-23-post.md │ │ │ ├── guides │ │ │ └── example.md │ │ │ └── index.md │ ├── env.d.ts │ ├── styles │ │ └── tailwind.css │ └── virtual.d.ts ├── tailwind.config.mjs └── tsconfig.json └── e2e-tests ├── .gitignore ├── README.md ├── ci-start-app-and-db.js ├── package-lock.json ├── package.json ├── playwright.config.ts ├── tests ├── demoAppTests.spec.ts ├── landingPageTests.spec.ts ├── pricingPageTests.spec.ts └── utils.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.wasp linguist-language=TypeScript -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ wasp-lang ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | > Describe your PR! If it fixes specific issue, mention it with "Fixes # (issue)". 4 | 5 | ## Contributor Checklist 6 | 7 | > Make sure to do the following steps if they are applicable to your PR: 8 | 9 | - [ ] **Update e2e tests**: If you changed the [/template/app](/template/app), then make sure to do any neccessary updates to [/template/e2e-tests](/template/e2e-tests) also. 10 | - [ ] **Update demo app**: If you changed the [/template/app](/template/app), then make sure to do any neccessary updates to [/opensaas-sh/app_diff](/opensaas-sh/app_diff) also. Check [/opensaas-sh/README.md](/opensaas-sh/README.md) for details. 11 | - [ ] **Update docs**: If needed, update the [/opensaas-sh/blog/src/content/docs](/opensaas-sh/blog/src/content/docs). 12 | -------------------------------------------------------------------------------- /.github/workflows/blog-deployment.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Blog to Netlify 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'opensaas-sh/blog/**' 9 | pull_request: 10 | branches: 11 | - main 12 | paths: 13 | - 'opensaas-sh/blog/**' 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | if: github.event_name == 'pull_request' 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v3 22 | 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: '18' 27 | 28 | - name: Install dependencies 29 | working-directory: ./opensaas-sh/blog 30 | run: npm install 31 | 32 | - name: Generate LLM files 33 | working-directory: ./opensaas-sh/blog 34 | run: npm run generate-llm-files 35 | 36 | - name: Build site 37 | working-directory: ./opensaas-sh/blog 38 | run: npm run build 39 | 40 | deploy: 41 | runs-on: ubuntu-latest 42 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 43 | steps: 44 | - name: Checkout code 45 | uses: actions/checkout@v3 46 | 47 | - name: Setup Node.js 48 | uses: actions/setup-node@v3 49 | with: 50 | node-version: '18' 51 | 52 | - name: Install dependencies 53 | working-directory: ./opensaas-sh/blog 54 | run: npm install 55 | 56 | - name: Generate LLM files 57 | working-directory: ./opensaas-sh/blog 58 | run: npm run generate-llm-files 59 | 60 | - name: Build site 61 | working-directory: ./opensaas-sh/blog 62 | run: npm run build 63 | 64 | - name: Deploy to Netlify 65 | uses: nwtgck/actions-netlify@v2 66 | with: 67 | publish-dir: './opensaas-sh/blog/dist' 68 | production-branch: main 69 | github-token: ${{ secrets.GITHUB_TOKEN }} 70 | deploy-message: "Deploy from GitHub Actions" 71 | env: 72 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 73 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 74 | timeout-minutes: 1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /template/app/migrations 2 | /template/app/package-lock.json 3 | .DS_Store 4 | 5 | # Local Netlify folder 6 | .netlify 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": true, 4 | "singleQuote": true, 5 | "endOfLine": "lf", 6 | "tabWidth": 2, 7 | "jsxSingleQuote": true, 8 | "printWidth": 110 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 wasp-lang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /opensaas-sh/.gitignore: -------------------------------------------------------------------------------- 1 | # We can't ignore `app/` because it messes up our patch/diff procedure (check 2 | # the README for more info on this) 3 | # app/ 4 | -------------------------------------------------------------------------------- /opensaas-sh/README.md: -------------------------------------------------------------------------------- 1 | # OpenSaas.sh 2 | 3 | This is the https://opensaas.sh page and demo app, built with the Open Saas template! 4 | 5 | It consists of a Wasp app for showcasing the Open Saas template (+ landing page), while the Astro blog is blog and docs for the Open Saas template, found at https://docs.opensaas.sh. 6 | 7 | Inception :)! 8 | 9 | ## Development 10 | 11 | ### Demo app (app_diff/) 12 | 13 | Since the demo app is just the open saas template with some small tweaks, and we want to be able to easily keep it up to date as the template changes, we don't version (in git) the actual demo app code, instead we version the diffs between it and the template: `app_diff/`. 14 | 15 | So because we don't version the actual demo app (`app/`) but its diffs instead (`app_diff`), the typical workflow is as follows: 16 | 17 | 1. Run `./tools/patch.sh` to generate `app/` from `../template/` and `app_diff/`. 18 | 2. If there are any conflicts (normally due to updates to the template), modify `app/` till you resolve them. Do any additional changes also if you wish. 19 | 3. Generate new `app_diff/`, based on the current updated `app/`, by running `./tools/diff.sh`. 20 | 21 | **Running on MacOS** 22 | 23 | If you're running the `patch.sh` or `diff.sh` scripts on Mac, you need to install: 24 | 25 | - `grealpath` (packaged within `coreutils`), 26 | - `gpatch`, 27 | - and `diffutils`. 28 | 29 | ```sh 30 | brew install coreutils # contains grealpath 31 | brew install gpatch 32 | brew install diffutils 33 | ``` 34 | 35 | Make sure not to commit `app/` to git. It is currently (until we resolve this) not added to .gitignore because that messes up diffing for us. 36 | 37 | ### Blog (blog/) 38 | 39 | Blog (and docs in it) is currently tracked in whole, as it has quite some content, so updating it to the latest version of Open Saas is done manually, but it might be interesting to also move it to the `diff` approach, as we use for the demo app, if it turns out to be a good match. 40 | 41 | For more info on authoring content for the docs and blog, including information on custom components, see the [blog/README.md](blog/README.md). 42 | 43 | ## Deployment 44 | 45 | App: check its README.md (after you generate it with `.tools/patch.sh`) . 46 | 47 | Blog (docs): hosted on Netlify. 48 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/.env.client.diff: -------------------------------------------------------------------------------- 1 | --- template/app/.env.client 2 | +++ opensaas-sh/app/.env.client 3 | @@ -0,0 +1 @@ 4 | +REACT_APP_GOOGLE_ANALYTICS_ID=G-H3LSJCK95H 5 | \ No newline at end of file 6 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/.gitignore.diff: -------------------------------------------------------------------------------- 1 | --- template/app/.gitignore 2 | +++ opensaas-sh/app/.gitignore 3 | @@ -6,6 +6,17 @@ 4 | .env 5 | .env.* 6 | 7 | +# These two we added only because dotenv-vault keeps adding them if it doesn't find them, 8 | +# even though we don't need them. Remove them once dotenv-vault stops doing that. 9 | +.env* 10 | +.flaskenv* 11 | + 12 | # Don't ignore example dotenv files. 13 | !.env.example 14 | !.env.*.example 15 | + 16 | +# We don't want to ignore .env.client as it doesn't have any secrets. 17 | +!.env.client 18 | +# These are config files for dotenv-vault, so we don't want to ignore them. 19 | +!.env.project 20 | +!.env.vault 21 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/README.md.diff: -------------------------------------------------------------------------------- 1 | --- template/app/README.md 2 | +++ opensaas-sh/app/README.md 3 | @@ -1,12 +1,25 @@ 4 | -# 5 | +# opensaas.sh (demo) app 6 | 7 | -Built with [Wasp](https://wasp.sh), based on the [Open Saas](https://opensaas.sh) template. 8 | +This is a Wasp app based on Open Saas template with minimal modifications that make it into a demo app that showcases Open Saas's abilities. 9 | + 10 | +It is deployed to https://opensaas.sh and serves both as a landing page for Open Saas and as a demo app. 11 | 12 | ## Development 13 | 14 | +### .env files 15 | +`.env.client` file is versioned, but `.env.server` file you have to obtain by running `npm run env:pull`, since it has secrets in it. 16 | +This will generate `.env.server` based on the `.env.vault`. 17 | +We are using https://vault.dotenv.org to power this and have an account/organization up there. 18 | +If you modify .env.server and want to persist the changes (for yourself and for the other team members), do `npm run env:push`. 19 | + 20 | ### Running locally 21 | - Make sure you have the `.env.client` and `.env.server` files with correct dev values in the root of the project. 22 | - Run the database with `wasp start db` and leave it running. 23 | - Run `wasp start` and leave it running. 24 | - [OPTIONAL]: If this is the first time starting the app, or you've just made changes to your entities/prisma schema, also run `wasp db migrate-dev`. 25 | 26 | +## Deployment 27 | + 28 | +This app is deployed to fly.io, Wasp org, via `wasp deploy fly deploy`. 29 | + 30 | +You can run `npm run deploy` to deploy it via `wasp deploy fly deploy` with required client side env vars correctly set. 31 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/deletions: -------------------------------------------------------------------------------- 1 | src/client/static/avatar-placeholder.webp 2 | src/client/static/da-boi.webp 3 | src/client/static/open-saas-banner.webp 4 | src/landing-page/logos/SalesforceLogo.tsx 5 | src/payment/lemonSqueezy/checkoutUtils.ts 6 | src/payment/lemonSqueezy/paymentDetails.ts 7 | src/payment/lemonSqueezy/paymentProcessor.ts 8 | src/payment/lemonSqueezy/webhook.ts 9 | src/payment/webhook.ts 10 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/fly-client.toml.diff: -------------------------------------------------------------------------------- 1 | --- template/app/fly-client.toml 2 | +++ opensaas-sh/app/fly-client.toml 3 | @@ -0,0 +1,25 @@ 4 | +# fly.toml app configuration file generated for open-saas-wasp-sh-client on 2023-12-04T12:34:07+01:00 5 | +# 6 | +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. 7 | +# 8 | + 9 | +app = "open-saas-wasp-sh-client" 10 | +primary_region = "ams" 11 | + 12 | +[build] 13 | + 14 | +[http_service] 15 | + internal_port = 8043 16 | + force_https = true 17 | + auto_stop_machines = true 18 | + auto_start_machines = true 19 | + min_machines_running = 0 20 | + processes = ["app"] 21 | + 22 | +[http_service.http_options.response] 23 | + pristine = true 24 | + 25 | +[[vm]] 26 | + cpu_kind = "shared" 27 | + cpus = 1 28 | + memory_mb = 1024 29 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/fly-server.toml.diff: -------------------------------------------------------------------------------- 1 | --- template/app/fly-server.toml 2 | +++ opensaas-sh/app/fly-server.toml 3 | @@ -0,0 +1,22 @@ 4 | +# fly.toml app configuration file generated for open-saas-wasp-sh-server on 2023-12-04T12:33:59+01:00 5 | +# 6 | +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. 7 | +# 8 | + 9 | +app = "open-saas-wasp-sh-server" 10 | +primary_region = "ams" 11 | + 12 | +[build] 13 | + 14 | +[http_service] 15 | + internal_port = 8080 16 | + force_https = true 17 | + auto_stop_machines = true 18 | + auto_start_machines = true 19 | + min_machines_running = 1 20 | + processes = ["app"] 21 | + 22 | +[[vm]] 23 | + cpu_kind = "shared" 24 | + cpus = 1 25 | + memory_mb = 1024 26 | \ No newline at end of file 27 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20240105224550_tasks/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20240105224550_tasks/migration.sql 2 | +++ opensaas-sh/app/migrations/20240105224550_tasks/migration.sql 3 | @@ -0,0 +1,14 @@ 4 | +-- CreateTable 5 | +CREATE TABLE "Task" ( 6 | + "id" TEXT NOT NULL, 7 | + "description" TEXT NOT NULL, 8 | + "time" TEXT NOT NULL DEFAULT '1', 9 | + "isDone" BOOLEAN NOT NULL DEFAULT false, 10 | + "userId" INTEGER NOT NULL, 11 | + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 12 | + 13 | + CONSTRAINT "Task_pkey" PRIMARY KEY ("id") 14 | +); 15 | + 16 | +-- AddForeignKey 17 | +ALTER TABLE "Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20240207164719_files/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20240207164719_files/migration.sql 2 | +++ opensaas-sh/app/migrations/20240207164719_files/migration.sql 3 | @@ -0,0 +1,15 @@ 4 | +-- CreateTable 5 | +CREATE TABLE "File" ( 6 | + "id" TEXT NOT NULL, 7 | + "name" TEXT NOT NULL, 8 | + "type" TEXT NOT NULL, 9 | + "key" TEXT NOT NULL, 10 | + "uploadUrl" TEXT NOT NULL, 11 | + "userId" INTEGER NOT NULL, 12 | + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 13 | + 14 | + CONSTRAINT "File_pkey" PRIMARY KEY ("id") 15 | +); 16 | + 17 | +-- AddForeignKey 18 | +ALTER TABLE "File" ADD CONSTRAINT "File_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20240226123357_new_auth_structure/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20240226123357_new_auth_structure/migration.sql 2 | +++ opensaas-sh/app/migrations/20240226123357_new_auth_structure/migration.sql 3 | @@ -0,0 +1,44 @@ 4 | +-- CreateTable 5 | +CREATE TABLE "Auth" ( 6 | + "id" TEXT NOT NULL, 7 | + "userId" INTEGER, 8 | + 9 | + CONSTRAINT "Auth_pkey" PRIMARY KEY ("id") 10 | +); 11 | + 12 | +-- CreateTable 13 | +CREATE TABLE "AuthIdentity" ( 14 | + "providerName" TEXT NOT NULL, 15 | + "providerUserId" TEXT NOT NULL, 16 | + "providerData" TEXT NOT NULL DEFAULT '{}', 17 | + "authId" TEXT NOT NULL, 18 | + 19 | + CONSTRAINT "AuthIdentity_pkey" PRIMARY KEY ("providerName","providerUserId") 20 | +); 21 | + 22 | +-- CreateTable 23 | +CREATE TABLE "Session" ( 24 | + "id" TEXT NOT NULL, 25 | + "expiresAt" TIMESTAMP(3) NOT NULL, 26 | + "userId" TEXT NOT NULL, 27 | + 28 | + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") 29 | +); 30 | + 31 | +-- CreateIndex 32 | +CREATE UNIQUE INDEX "Auth_userId_key" ON "Auth"("userId"); 33 | + 34 | +-- CreateIndex 35 | +CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id"); 36 | + 37 | +-- CreateIndex 38 | +CREATE INDEX "Session_userId_idx" ON "Session"("userId"); 39 | + 40 | +-- AddForeignKey 41 | +ALTER TABLE "Auth" ADD CONSTRAINT "Auth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 42 | + 43 | +-- AddForeignKey 44 | +ALTER TABLE "AuthIdentity" ADD CONSTRAINT "AuthIdentity_authId_fkey" FOREIGN KEY ("authId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE; 45 | + 46 | +-- AddForeignKey 47 | +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE; 48 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20240226130234_remove_old_auth_structure/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20240226130234_remove_old_auth_structure/migration.sql 2 | +++ opensaas-sh/app/migrations/20240226130234_remove_old_auth_structure/migration.sql 3 | @@ -0,0 +1,21 @@ 4 | +/* 5 | + Warnings: 6 | + 7 | + - You are about to drop the column `emailVerificationSentAt` on the `User` table. All the data in the column will be lost. 8 | + - You are about to drop the column `isEmailVerified` on the `User` table. All the data in the column will be lost. 9 | + - You are about to drop the column `password` on the `User` table. All the data in the column will be lost. 10 | + - You are about to drop the column `passwordResetSentAt` on the `User` table. All the data in the column will be lost. 11 | + - You are about to drop the `SocialLogin` table. If the table is not empty, all the data it contains will be lost. 12 | + 13 | +*/ 14 | +-- DropForeignKey 15 | +ALTER TABLE "SocialLogin" DROP CONSTRAINT "SocialLogin_userId_fkey"; 16 | + 17 | +-- AlterTable 18 | +ALTER TABLE "User" DROP COLUMN "emailVerificationSentAt", 19 | +DROP COLUMN "isEmailVerified", 20 | +DROP COLUMN "password", 21 | +DROP COLUMN "passwordResetSentAt"; 22 | + 23 | +-- DropTable 24 | +DROP TABLE "SocialLogin"; 25 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20240226145340_remove_unique_email_username/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20240226145340_remove_unique_email_username/migration.sql 2 | +++ opensaas-sh/app/migrations/20240226145340_remove_unique_email_username/migration.sql 3 | @@ -0,0 +1,5 @@ 4 | +-- DropIndex 5 | +DROP INDEX "User_email_key"; 6 | + 7 | +-- DropIndex 8 | +DROP INDEX "User_username_key"; 9 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20240605151848_remove_has_paid/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20240605151848_remove_has_paid/migration.sql 2 | +++ opensaas-sh/app/migrations/20240605151848_remove_has_paid/migration.sql 3 | @@ -0,0 +1,8 @@ 4 | +/* 5 | + Warnings: 6 | + 7 | + - You are about to drop the column `hasPaid` on the `User` table. All the data in the column will be lost. 8 | + 9 | +*/ 10 | +-- AlterTable 11 | +ALTER TABLE "User" DROP COLUMN "hasPaid"; 12 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20240715142249_version_14/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20240715142249_version_14/migration.sql 2 | +++ opensaas-sh/app/migrations/20240715142249_version_14/migration.sql 3 | @@ -0,0 +1,16 @@ 4 | +/* 5 | + Warnings: 6 | + 7 | + - You are about to drop the column `sendEmail` on the `User` table. All the data in the column will be lost. 8 | + - You are about to drop the column `subscriptionTier` on the `User` table. All the data in the column will be lost. 9 | + - A unique constraint covering the columns `[stripeId]` on the table `User` will be added. If there are existing duplicate values, this will fail. 10 | + 11 | +*/ 12 | +-- AlterTable 13 | +ALTER TABLE "User" DROP COLUMN "sendEmail", 14 | +DROP COLUMN "subscriptionTier", 15 | +ADD COLUMN "sendNewsletter" BOOLEAN NOT NULL DEFAULT false, 16 | +ADD COLUMN "subscriptionPlan" TEXT; 17 | + 18 | +-- CreateIndex 19 | +CREATE UNIQUE INDEX "User_stripeId_key" ON "User"("stripeId"); 20 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20241126132514_remove_checkout_session_id/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20241126132514_remove_checkout_session_id/migration.sql 2 | +++ opensaas-sh/app/migrations/20241126132514_remove_checkout_session_id/migration.sql 3 | @@ -0,0 +1,8 @@ 4 | +/* 5 | + Warnings: 6 | + 7 | + - You are about to drop the column `checkoutSessionId` on the `User` table. All the data in the column will be lost. 8 | + 9 | +*/ 10 | +-- AlterTable 11 | +ALTER TABLE "User" DROP COLUMN IF EXISTS "checkoutSessionId"; 12 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/20250220095333_remove_last_active_timestamp/migration.sql.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/20250220095333_remove_last_active_timestamp/migration.sql 2 | +++ opensaas-sh/app/migrations/20250220095333_remove_last_active_timestamp/migration.sql 3 | @@ -0,0 +1,8 @@ 4 | +/* 5 | + Warnings: 6 | + 7 | + - You are about to drop the column `lastActiveTimestamp` on the `User` table. All the data in the column will be lost. 8 | + 9 | +*/ 10 | +-- AlterTable 11 | +ALTER TABLE "User" DROP COLUMN "lastActiveTimestamp"; 12 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/migrations/migration_lock.toml.diff: -------------------------------------------------------------------------------- 1 | --- template/app/migrations/migration_lock.toml 2 | +++ opensaas-sh/app/migrations/migration_lock.toml 3 | @@ -0,0 +1,3 @@ 4 | +# Please do not edit this file manually 5 | +# It should be added in your version-control system (i.e. Git) 6 | +provider = "postgresql" 7 | \ No newline at end of file 8 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/package.json.diff: -------------------------------------------------------------------------------- 1 | --- template/app/package.json 2 | +++ opensaas-sh/app/package.json 3 | @@ -1,6 +1,11 @@ 4 | { 5 | "name": "opensaas", 6 | "type": "module", 7 | + "scripts": { 8 | + "env:pull": "npx dotenv-vault@latest pull development .env.server", 9 | + "env:push": "npx dotenv-vault@latest push development .env.server", 10 | + "deploy": "REACT_APP_GOOGLE_ANALYTICS_ID=G-H3LSJCK95H wasp deploy fly deploy" 11 | + }, 12 | "dependencies": { 13 | "@aws-sdk/client-s3": "^3.523.0", 14 | "@aws-sdk/s3-presigned-post": "^3.750.0", 15 | @@ -8,7 +13,6 @@ 16 | "@faker-js/faker": "8.3.1", 17 | "@google-analytics/data": "4.1.0", 18 | "@headlessui/react": "1.7.13", 19 | - "@lemonsqueezy/lemonsqueezy.js": "^3.2.0", 20 | "@tailwindcss/forms": "^0.5.3", 21 | "@tailwindcss/typography": "^0.5.7", 22 | "apexcharts": "3.41.0", 23 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/public/piggy.js.diff: -------------------------------------------------------------------------------- 1 | --- template/app/public/piggy.js 2 | +++ opensaas-sh/app/public/piggy.js 3 | @@ -0,0 +1,5 @@ 4 | +!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug getPageViewId captureTraceFeedback captureTraceMetric".split(" "),n=0;n { 12 | - useRedirectHomeUnlessUserIsAdmin({user}) 13 | + const [isDemoInfoVisible, setIsDemoInfoVisible] = useState(false); 14 | + useRedirectHomeUnlessUserIsAdmin({user}); 15 | + 16 | + useEffect(() => { 17 | + try { 18 | + if (localStorage.getItem('isDemoInfoVisible') === 'false') { 19 | + // do nothing 20 | + } else { 21 | + setIsDemoInfoVisible(true); 22 | + } 23 | + } catch (error) { 24 | + console.error(error); 25 | + } 26 | + }, []); 27 | + 28 | + const handleDemoInfoClose = () => { 29 | + try { 30 | + localStorage.setItem('isDemoInfoVisible', 'false'); 31 | + setIsDemoInfoVisible(false); 32 | + } catch (error) { 33 | + console.error(error); 34 | + } 35 | + }; 36 | 37 | return ( 38 | 39 | + {/* Floating Demo Announcement */} 40 | + {isDemoInfoVisible && ( 41 | +
42 | +
43 | + 44 | + You are viewing mock user data only ;) 45 | + 46 | + 49 | +
50 | +
51 | + )} 52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/admin/dashboards/users/UsersTable.tsx.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/admin/dashboards/users/UsersTable.tsx 2 | +++ opensaas-sh/app/src/admin/dashboards/users/UsersTable.tsx 3 | @@ -207,7 +207,7 @@ 4 |

{user.subscriptionStatus}

5 |
6 |
7 | -

{user.paymentProcessorUserId}

8 | +

{user.stripeId}

9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/analytics/providers/plausibleAnalyticsUtils.ts.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/analytics/providers/plausibleAnalyticsUtils.ts 2 | +++ opensaas-sh/app/src/analytics/providers/plausibleAnalyticsUtils.ts 3 | @@ -36,7 +36,7 @@ 4 | 5 | async function getTotalPageViews() { 6 | const response = await fetch( 7 | - `${PLAUSIBLE_BASE_URL}/v1/stats/aggregate?site_id=${PLAUSIBLE_SITE_ID}&metrics=pageviews`, 8 | + `${PLAUSIBLE_BASE_URL}/v1/stats/aggregate?site_id=${PLAUSIBLE_SITE_ID}&metrics=pageviews&with_imported=true`, 9 | { 10 | method: 'GET', 11 | headers: { 12 | @@ -80,7 +80,7 @@ 13 | } 14 | 15 | async function getPageviewsForDate(date: string) { 16 | - const url = `${PLAUSIBLE_BASE_URL}/v1/stats/aggregate?site_id=${PLAUSIBLE_SITE_ID}&period=day&date=${date}&metrics=pageviews`; 17 | + const url = `${PLAUSIBLE_BASE_URL}/v1/stats/aggregate?site_id=${PLAUSIBLE_SITE_ID}&period=day&date=${date}&metrics=pageviews&with_imported=true`; 18 | const response = await fetch(url, { 19 | method: 'GET', 20 | headers: headers, 21 | @@ -93,7 +93,7 @@ 22 | } 23 | 24 | export async function getSources() { 25 | - const url = `${PLAUSIBLE_BASE_URL}/v1/stats/breakdown?site_id=${PLAUSIBLE_SITE_ID}&property=visit:source&metrics=visitors`; 26 | + const url = `${PLAUSIBLE_BASE_URL}/v1/stats/breakdown?site_id=${PLAUSIBLE_SITE_ID}&property=visit:source&metrics=visitors&with_imported=true`; 27 | const response = await fetch(url, { 28 | method: 'GET', 29 | headers: headers, 30 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/auth/LoginPage.tsx.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/auth/LoginPage.tsx 2 | +++ opensaas-sh/app/src/auth/LoginPage.tsx 3 | @@ -1,8 +1,15 @@ 4 | +import { Navigate } from 'react-router-dom'; 5 | import { Link as WaspRouterLink, routes } from 'wasp/client/router'; 6 | -import { LoginForm } from 'wasp/client/auth'; 7 | +import { LoginForm, useAuth } from 'wasp/client/auth'; 8 | import { AuthPageLayout } from './AuthPageLayout'; 9 | 10 | export default function Login() { 11 | + const { data: user } = useAuth(); 12 | + 13 | + if (user) { 14 | + return ; 15 | + } 16 | + 17 | return ( 18 | 19 | 20 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/auth/userSignupFields.ts.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/auth/userSignupFields.ts 2 | +++ opensaas-sh/app/src/auth/userSignupFields.ts 3 | @@ -1,8 +1,6 @@ 4 | import { z } from 'zod'; 5 | import { defineUserSignupFields } from 'wasp/auth/providers/types'; 6 | 7 | -const adminEmails = process.env.ADMIN_EMAILS?.split(',') || []; 8 | - 9 | const emailDataSchema = z.object({ 10 | email: z.string(), 11 | }); 12 | @@ -16,10 +14,6 @@ 13 | const emailData = emailDataSchema.parse(data); 14 | return emailData.email; 15 | }, 16 | - isAdmin: (data) => { 17 | - const emailData = emailDataSchema.parse(data); 18 | - return adminEmails.includes(emailData.email); 19 | - }, 20 | }); 21 | 22 | const githubDataSchema = z.object({ 23 | @@ -45,14 +39,6 @@ 24 | const githubData = githubDataSchema.parse(data); 25 | return githubData.profile.login; 26 | }, 27 | - isAdmin: (data) => { 28 | - const githubData = githubDataSchema.parse(data); 29 | - const emailInfo = getGithubEmailInfo(githubData); 30 | - if (!emailInfo.verified) { 31 | - return false; 32 | - } 33 | - return adminEmails.includes(emailInfo.email); 34 | - }, 35 | }); 36 | 37 | // We are using the first email from the list of emails returned by GitHub. 38 | @@ -85,13 +71,6 @@ 39 | const googleData = googleDataSchema.parse(data); 40 | return googleData.profile.email; 41 | }, 42 | - isAdmin: (data) => { 43 | - const googleData = googleDataSchema.parse(data); 44 | - if (!googleData.profile.email_verified) { 45 | - return false; 46 | - } 47 | - return adminEmails.includes(googleData.profile.email); 48 | - }, 49 | }); 50 | 51 | export function getGoogleAuthConfig() { 52 | @@ -121,13 +100,6 @@ 53 | const discordData = discordDataSchema.parse(data); 54 | return discordData.profile.username; 55 | }, 56 | - isAdmin: (data) => { 57 | - const discordData = discordDataSchema.parse(data); 58 | - if (!discordData.profile.email || !discordData.profile.verified) { 59 | - return false; 60 | - } 61 | - return adminEmails.includes(discordData.profile.email); 62 | - }, 63 | }); 64 | 65 | export function getDiscordAuthConfig() { 66 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/client/components/cookie-consent/Config.ts.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/client/components/cookie-consent/Config.ts 2 | +++ opensaas-sh/app/src/client/components/cookie-consent/Config.ts 3 | @@ -97,8 +97,8 @@ 4 | // showPreferencesBtn: 'Manage Individual preferences', // (OPTIONAL) Activates the preferences modal 5 | // TODO: Add your own privacy policy and terms and conditions links below. 6 | footer: ` 7 | - Privacy Policy 8 | - Terms and Conditions 9 | + Privacy Policy 10 | + Terms and Conditions 11 | `, 12 | }, 13 | // The showPreferencesBtn activates this modal to manage individual preferences https://cookieconsent.orestbida.com/reference/configuration-reference.html#translation-preferencesmodal 14 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/file-upload/operations.ts.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/file-upload/operations.ts 2 | +++ opensaas-sh/app/src/file-upload/operations.ts 3 | @@ -37,6 +37,18 @@ 4 | userId: context.user.id, 5 | }); 6 | 7 | + const numberOfFilesByUser = await context.entities.File.count({ 8 | + where: { 9 | + user: { 10 | + id: context.user.id, 11 | + }, 12 | + }, 13 | + }); 14 | + 15 | + if (numberOfFilesByUser >= 2) { 16 | + throw new HttpError(403, 'Thanks for trying Open SaaS. This demo only allows 2 file uploads per user.'); 17 | + } 18 | + 19 | await context.entities.File.create({ 20 | data: { 21 | name: fileName, 22 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/landing-page/components/FAQ.tsx.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/landing-page/components/FAQ.tsx 2 | +++ opensaas-sh/app/src/landing-page/components/FAQ.tsx 3 | @@ -7,20 +7,20 @@ 4 | 5 | export default function FAQ({ faqs }: { faqs: FAQ[] }) { 6 | return ( 7 | -
8 | +
9 |

10 | Frequently asked questions 11 |

12 | -
13 | +
14 | {faqs.map((faq) => ( 15 |
16 |
17 | {faq.question} 18 |
19 | -
20 | -

{faq.answer}

21 | +
22 | +

{faq.answer}

23 | {faq.href && ( 24 | - 25 | + 26 | Learn more → 27 | 28 | )} 29 | -------------------------------------------------------------------------------- /opensaas-sh/app_diff/src/landing-page/components/Footer.tsx.diff: -------------------------------------------------------------------------------- 1 | --- template/app/src/landing-page/components/Footer.tsx 2 | +++ opensaas-sh/app/src/landing-page/components/Footer.tsx 3 | @@ -13,7 +13,7 @@ 4 |
5 |