mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-11-25 19:49:47 +01:00
All in dirs (#154)
* Split the project into template and opensaas-sh (demo app (diff) + docs). * fix
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: How I Built & Grew CoverLetterGPT to 5,000 Users and $200 MRR
|
||||
date: 2023-11-21
|
||||
tags: ["indiehacker", "saas", "sideproject"]
|
||||
---
|
||||
## Hey, I’m Vince…
|
||||
|
||||

|
||||
|
||||
I’m a self-taught developer that changed careers during the Covid Pandemic. I did it because I wanted a better career, enjoyed programming, and at the same time, had a keen interest in IndieHacking.
|
||||
<!--truncate-->
|
||||
If you’re not aware, IndieHacking is the movement of developers who build potentially profitable side-projects in their spare time. And there are some very successful examples of IndieHackers and “solopreneurs” out there inspiring others, such as [levels.io](http://levels.io) and [Marc Lou](https://twitter.com/marc_louvion).
|
||||
|
||||
This thought of being able to build my own side-project that could generate profit while I slept was always attractive to me.
|
||||
|
||||

|
||||
|
||||
So I’m happy to report that I’ve finally done it with my first software-as-a-service (SaaS) app, [CoverLetterGPT.xyz](http://CoverLetterGPT.xyz), which I launched in March 2023!
|
||||
|
||||
I’ll be the first to admit that the results aren’t spectacular, but they’re still something I’m very proud of:
|
||||
|
||||
- over 5,000 registered users
|
||||
- $203 monthly recurring revenue (MRR)
|
||||
|
||||
Below, I’m going to share with you how I built it (yes, it’s [open-source](https://github.com/vincanger/coverlettergpt)!), how I marketed and monetized it, along with a bunch of helpful resources to help you build your own profitable side-project.
|
||||
|
||||
## What the heck is CoverLetterGPT?
|
||||
|
||||
[CoverLetterGPT.xyz](http://CoverLetterGPT.xyz) was an idea I got after the OpenAI API was released. It’s an app that allows you to upload a PDF of your CV/resumé, along with the job description you’re applying to, and it will generate and edit unique cover letters for you based on this information.
|
||||
|
||||
{% embed https://youtu.be/ZhcFRD9cVrI %}
|
||||
|
||||
It also lets you save and manage your cover letters per each job, making it easy to make and apply to multiple jobs without having to keep copy and pasting all your important info into ChatGPT!
|
||||
|
||||
## What’s the Tech Stack?
|
||||
|
||||

|
||||
|
||||
CoverLetterGPT is entirely open-source, so you can [check out the code](https://github.com/vincanger/coverlettergpt), fork it, learn from it, make your own, submit a PR (I’d love you forever if you did 🙂)… whatever!
|
||||
|
||||
I built it using the [Wasp full-stack framework](https://wasp-lang.dev) which allowed me to ship it about 10x faster.
|
||||
|
||||
Why?
|
||||
|
||||
Because [Wasp](https://wasp-lang.dev) as a framework allows you to describe your app’s core features in a `main.wasp` config file. Then it continually compiles and “glues” these features into a React-ExpressJS-Prisma full-stack app for you.
|
||||
|
||||
All you have to focus on is writing the client and server-side logic, and Wasp will do the boring stuff for you, like authentication & authorization, server config, email sending, and cron jobs.
|
||||
|
||||
BTW, [Wasp](https://wasp-lang.dev) is open-source and free and you can help the project out a ton by starring the repo on GitHub: [https://www.github.com/wasp-lang/wasp](https://www.github.com/wasp-lang/wasp) 🙏
|
||||
|
||||

|
||||
|
||||
{% cta [https://www.github.com/wasp-lang/wasp](https://www.github.com/wasp-lang/wasp) %} ⭐️ Thanks For Your Support 🙏 {% endcta %}
|
||||
|
||||
For the UI, I used [Chakra UI](https://chakra-ui.com/), as I always do. I like that it’s a component-based UI library. This helps me build UI’s a lot faster than I would with Tailwind or vanilla CSS.
|
||||
|
||||
For payments, I used [Stripe](https://www.notion.so/How-I-Built-and-Open-Sourced-CoverLetterGPT-5-000-users-200-MRR-0d32f13fa00a440fb8e08c8dbf2b8a27?pvs=21), (I’ll go into the details of monetization below).
|
||||
|
||||
The Server and Postgres Database are hosted on [https://railway.app](https://railway.app/), with the client on [Netlify.com](http://Netlify.com)’s free tier.
|
||||
|
||||

|
||||
|
||||
By the way, If you’re interested in building your own SaaS with almost the same stack as above, I also built a [free SaaS template](https://github.com/wasp-lang/SaaS-Template-GPT) you can use that will save you days of work!
|
||||
|
||||
## How I Marketed It
|
||||
|
||||
My biggest take-away from this whole project was that open-sourcing it was the best way to market it!
|
||||
|
||||
This seems counter-intuitive, right? Why would making the code available for anyone to see and copy be good for a business? You’re basically rolling out a red carpet for competitors, aren’t you?
|
||||
|
||||
Well, not quite.
|
||||
|
||||
First of all, the number of people who will realistically spend the time and energy launching a direct competitor is low. Also, most people interested in your open-source code want to learn some aspect of it and apply it to their own ideas, not just copy yours directly.
|
||||
|
||||
Secondly, and most importantly, the fact that it’s open-source makes people a lot more receptive to you talking about it.
|
||||
|
||||

|
||||
|
||||
When you present something you’ve built and give people the opportunity to learn from it, they’re much more welcoming! As a result, they’re more likely to upvote it, share it, use it, and recommend it to others.
|
||||
|
||||
This is exactly what happened with CoverLetterGPT! As a result of me sharing the open-source code, it get featured on the [IndieHackers.com](https://www.indiehackers.com/post/whats-new-don-t-build-things-no-one-wants-833ee752ba?utm_source=indie-hackers-emails&utm_campaign=ih-newsletter&utm_medium=email) newsletter (>100k subscribers), shared on blogs, and talked about on social media platforms.
|
||||
|
||||

|
||||
|
||||
And even though it’s a small, simple product, I tried launching it on [Product Hunt](http://producthunt.com), where it also performed considerably well.
|
||||
|
||||
So, all together, these initial efforts combined gave my product a good initial marketing presence. To this day, I haven’t really done much else to market it, except some twitter posts (and this post, if you want to consider it marketing 🤑).
|
||||
|
||||
## How I Monetized It
|
||||
|
||||
When I first launched in March 2023, I didn’t really expect anyone to pay for the product, but I wanted to learn how to use Stripe as a payments processor, thinking that the skills might be useful in the future.
|
||||
|
||||
So I started simple, and just put a one-time payment link for tips. No paywall, no subscriptions. It was entirely free to use with any tip amount welcome.
|
||||
|
||||
To my surprise, tips started coming in, with some as high as $10 dollars!
|
||||
|
||||
This encouraged me to force users to login to use the product, and add a paywall after users used up 3 credits.
|
||||
|
||||
My initial payment options were:
|
||||
|
||||
- $4.95 for a 3 months access
|
||||
- $2.95 for 10 cover letter generations
|
||||
|
||||

|
||||
|
||||
That went reasonably well until I implemented the ability for users to use GPT to make finer edits to their generated cover letters. That’s when I changed my pricing and that’s when better profits started to come in:
|
||||
|
||||
- $5.95 / month subscription with GPT-4
|
||||
- $2.95 / month subscription with GPT-3.5-turbo
|
||||
|
||||
Currently, over 90% of my customers choose the more powerful, more [expensive plan with GPT-4](https://openai.com/pricing), even though the 3 trial credits use the GPT-3.5-turbo model.
|
||||
|
||||
(I also integrated Bitcoin Lightning payments — check out the [repo](https://github.com/vincanger/coverlettergpt) if you want to learn how — but haven’t received any yet.)
|
||||
|
||||
Now, with an MRR of ~$203, my monthly profit of course depends on my costs, which are:
|
||||
|
||||
- Domain Name: $10/year
|
||||
- OpenAI bill: ~ $15/month
|
||||
- Hosting bill: ~ $3/month
|
||||
|
||||
Which leaves me at about ~ $183/month in profits 😀
|
||||
|
||||
## Future Plans
|
||||
|
||||
One of the most surprising aspects about [CoverLetterGPT.xyz](http://CoverLetterGPT.xyz)’s success is that, on the surface, the product is very simple. Also, I’ve done very little in the way of SEO marketing, and haven’t continued to market it much at all. The current growth is mostly organic at this point thanks to my initial marketing efforts.
|
||||
|
||||
But I still have some plans to make it better:
|
||||
|
||||
- buy a better top-level domain (TLD), like [CoverLetterGPT.ai](http://CoverLetterGPT.ai)
|
||||
- add more features, like the ability to generate interview questions based on the cover letters
|
||||
- improve the UX and make it look more “professional”
|
||||
|
||||
If you have any other ideas how I could improve it, drop me a comment, message me on [twitter/x](https://twitter.com/hot_town), or submit a [PR to the repo](https://github.com/vincanger/coverlettergpt).
|
||||
|
||||
## Final Words + More Resources
|
||||
|
||||
My intention with this article was to help others who might be considering launching their own SaaS product. So I hope that’s been the case here. If you still have any questions, don’t hesitate to ask.
|
||||
|
||||
Here are also the most important links from this article along with some further resources that will help in building and marketing your own profitable side-project:
|
||||
|
||||
- 👨💻 [CoverLetterGPT GitHub Repo](https://github.com/vincanger/coverlettergpt)
|
||||
- 💸 [Free Full-Stack SaaS Template w/ Google Auth, Stripe, GPT, & instructions in the README!](https://github.com/wasp-lang/SaaS-Template-GPT)
|
||||
- ✍️ [Initial CoverLetterGPT Reddit Post](https://www.reddit.com/r/webdev/comments/11uh4qo/comment/jco5ggp/?utm_source=share&utm_medium=web2x&context=3)
|
||||
- 🪓 [IndieHackers Feature](https://www.indiehackers.com/post/whats-new-don-t-build-things-no-one-wants-833ee752ba?utm_source=indie-hackers-emails&utm_campaign=ih-newsletter&utm_medium=email)
|
||||
- 💸 [Great Video on how to use Stripe CLI & Webhooks](https://www.youtube.com/watch?v=Psq5N5C-FGo&t=1041s)
|
||||
|
||||
Oh, and if you found these resources useful, don't forget to support Wasp by [starring the repo on GitHub](https://github.com/wasp-lang/wasp)!
|
||||
|
||||

|
||||
|
||||
{% cta [https://www.github.com/wasp-lang/wasp](https://www.github.com/wasp-lang/wasp) %} ⭐️ Thanks For Your Support 🙏 {% endcta %}
|
||||
15
opensaas-sh/blog/src/content/docs/blog/2023-11-23-post.md
Normal file
15
opensaas-sh/blog/src/content/docs/blog/2023-11-23-post.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: My first blog post
|
||||
date: 2023-11-20
|
||||
authors:
|
||||
- name: Craig Man
|
||||
title: Rock n Roller
|
||||
picture: /CRAIG_ROCK.png
|
||||
tags: ["blog", "post", "saas", "rocknroll"]
|
||||
---
|
||||
|
||||
## Hello
|
||||
|
||||
Hello world!
|
||||
|
||||

|
||||
80
opensaas-sh/blog/src/content/docs/general/admin-dashboard.md
Normal file
80
opensaas-sh/blog/src/content/docs/general/admin-dashboard.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Admin Dashboard
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
This is a reference on how the Admin dashboard is set up and works.
|
||||
|
||||
## Permissions
|
||||
|
||||
The Admin dashboard is only accessible to users with the `isAdmin` field set to true.
|
||||
|
||||
```tsx title="main.wasp" {5}
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
email String? @unique
|
||||
username String?
|
||||
isAdmin Boolean @default(false)
|
||||
//...
|
||||
```
|
||||
|
||||
To give yourself administrator priveledges, make sure you add your email adderesses to the `ADMIN_EMAILS` environment variable in `.env.server` file before registering/logging in with that email address.
|
||||
|
||||
```sh title=".env.server"
|
||||
ADMIN_EMAILS=me@example.com
|
||||
|
||||
// or add many admins with a comma-separated list
|
||||
|
||||
ADMIN_EMAILS=me@example.com,you@example.com,them@example.com
|
||||
```
|
||||
|
||||
if you've already logged in with an email address that you want to give admin priveledges to, you can run the following command in a separate terminal window to update the user's `isAdmin` field:
|
||||
|
||||
```sh
|
||||
wasp db studio
|
||||
```
|
||||
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
|
||||
## Admin Dashboard Pages
|
||||
|
||||
### Dashboard
|
||||
The Admin dashboard is a single place for you to view your most important metrics and perform some admin tasks. At the moment, it pulls data from:
|
||||
|
||||
<!-- TODO: add photo -->
|
||||
|
||||
- [Stripe](/guides/stripe-integration):
|
||||
- total revenue
|
||||
- revenue for each day of the past week
|
||||
- [Google or Plausible](/guides/analytics):
|
||||
- total number of page views (non-unique)
|
||||
- percentage change in page views from the previous day
|
||||
- top sources/referrers with unique visitor count (i.e. how many people came from that source to your app)
|
||||
- Database:
|
||||
- total number of registered users
|
||||
- daily change in number of registered users
|
||||
- total number of paying users
|
||||
- daily change in number of paying users
|
||||
|
||||
For a guide on how to integrate these services, check out the [Stripe](/guides/stripe-integration) and [Analytics guide](/guides/analytics) of the docs.
|
||||
|
||||
:::note[Help us improve]
|
||||
We're always looking to improve the Admin dashboard. If you feel something is missing or could be improved, consider [opening an issue](https://github.com/wasp-lang/open-saas/issues) or [submitting a pull request](https://github.com/wasp-lang/open-saas/pulls)
|
||||
:::
|
||||
|
||||
### Users
|
||||
The Users page is where you can view all your users and their most important details. You can also search and filter users by:
|
||||
- email address
|
||||
- subscription/payment status
|
||||
|
||||
133
opensaas-sh/blog/src/content/docs/general/user-overview.md
Normal file
133
opensaas-sh/blog/src/content/docs/general/user-overview.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
title: User Overview
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
This reference will help you understand how the User entity works in this template.
|
||||
This includes the user roles, subscription tiers and statuses, and how to authorize access to certain pages and components.
|
||||
|
||||
## User Entity
|
||||
|
||||
The `User` entity within your app is defined in the `main.wasp` file:
|
||||
|
||||
```tsx title="main.wasp" ins="User: {}"
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
email String? @unique
|
||||
username String?
|
||||
createdAt DateTime @default(now())
|
||||
lastActiveTimestamp DateTime @default(now())
|
||||
isAdmin Boolean @default(false)
|
||||
stripeId String?
|
||||
checkoutSessionId String?
|
||||
subscriptionTier String?
|
||||
subscriptionStatus String?
|
||||
sendEmail Boolean @default(false)
|
||||
datePaid DateTime?
|
||||
credits Int @default(3)
|
||||
relatedObject RelatedObject[]
|
||||
externalAuthAssociations SocialLogin[]
|
||||
contactFormMessages ContactFormMessage[]
|
||||
psl=}
|
||||
```
|
||||
|
||||
We store all pertinent information to the user, including identification, subscription, and Stripe information. Meanwhile, Wasp abstracts away all the Auth related entities dealing with `passwords`, `sessions`, and `socialLogins`, so you don't have to worry about these at all in your Prisma schema (if you want to learn more about this process, check out the [Wasp Auth Docs](https://wasp-lang.dev/docs/auth/overview)).
|
||||
|
||||
## Stripe and Subscriptions
|
||||
|
||||
We use Stripe to handle all of our subscription payments. The `User` entity has a number of fields that are related to Stripe and their ability to access features behind the paywall:
|
||||
|
||||
```tsx title="main.wasp" {4-10}
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
//...
|
||||
stripeId String?
|
||||
checkoutSessionId String?
|
||||
subscriptionTier String?
|
||||
subscriptionStatus String?
|
||||
datePaid DateTime?
|
||||
credits Int @default(3)
|
||||
//...
|
||||
psl=}
|
||||
```
|
||||
|
||||
- `stripeId`: The Stripe customer ID. This is created by Stripe on checkout and used to identify the customer.
|
||||
- `checkoutSessionId`: The Stripe checkout session ID. This is created by Stripe on checkout and used to identify the checkout session.
|
||||
- `subscriptionTier`: The subscription tier the user is on. This is set by the app and is used to determine what features the user has access to. By default, we have two tiers: `hobby-tier` and `pro-tier`.
|
||||
- `subscriptionStatus`: The subscription status of the user. This is set by Stripe and is used to determine whether the user has access to the app or not. By default, we have four statuses: `active`, `past_due`, `canceled`, and `deleted`.
|
||||
- `credits` (optional): By default, a user is given 3 credits to trial your product before they have to pay. You can create a one-time purchase product in Stripe to allow users to purchase more credits if they run out.
|
||||
|
||||
### Subscription Statuses
|
||||
|
||||
In general, we determine if a user has paid for an initial subscription by checking if the `subscriptionStatus` field is set. This field is set by Stripe within your webhook handler and is used to signify more detailed information on the user's current status. By default, the template handles four statuses: `active`, `past_due`, `canceled`, and `deleted`.
|
||||
|
||||
- When `active` the user has paid for a subscription and has full access to the app.
|
||||
|
||||
- When `canceled`, the user has canceled their subscription and has access to the app until the end of their billing period.
|
||||
|
||||
- When `deleted`, the user has reached the end of their subscription period after canceling and no longer has access to the app.
|
||||
|
||||
- When `past_due`, the user's automatic subscription renewal payment was declined (e.g. their credit card expired). You can choose how to handle this status within your app. For example, you can send the user an email to update their payment information:
|
||||
```tsx title="src/server/webhooks/stripe.ts"
|
||||
import { emailSender } from "wasp/server/email";
|
||||
//...
|
||||
|
||||
if (subscription.status === 'past_due') {
|
||||
const updatedCustomer = await context.entities.User.update({
|
||||
where: {
|
||||
id: customer.id,
|
||||
},
|
||||
data: {
|
||||
subscriptionStatus: 'past_due',
|
||||
},
|
||||
});
|
||||
|
||||
if (updatedCustomer.email) {
|
||||
await emailSender.send({
|
||||
to: updatedCustomer.email,
|
||||
subject: 'Your Payment is Past Due',
|
||||
text: 'Please update your payment information to continue using our service.',
|
||||
html: '...',
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See the client-side [authorization section](/guides/authorization) below for more info on how to handle these statuses within your app.
|
||||
|
||||
### Subscription Tiers
|
||||
|
||||
The `subscriptionTier` field is used to determine what features the user has access to.
|
||||
|
||||
By default, we have two tiers: `hobby-tier` and `pro-tier`.
|
||||
|
||||
You can add more tiers by adding more products and price IDs to your Stripe product and updating environment variables in your `.env.server` file as well as the relevant code in your app.
|
||||
|
||||
See the [Stripe Integration Guide](/guides/stripe-integration) for more info on how to do this.
|
||||
|
||||
## User Roles
|
||||
|
||||
At the moment, we have two user roles: `admin` and `user`. This is defined within the `isAdmin` field in the `User` entity:
|
||||
|
||||
```tsx title="main.wasp" {7}
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
email String? @unique
|
||||
username String?
|
||||
createdAt DateTime @default(now())
|
||||
lastActiveTimestamp DateTime @default(now())
|
||||
isAdmin Boolean @default(false)
|
||||
//...
|
||||
psl=}
|
||||
```
|
||||
|
||||
As an Admin, a user has access to the Admin dashboard, along with the user table where they can view and search for users, and edit and update information manually if necessary.
|
||||
|
||||
:::tip[Admin Priveleges]
|
||||
If you'd like to give yourself and/or certain users admin priveleges, follow the instructions in the [Admin Dashboard](/general/admin-dashboard/#permissions) section.
|
||||
:::
|
||||
|
||||
As a general User, a user has access to the user-facing app that sits behind the login, but not the Admin dashboard. You can further restrict access to certain features within the app by following the [authorization guide](/guides/authorization).
|
||||
135
opensaas-sh/blog/src/content/docs/guides/analytics.md
Normal file
135
opensaas-sh/blog/src/content/docs/guides/analytics.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Analytics
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
This guide will show you how to integrate analytics for your app. You can choose between [Google Analytics](#google-analytics) and [Plausible](#plausible).
|
||||
|
||||
Google Analytics is free, but tends to be more cumbersome to use.
|
||||
|
||||
Plausible is an open-source, privacy-friendly alternative to Google Analytics. It's also easier to use than Google if you use their hosted service, which is a paid feature. But, it is completely free if you want to self-host it, although this comes with some additional setup steps.
|
||||
|
||||
If you're looking to add analytics to your blog, you can follow the [Adding Analytics to your Blog](#adding-analytics-to-your-blog) section at the end of this guide.
|
||||
|
||||
## Plausible
|
||||
|
||||
### Hosted Plausible
|
||||
Sign up for a hosted Plausible account [here](https://plausible.io/).
|
||||
|
||||
Once you've signed up, you'll be taken to your dashboard. Create your site by adding your domain. Your domain is also your `PLAUSIBLE_SITE_ID` in your `.env.server` file. Make sure to add it.
|
||||
|
||||
```sh
|
||||
PLAUSIBLE_SITE_ID=<your domain without www>
|
||||
```
|
||||
|
||||
After adding your domain, you'll be taken to a page with your Plausible script tag. Copy and paste this script tag into the `main.wasp` file's head section.
|
||||
|
||||
```js {7}
|
||||
app OpenSaaS {
|
||||
wasp: {
|
||||
version: "^0.13.0"
|
||||
},
|
||||
title: "My SaaS App",
|
||||
head: [
|
||||
"<your plausible script tag here>",
|
||||
],
|
||||
//...
|
||||
```
|
||||
|
||||
Go back to your Plausible dashboard, click on your username in the top right, and click on the `Settings` tab. Scroll down, find your API key and paste it into your `.env.server` file under the `PLAUSIBLE_API_KEY` variable.
|
||||
|
||||
|
||||
### Self-hosted Plausible
|
||||
|
||||
Plausible, being an open-source project, allows you to self-host your analytics. This is a great option if you want to keep your data private and not pay for the hosted service.
|
||||
|
||||
*coming soon...*
|
||||
*until then, check out the [official documentation](https://plausible.io/docs)*
|
||||
|
||||
:::tip[Contribute!]
|
||||
If you'd like to help us write this guide, click the "Edit page" button at the bottom of this page
|
||||
|
||||
As a completely free, open-source project, we appreciate any help 🙏
|
||||
:::
|
||||
|
||||
## Google Analytics
|
||||
|
||||
After you sign up for [Google analytics](https://analytics.google.com/), go to your `Admin` panel in the bottom of the left sidebar and then create a "Property" for your app.
|
||||
|
||||
Once you've completed the steps to create a new Property, some Installation Instructions will pop up. Select `install manually` and copy and paste the Google script tag into the `main.wasp` file's head section.
|
||||
|
||||
```js {7}
|
||||
app OpenSaaS {
|
||||
wasp: {
|
||||
version: "^0.13.0"
|
||||
},
|
||||
title: "My SaaS App",
|
||||
head: [
|
||||
"<your google analytics script tag here>",
|
||||
],
|
||||
//...
|
||||
```
|
||||
|
||||
:::tip[noscript]
|
||||
In the Installation Instructions, Google Tag Manager might also instruct you to paste the `noscript` code snippet immediately after the opening `<body>` tag.
|
||||
You should skip this step because this snippet is activated only if users try to browse your app without JavaScript enabled, which is very rare and Wasp needs JS anyway.
|
||||
:::
|
||||
|
||||
Then, set up the Google Analytics API access by following these steps:
|
||||
|
||||
1. **Set up a Google Cloud project:** If you haven't already, start by setting up a project in the [Google Cloud Console](https://console.cloud.google.com/).
|
||||
|
||||
2. **Enable the Google Analytics API for your project:** Navigate to the "Library" in the Google Cloud Console and search for the "Google Analytics Data API" (for Google Analytics 4 properties) and enable it.
|
||||
|
||||
3. **Create credentials:** Now go to the "Credentials" tab within your Google Cloud project, click on `+ credentials`, and create a new service account key. First, give it a name. Then, under "Grant this service account access to project", choose `viewer`.
|
||||
|
||||
4. **Create Credentials:** When you go back to `Credentials` page, you should see a new service account listed under "Service Accounts". It will be a long email address to ends with `@your-project-id.iam.gserviceaccount.com`. Click on the service account name to go to the service account details page.
|
||||
|
||||
- Under “Keys” in the service account details page, click “Add Key” and choose `Create new key`.
|
||||
|
||||
- Select "JSON", then click “Create” to download your new service account’s JSON key file. Keep this file secure and don't add it to your git repo – it grants access to your Google Analytics data.
|
||||
5. **Update your Google Anayltics Settings:** Go back to your Google Analytics dashboard, and click on the `Admin` section in the left sidebar. Under `Property Settings > Property > Property Access Management` Add the service account email address (the one that ends with `@your-project-id.iam.gserviceaccount.com`) and give it `Viewer` permissions.
|
||||
|
||||
6. **Encode and add the Credentials:** Add the `client_email` and the `private_key` from your JSON Key file into your `.env.server` file. But be careful! Because Google uses a special PEM private key, you need to first convert the key to base64, otherwise you will run into errors parsing the key. To do this, in a terminal window, run the command below and paste the output into your `.env.server` file under the `GOOGLE_ANALYTICS_PRIVATE_KEY` variable:
|
||||
```sh
|
||||
echo -n "PRIVATE_KEY" | base64
|
||||
```
|
||||
|
||||
7. **Add your Google Analytics Property ID:** You will find the Property ID in your Google Analytics dashboard in the `Admin > Property > Property Settings > Property Details` section of your Google Analytics property (**not** your Google Cloud console). Add this 9-digit number to your `.env.server` file under the `GOOGLE_ANALYTICS_PROPERTY_ID` variable.
|
||||
|
||||
## Adding Analytics to your Blog
|
||||
|
||||
To add your analytics script to your Astro Starlight blog, all you need to do is modify the `head` property in your `blog/astro.config.mjs` file.
|
||||
|
||||
Below is an example of how to add Google Analytics to your blog:
|
||||
|
||||
```js
|
||||
export default defineConfig({
|
||||
site: 'https://opensaas.sh',
|
||||
integrations: [
|
||||
starlightBlog({
|
||||
// ...
|
||||
}),
|
||||
starlight({
|
||||
//...
|
||||
head: [
|
||||
{
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
src: 'https://www.googletagmanager.com/gtag/js?id=<YOUR-GOOGLE-ANALYTICS-ID>',
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'script',
|
||||
content: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '<YOUR-GOOGLE-ANALYTICS-ID>');
|
||||
`,
|
||||
},
|
||||
],
|
||||
```
|
||||
92
opensaas-sh/blog/src/content/docs/guides/authentication.md
Normal file
92
opensaas-sh/blog/src/content/docs/guides/authentication.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Authentication
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
Setting up your app's authentication is easy with Wasp. In fact, it's already set up for you in the `main.wasp` file:
|
||||
|
||||
```tsx title="main.wasp" "
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
email: {},
|
||||
google: {},
|
||||
gitHub: {}
|
||||
},
|
||||
onAuthFailedRedirectTo: "/",
|
||||
},
|
||||
```
|
||||
|
||||
The great part is, by defining your auth config in the `main.wasp` file, Wasp manages most of the Auth process for you, including the auth-related database entities for user credentials and sessions, as well as auto-generated client components for your app on the fly (aka AuthUI -- you can see them in the `src/client/auth` folder).
|
||||
|
||||
## Email Verified Auth
|
||||
|
||||
`email` method is the default auth method in Open Saas.
|
||||
|
||||
Since it needs to send emails to verify users and reset passwords, it requires an [email sender](https://wasp-lang.dev/docs/advanced/email) provider: a service it can use to send emails.
|
||||
"email sender" provider is configured via `app.emailSender` field in the `main.wasp` file.
|
||||
|
||||
:::caution[Dummy Email Provider]
|
||||
To make it easy for you to get started, Open SaaS initially comes with the `Dummy` "email sender" provider, which does not send any emails, but instead logs all email verification links/tokens to the server's console!
|
||||
You can then follow these links to verify the user and continue with the sign-up process.
|
||||
|
||||
```tsx title="main.wasp"
|
||||
emailSender: {
|
||||
provider: Dummy, // logs all email verification links/tokens to the server's console
|
||||
defaultFrom: {
|
||||
name: "Open SaaS App",
|
||||
email: "me@example.com"
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
You **can not use the Dummy provider in production** and your app **will not build** until you move to a production-ready provider, such as SendGrid. We outline the process of migrating to SendGrid below.
|
||||
:::
|
||||
|
||||
In order to use the `email` auth method in production, you'll need to switch from the `Dummy` "email sender" provider to a production-ready provider like SendGrid:
|
||||
|
||||
1. First, set up your app's `emailSender` in the `main.wasp` file by following [this guide](/guides/email-sending/#integrate-your-email-sender).
|
||||
2. Add your `SENDGRID_API_KEY` to the `.env.server` file.
|
||||
3. Make sure the email address you use in the `fromField` object is the same email address that you configured your SendGrid account to send out emails with. In the end, your `main.wasp` file should look something like this:
|
||||
```ts title="main.wasp" {6,7} del={15} ins={16}
|
||||
auth: {
|
||||
methods: {
|
||||
email: {
|
||||
fromField: {
|
||||
name: "Open SaaS App",
|
||||
// When using SendGrid, you must use the same email address that you configured your account to send out emails with!
|
||||
email: "me@example.com"
|
||||
},
|
||||
//...
|
||||
},
|
||||
}
|
||||
},
|
||||
//...
|
||||
emailSender: {
|
||||
provider: Dummy,
|
||||
provider: SendGrid,
|
||||
defaultFrom: {
|
||||
name: "Open SaaS App",
|
||||
// When using SendGrid, you must use the same email address that you configured your account to send out emails with!
|
||||
email: "me@example.com"
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
And that's it. Wasp will take care of the rest and update your AuthUI components accordingly.
|
||||
|
||||
Check out the [Wasp Auth docs](https://wasp-lang.dev/docs/auth/overview) for more info.
|
||||
|
||||
## Google & GitHub Auth
|
||||
|
||||
We've also customized and pre-built the Google and GitHub auth flow for you. To start using them, you just need to uncomment out the methods you want in your `main.wasp` file and obtain the proper API keys to add to your `.env.server` file.
|
||||
|
||||
To create a Google OAuth app and get your Google API keys, follow the instructions in [Wasp's Google Auth docs](https://wasp-lang.dev/docs/auth/social-auth/google#3-creating-a-google-oauth-app).
|
||||
|
||||
To create a GitHub OAuth app and get your GitHub API keys, follow the instructions in [Wasp's GitHub Auth docs](https://wasp-lang.dev/docs/auth/social-auth/github#3-creating-a-github-oauth-app).
|
||||
|
||||
Again, Wasp will take care of the rest and update your AuthUI components accordingly.
|
||||
95
opensaas-sh/blog/src/content/docs/guides/authorization.md
Normal file
95
opensaas-sh/blog/src/content/docs/guides/authorization.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Authorization
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
This guide will help you get started with authorization in your SaaS app.
|
||||
|
||||
Authorization refers to what users can access in your app. This is useful for differentiating between users who have paid for different subscription tiers (e.g. "hobby" vs "pro"), or between users who have admin privileges and those who do not.
|
||||
|
||||
Authorization differs from [authentication](/guides/authentication) in that authentication refers to the process of verifying that a user is who they say they are (e.g. logging in with a username and password).
|
||||
|
||||
To learn more about the different types of user permissions built into this SaaS template, including Stripe subscription tiers and statuses, check out the [User Permissions Reference](/general/user-permissions).
|
||||
|
||||
Also, check out our [blog post](https://wasp-lang.dev/blog/2022/11/29/permissions-in-web-apps) to learn more about authorization (access control) in web apps.
|
||||
|
||||
### Client-side Authorization
|
||||
|
||||
Open Saas starts with all users having access to the landing page (`/`), but only authenticated users having access to the rest of the app (e.g. to the `/demo-app`, or to the `/account`).
|
||||
|
||||
To control which pages require users to be authenticated to access them, you can set the `authRequired` property of the corresponding `page` definition in your `main.wasp` file:
|
||||
|
||||
```tsx title="main.wasp" {3}
|
||||
route AccountRoute { path: "/account", to: AccountPage }
|
||||
page AccountPage {
|
||||
authRequired: true,
|
||||
component: import Account from "@src/client/app/AccountPage"
|
||||
}
|
||||
```
|
||||
|
||||
This will automatically redirect users to the login page if they are not logged in while trying to access that page.
|
||||
|
||||
:::caution[Client-side authorization is just for the looks]
|
||||
Users can manipulate the client code as they wish, meaning that client-side access control (authorization) serves the purpose of ergonomics/user experience, not the purpose of restricting access to sensitive data.
|
||||
This means that authorization in the client code is a nice-to-have: it is here to make sure users don't get lost in the part of the app they can't work with because data is missing due to them not having access, not to actually restrict them from doing something.
|
||||
Actually ensuring they don't have access to the data, that is on the server to ensure, via server-side logic that you will implement for authorization (access control).
|
||||
:::
|
||||
|
||||
If you want more fine-grained control over what users can access, there are two Wasp-specific options:
|
||||
1. When you define the `authRequired: true` property on the `page` definition, Wasp automatically passes the User object to the page component. Here you can check for certain user properties before authorizing access:
|
||||
|
||||
```tsx title="ExamplePage.tsx" "{ user }: { user: User }"
|
||||
import { type User } from "wasp/entities";
|
||||
|
||||
export default function Example({ user }: { user: User }) {
|
||||
|
||||
if (user.subscriptionStatus === 'past_due') {
|
||||
return (<span>Your subscription is past due. Please update your payment information.</span>)
|
||||
}
|
||||
if (user.subscriptionStatus === 'canceled') {
|
||||
return (<span>Your will susbscription end on 01.01.2024</span>)
|
||||
}
|
||||
if (user.subscriptionStatus === 'active') {
|
||||
return (<span>Thanks so much for your support!</span>)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
2. Or you can take advantage of the `useAuth` hook and check for certain user properties before authorizing access to certain pages or components:
|
||||
|
||||
```tsx title="ExamplePage.tsx" {1, 4}
|
||||
import { useAuth } from "wasp/client/auth";
|
||||
|
||||
export default function ExampleHomePage() {
|
||||
const { data: user } = useAuth();
|
||||
|
||||
return (
|
||||
<h1> Hi {user.email || 'there'} 👋 </h1>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Server-side Authorization
|
||||
|
||||
Authorization on the server-side is the core of your access control logic, and determines what users actually can or can't do (unlike client-side authorization logic which is there merely for UX).
|
||||
|
||||
You can authorize access to server-side operations by adding a check for a logged-in user on the `context.user` object which is passed to all operations in Wasp:
|
||||
|
||||
```tsx title="src/server/actions.ts"
|
||||
export const updateCurrentUser: UpdateCurrentUser<...> = async (args, context) => {
|
||||
if (!context.user) {
|
||||
throw new HttpError(401); // throw an error if user is not logged in
|
||||
}
|
||||
|
||||
if (context.user.subscriptionStatus === 'past_due') {
|
||||
throw new HttpError(403, 'Your subscription is past due. Please update your payment information.');
|
||||
}
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
160
opensaas-sh/blog/src/content/docs/guides/deploying.md
Normal file
160
opensaas-sh/blog/src/content/docs/guides/deploying.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: Deploying
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
Because this SaaS app is a React/NodeJS/Postgres app built on top of [Wasp](https://wasp-lang.dev), we will direct you to the [Wasp Deployment Guide](https://wasp-lang.dev/docs/advanced/deployment/overview/) for more detailed instructions, except for where the instructions are specific to this template.
|
||||
|
||||
The simplest and quickest option is to take advantage of Wasp's one-command deploy to [Fly.io](#deploying-to-flyio) (`wasp deploy`).
|
||||
|
||||
Or if you prefer to deploy to a different provider, or your frontend and backend separately, you can follow the [Deploying Manually](#deploying-manually--to-other-providers) section below.
|
||||
|
||||
If you're looking to deploy your Astro Blog, you can follow the [Deploying your Blog](#deploying-your-blog) section at the end of this guide.
|
||||
|
||||
## Deploying your App
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Make sure you've got all your API keys and environment variables set up before you deploy.
|
||||
|
||||
#### Env Vars
|
||||
##### Stripe Vars
|
||||
In the [Stripe integration guide](/guides/stripe-integration), you set up your Stripe API keys using test keys and product ids. You'll need to get the live/production versions of those keys at [https://dashboard.stripe.com](https://dashboard.stripe.com). To get these, repeat the instructions in the [Stripe Integration Guide](/guides/stripe-integration) without being in test mode.
|
||||
- [ ] `STRIPE_KEY`
|
||||
- [ ] `STRIPE_WEBHOOK_SECRET`
|
||||
- [ ] all `PRICE_ID` variables
|
||||
- [ ] `REACT_APP_STRIPE_CUSTOMER_PORTAL` (for the client-side)
|
||||
|
||||
##### Other Vars
|
||||
Many of your other environment variables will probably be the same as in development, but you should double-check that they are set correctly for production.
|
||||
|
||||
Here are a list of all of them (some of which you may not be using, e.g. Analytics, Social Auth) in case you need to check:
|
||||
###### General Vars
|
||||
- [ ] `DATABASE_URL`
|
||||
- [ ] `JWT_SECRET`
|
||||
- [ ] `WASP_WEB_CLIENT_URL`
|
||||
- [ ] `WASP_SERVER_URL`
|
||||
|
||||
###### Open AI API Key
|
||||
- [ ] `OPENAI_API_KEY`
|
||||
|
||||
###### Sendgrid API Key
|
||||
- [ ] `SENDGRID_API_KEY`
|
||||
|
||||
###### Social Auth Vars
|
||||
- [ ] `GOOGLE_CLIENT_ID`
|
||||
- [ ] `GOOGLE_CLIENT_SECRET`
|
||||
- [ ] `GITHUB_CLIENT_ID`
|
||||
- [ ] `GITHUB_CLIENT_SECRET`
|
||||
|
||||
###### Analytics Vars
|
||||
- [ ] `REACT_APP_PLAUSIBLE_ANALYTICS_ID` (for client-side)
|
||||
- [ ] `PLAUSIBLE_API_KEY`
|
||||
- [ ] `PLAUSIBLE_SITE_ID`
|
||||
- [ ] `PLAUSIBLE_BASE_URL`
|
||||
- [ ] `REACT_APP_GOOGLE_ANALYTICS_ID` (for client-side)
|
||||
- [ ] `GOOGLE_ANALYTICS_CLIENT_EMAIL`
|
||||
- [ ] `GOOGLE_ANALYTICS_PROPERTY_ID`
|
||||
- [ ] `GOOGLE_ANALYTICS_PRIVATE_KEY`
|
||||
(Make sure you convert the private key within the JSON file to base64 first with `echo -n "PRIVATE_KEY" | base64`. See the [Analytics docs](/guides/analytics/#google-analytics) for more info)
|
||||
|
||||
###### AWS S3 Vars
|
||||
- [ ] `AWS_S3_IAM_ACCESS_KEY`
|
||||
- [ ] `AWS_S3_IAM_SECRET_KEY`
|
||||
- [ ] `AWS_S3_FILES_BUCKET`
|
||||
- [ ] `AWS_S3_REGION`
|
||||
|
||||
### Deploying to Fly.io
|
||||
|
||||
[Fly.io](https://fly.io) is a platform for running your apps globally. It's a great choice for deploying your SaaS app because it's free to get started, can host your entire full-stack app in one place, scales well, and has one-command deploy integration with Wasp.
|
||||
|
||||
**Wasp provides the handy `wasp deploy` command to deploy your entire full-stack app (DB, server, and client) in one command.**
|
||||
|
||||
To learn how, please follow the detailed guide for [deploying to Fly via the Wasp CLI](https://wasp-lang.dev/docs/advanced/deployment/cli) from the Wasp documentation. We suggest you follow this guide carefully to get your app deployed.
|
||||
|
||||
:::caution[Setting Environment Variables]
|
||||
Remember, because we've set certain client-side env variables, make sure to pass them to the `wasp deploy` commands so that they can be included in the build:
|
||||
```sh
|
||||
REACT_APP_CLIENT_ENV_VAR_1=<...> REACT_APP_CLIENT_ENV_VAR_2=<...> wasp deploy
|
||||
```
|
||||
|
||||
The `wasp deploy` command will also take care of setting the following server-side environment variables for you so you don't have to:
|
||||
- `DATABASE_URL`
|
||||
- `PORT`
|
||||
- `JWT_SECRET`
|
||||
- `WASP_WEB_CLIENT_URL`
|
||||
- `WASP_SERVER_URL`
|
||||
|
||||
For setting the remaining server-side environment variables, please refer to the [Deploying with the Wasp CLI Guide](https://wasp-lang.dev/docs/advanced/deployment/cli#launch).
|
||||
:::
|
||||
|
||||
### Deploying Manually / to Other Providers
|
||||
|
||||
If you prefer to deploy manually, your frontend and backend separately, or just prefer using your favorite provider you can follow [Wasp's Manual Deployment Guide](https://wasp-lang.dev/docs/advanced/deployment/manually).
|
||||
|
||||
:::caution[Client-side Environment Variables]
|
||||
Remember to always set additional client-side environment variables, such as `REACT_APP_STRIPE_CUSTOMER_PORTAL` by appending them to the build command, e.g.
|
||||
```sh
|
||||
REACT_APP_CLIENT_ENV_VAR_1=<...> npm run build
|
||||
```
|
||||
:::
|
||||
|
||||
### Adding Server Redirect URL's to Social Auth
|
||||
|
||||
After deploying your server, you need to add the correct redirect URIs to the credential settings. For this, refer to the following guides from the Wasp Docs:
|
||||
|
||||
- [Google Auth](https://wasp-lang.dev/docs/auth/social-auth/google#3-creating-a-google-oauth-app:~:text=Under%20Authorized%20redirect%20URIs)
|
||||
- [Github Auth](https://wasp-lang.dev/docs/auth/social-auth/github#3-creating-a-github-oauth-app:~:text=Authorization%20callback%20URL)
|
||||
|
||||
### Setting up your Stripe Webhook
|
||||
|
||||
Now you need to set up your stripe webhook for production use.
|
||||
|
||||
1. go to [https://dashboard.stripe.com/webhooks](https://dashboard.stripe.com/webhooks)
|
||||
2. click on `+ add endpoint`
|
||||
3. enter your endpoint url, which will be the url of your deployed server + `/stripe-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/stripe-webhook`
|
||||

|
||||
4. select the events you want to listen to. These should be the same events you're consuming in your webhook. For example, if you haven't added any additional events to the webhook and are using the defaults that came with this template, then you'll need to add:
|
||||
<br/>- `account.updated`
|
||||
<br/>- `checkout.session.completed`
|
||||
<br/>- `customer.subscription.deleted`
|
||||
<br/>- `customer.subscription.updated`
|
||||
<br/>- `invoice.paid`
|
||||

|
||||
5. after that, go to the webhook you just created and `reveal` the new signing secret.
|
||||
6. add this secret to your deployed server's `STRIPE_WEBHOOK_SECRET=` environment variable. <br/>If you've deployed to Fly.io, you can do that easily with the following command:
|
||||
```sh
|
||||
wasp deploy fly cmd --context server secrets set STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
```
|
||||
|
||||
## Deploying your Blog
|
||||
|
||||
Deploying your Astro Starlight blog is a bit different than deploying your SaaS app. As an example, we will show you how to deploy your blog for free to Netlify. You will need a Netlify account and [Netlify CLI](https://docs.netlify.com/cli/get-started/) installed to follow these instructions.
|
||||
|
||||
Make sure you are logged in with Netlify CLI.
|
||||
- You can check if you are logged in with `netlify status`,
|
||||
- you can log in with `netlify login`.
|
||||
|
||||
Position yourself in the `blog` directory and run the following command:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
This will build your blog into the `blog/dist` directory. Now you can deploy your blog to Netlify with the following command:
|
||||
|
||||
```sh
|
||||
netlify deploy
|
||||
```
|
||||
|
||||
Select the `dist` directory as the deploy path.
|
||||
|
||||
Finally, if the deployment looks good, you can deploy your blog to production with the following command:
|
||||
|
||||
```sh
|
||||
netlify deploy --prod
|
||||
```
|
||||
|
||||
97
opensaas-sh/blog/src/content/docs/guides/email-sending.mdx
Normal file
97
opensaas-sh/blog/src/content/docs/guides/email-sending.mdx
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: Email Sending
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
This guide explains how to use the integrated email sender and how you can integrate your own account in this template.
|
||||
|
||||
## Sending Emails
|
||||
|
||||
### The `Dummy` Email Provider (for Local Dev Only)
|
||||
By default we've set up the email sender to use the `Dummy` provider. This is **for local development only** and no emails will actually be sent out!
|
||||
To obtain an email verification token/link, you must check the server logs on initial sign up. You can click this link to verify your email and continue with the sign up process.
|
||||
```tsx title="main.wasp"
|
||||
app SaaSTemplate {
|
||||
// ...
|
||||
emailSender: {
|
||||
provider: Dummy,
|
||||
defaultFrom: {
|
||||
name: "Open SaaS App",
|
||||
email: "me@example.com"
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Note that your app will not build if using the `Dummy` provider and you must switch to a production-ready provider in order to do so.
|
||||
|
||||
### Using a Production-Ready Email Provider (e.g. SendGrid)
|
||||
To change your email provider to a production-ready one, such as SendGrid, you'll want to configure your `emailSender` like so:
|
||||
|
||||
```tsx title="main.wasp"
|
||||
app SaaSTemplate {
|
||||
// ...
|
||||
emailSender: {
|
||||
provider: SendGrid,
|
||||
defaultFrom: {
|
||||
name: "Open SaaS App",
|
||||
// When using SendGrid, you must use the same email address that you configured your account to send out emails with!
|
||||
email: "me@example.com"
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
This means that you can send emails from your app using the `send` function from the `email` modul provided by Wasp:
|
||||
|
||||
```tsx title="src/server/webhooks.ts"
|
||||
import { emailSender } from "wasp/server/email";
|
||||
|
||||
//...
|
||||
|
||||
if (subscription.cancel_at_period_end) {
|
||||
await emailSender.send({
|
||||
to: customer.email,
|
||||
subject: 'We hate to see you go :(',
|
||||
text: 'We hate to see you go. Here is a sweet offer...',
|
||||
html: 'We hate to see you go. Here is a sweet offer...',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, you can see that we're sending an email to the customer when we receive a cancel subscription event within the Stripe webhook.
|
||||
|
||||
This is a powerful feature and super simple to use.
|
||||
|
||||
## Integrate your email sender
|
||||
|
||||
To set up your email sender, you first need an account with one of the supported email providers.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="SendGrid">
|
||||
- Register at SendGrid.com and then get your [API KEYS](https://app.sendgrid.com/settings/api_keys).
|
||||
- Copy yours to the `.env.server` file under the `SENDGRID_API_KEY` variable.
|
||||
</TabItem>
|
||||
<TabItem label="MailGun">
|
||||
- Go to [Mailgun](https://mailgun.com) and create an account.
|
||||
- Go to [API Keys](https://app.mailgun.com/app/account/security/api_keys) and create a new API key.
|
||||
- Copy the API key and add it to your .env.server file under the `MAILGUN_API_KEY=` variable.
|
||||
- Go to [Domains](https://app.mailgun.com/app/domains) and create a new domain.
|
||||
- Copy the domain and add it to your .env.server file as `MAILGUN_DOMAIN=`.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Make sure to change the `defaultFrom` email address in the `main.wasp` file to use the same email address that you configured your account to send out emails with!
|
||||
|
||||
```tsx title="main.wasp" {5}
|
||||
emailSender: {
|
||||
provider: SendGrid,
|
||||
defaultFrom: {
|
||||
name: "Open SaaS App",
|
||||
email: "me@example.com" // <--- same email address you configured your SendGrid account to send emails with!
|
||||
},
|
||||
```
|
||||
|
||||
If you want more detailed info, or would like to use SMTP, check out the [Wasp docs](https://wasp-lang.dev/docs/advanced/email).
|
||||
151
opensaas-sh/blog/src/content/docs/guides/file-uploading.md
Normal file
151
opensaas-sh/blog/src/content/docs/guides/file-uploading.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: File Uploading
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
This guide will show you how to set up file uploading in your SaaS app.
|
||||
|
||||
There are two options we recommend:
|
||||
1. Using [AWS S3](https://aws.amazon.com/s3/) with presigned URLS for secure file storage
|
||||
2. Using Multer middleware to upload files to your own server
|
||||
|
||||
**We recommend using AWS S3 as it's a scalable, secure option, that can handle a large amount of storage.**
|
||||
|
||||
If you're just looking to upload small files and don't expect your app to grow to a large scale, you can use Multer to upload files to your app's server.
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
|
||||
## Using AWS S3
|
||||
|
||||
### How presigned URLs work
|
||||
|
||||
Presigned URLs are URLs that have been signed with your AWS credentials and can be used to upload files to your S3 bucket. They are time-limited and can be generated on the server and sent to the client to upload files directly to S3.
|
||||
|
||||
The process of generating a presigned URL is as follows:
|
||||
1. The client sends a request to the server to upload a file
|
||||
2. The server generates a presigned URL using its AWS credentials
|
||||
3. The server sends the presigned URL to the client
|
||||
4. The client uses the presigned URL to upload the file directly to S3 before the URL expires
|
||||
|
||||
We use this method to upload files to S3 because it is more secure than uploading files directly from the client to S3. It also allows us to keep our AWS credentials private and not expose them to the client.
|
||||
|
||||
To use presigned URLs, we'll need to set up an S3 bucket and get our AWS credentials.
|
||||
|
||||
### Create an AWS Account
|
||||
|
||||
Before you begin, you'll need to create an AWS account. AWS accounts are free to create and are split up into:
|
||||
1. Root account
|
||||
2. IAM users
|
||||
|
||||
You'll need to first create a root account, and then an IAM user for your SaaS app before you can start uploading files to S3.
|
||||
|
||||
To do so, follow the steps in this external guide: [Creating IAM users and S3 buckets in AWS](https://medium.com/@emmanuelnwright/create-iam-users-and-s3-buckets-in-aws-264e78281f7f)
|
||||
|
||||
### Create an AWS S3 Bucket
|
||||
|
||||
Once you are logged in with your IAM user, you'll need to create an S3 bucket to store your files.
|
||||
|
||||
1. Navigate to the S3 service in the AWS console
|
||||

|
||||
2. Click on the `Create bucket` button
|
||||

|
||||
3. Fill in the bucket name and region
|
||||
4. **Leave all the settings as default** and click `Create bucket`
|
||||

|
||||
|
||||
### Change the CORS settings
|
||||
|
||||
Now we need to change some permissions on the bucket to allow for file uploads from your app.
|
||||
|
||||
1. Click on the bucket you just created
|
||||

|
||||
2. Click on the `Permissions` tab
|
||||

|
||||
3. Scroll down to the `Cross-origin resource sharing (CORS)` section and click `Edit`
|
||||

|
||||
5. Paste the following CORS configuration and click `Save changes`:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"AllowedHeaders": [
|
||||
"*"
|
||||
],
|
||||
"AllowedMethods": [
|
||||
"PUT",
|
||||
"GET"
|
||||
],
|
||||
"AllowedOrigins": [
|
||||
"*"
|
||||
],
|
||||
"ExposeHeaders": []
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get your AWS S3 credentials
|
||||
|
||||
Now that you have your S3 bucket set up, you'll need to get your S3 credentials to use in your app.
|
||||
|
||||
1. Click on your username in the top right corner of the AWS console and select `Security Credentials`
|
||||

|
||||
2. Scroll down to the `Access keys` section
|
||||
3. Click on `Create Access Key`
|
||||
4. Select the `Application running on an AWS service` option and create the access key
|
||||

|
||||
5. Copy the `Access key ID` and `Secret access key` and paste them in your `src/app/.env.server` file:
|
||||
```sh
|
||||
AWS_S3_IAM_ACCESS_KEY=ACK...
|
||||
AWS_S3_IAM_SECRET_KEY=t+33a...
|
||||
AWS_S3_FILES_BUCKET=your-bucket-name
|
||||
AWS_S3_REGION=your-region // (e.g. us-west-2)
|
||||
```
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
|
||||
### Using and Customizing File Uploads with S3 in your App
|
||||
|
||||
With your S3 bucket set up and your AWS credentials in place, you can now start uploading files in your app using presigned URLs by navigating to `localhost:3000/file-upload` and uploading a file.
|
||||
|
||||
To begin customizing file uploads, is important to know where everything lives in your app. Here's a quick overview:
|
||||
- `main.wasp`:
|
||||
- The `File entity` can be found here. Here you can modify the fields to suit your needs:
|
||||
```c
|
||||
entity File {=psl
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
type String
|
||||
key String
|
||||
uploadUrl String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
createdAt DateTime @default(now())
|
||||
psl=}
|
||||
```
|
||||
- `src/server/actions.ts`:
|
||||
- The `createFile` action lives here and calls the `getUploadFileSignedURLFromS3` within it using your AWS credentials before passing it to the client. This function stores the files in the S3 bucket within folders named after the user's ID, so that each user's files are stored separately.
|
||||
- `src/server/queries.ts`:
|
||||
- The `getAllFilesByUser` fetches all File information uploaded by the user. Note that the files do not exist in the app database, but rather the file data, name its `key`, which is used to fetch the file from S3
|
||||
- The `getDownloadFileSignedURL` query fetches the presigned URL for a file to be downloaded from S3 using the file's `key` stored in the app's database
|
||||
- `src/client/app/FileUploadPage.tsx`:
|
||||
- The `FileUploadPage` component is where the file upload form lives. It also allows you to download the file from S3 by calling the `getDownloadFileSignedURL` based on that files `key` in the app DB.
|
||||
|
||||
## Using Multer to upload files to your server
|
||||
|
||||
If you're looking to upload files to the app server, you can use the Multer middleware to handle file uploads. This will allow you to store files on your server and is a good option if you need a quick and dirty, free solution for simple file uploads.
|
||||
|
||||
Below are GitHub Gists that show you how to set up file uploads using Multer in your app:
|
||||
|
||||
### Wasp version 0.12 & higher
|
||||
|
||||
<script src="https://gist.github.com/infomiho/ec379df4e33f3ae3410a251ba3aa81af.js"></script>
|
||||
68
opensaas-sh/blog/src/content/docs/guides/seo.md
Normal file
68
opensaas-sh/blog/src/content/docs/guides/seo.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: SEO
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
This guides explains how to improve SEO for of your app
|
||||
|
||||
## Landing Page Meta Tags
|
||||
|
||||
Wasp gives you the ability to add meta tags to your landing page HTML via the `main.wasp` file's `head` property:
|
||||
|
||||
```js {8-11}
|
||||
app SaaSTemplate {
|
||||
wasp: {
|
||||
version: "^0.13.0"
|
||||
},
|
||||
title: "Open SaaS",
|
||||
head: [
|
||||
"<meta property='og:type' content='website' />",
|
||||
"<meta property='og:url' content='https://opensaas.sh' />",
|
||||
"<meta property='og:title' content='Open SaaS' />",
|
||||
"<meta property='og:description' content='Free, open-source SaaS boilerplate starter for React & NodeJS.' />",
|
||||
"<meta property='og:image' content='https://opensaas.sh/public-banner.png' />",
|
||||
//...
|
||||
],
|
||||
//...
|
||||
```
|
||||
|
||||
Change the above highlighted meta tags to match your app. Wasp will inject these tags into the HTML of your `index.html` file, which is the Landing Page (`app/src/client/landing-page/LandingPage.tsx`), in this case.
|
||||
|
||||
This means you **do not** need to rely on a seperate app or framework to serve your landing page for SEO purposes.
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
|
||||
## Docs & Blog Meta Tags
|
||||
|
||||
Astro, being a static-site generator, will automatically inject relevant information provided in the `blog/astro.config.mjs` file, as well as in the frontmatter of `.md` files into the pages HTML:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 'My First Blog Post'
|
||||
pubDate: 2022-07-01
|
||||
description: 'This is the first post of my new Astro blog.'
|
||||
author: 'Astro Learner'
|
||||
image:
|
||||
url: 'https://docs.astro.build/assets/full-logo-light.png'
|
||||
alt: 'The full Astro logo.'
|
||||
tags: ["astro", "blogging", "learning in public"]
|
||||
---
|
||||
```
|
||||
|
||||
Improving your SEO is as simple as adding these properties to your docs and blog content!
|
||||
|
||||
## A Word on SSR & SEO
|
||||
|
||||
Open SaaS and Wasp do not currently have a SSR option (although it is coming soon!), but that does not mean that Open SaaS apps are at a disadvantage with regards to SEO.
|
||||
|
||||
That's because the meta tags for the landing page (described above), plus the Astro docs/blog provided with Open SaaS are more than enough! Not to mention, Google is also able to crawl websites with JavaScript activated, making SSR unnecessary.
|
||||
|
||||
For example, try searching "Open SaaS" on Google and you'll see this App, which was built with this template, as the first result!
|
||||

|
||||
108
opensaas-sh/blog/src/content/docs/guides/stripe-integration.md
Normal file
108
opensaas-sh/blog/src/content/docs/guides/stripe-integration.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: Stripe Integration
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
This guide will show you how to set up your Stripe account for testing and local development.
|
||||
|
||||
Once you deploy your app, you can follow the same steps, just make sure you're using your live Stripe API keys and product IDs and you are no longer in test mode within the Stripe Dashboard.
|
||||
|
||||
To get started, you'll need to create a Stripe account. You can do that [here](https://dashboard.stripe.com/register).
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
|
||||
## Get your test Stripe API Keys
|
||||
|
||||
Once you've created your account, you'll need to get your test API keys. You can do that by navigating to [https://dashboard.stripe.com/test/apikeys](https://dashboard.stripe.com/test/apikeys) or by going to the [Stripe Dashboard](https://dashboard.stripe.com/test/dashboard) and clicking on the `Developers`.
|
||||
|
||||

|
||||
|
||||
- Click on the `Reveal test key token` button and copy the `Secret key`.
|
||||
- Paste it in your `.env.server` file under `STRIPE_KEY=`
|
||||
|
||||
## Create Test Products
|
||||
|
||||
To create a test product, go to the test products url [https://dashboard.stripe.com/test/products](https://dashboard.stripe.com/test/products), or after navigating to your dashboard, click the `test mode` toggle.
|
||||
|
||||

|
||||
|
||||
- Click on the `Add a product` button and fill in the relevant information for your product.
|
||||
- Make sure you select `Software as a service (SaaS)` as the product type.
|
||||
- For Subscription products, make sure you select `Recurring` as the billing type.
|
||||
- For One-time payment products, make sure you select `One-time` as the billing type.
|
||||
- If you want to add different price tiers for the same product, click the `Add another price` button at the buttom.
|
||||
|
||||

|
||||
|
||||
- After you save the product, you'll be directed to the product page.
|
||||
- Copy the price IDs and paste them in the `.env.server` file
|
||||
- We've set you up with two example subscription product environment variables, `HOBBY_SUBSCRIPTION_PRICE_ID=` and `PRO_SUBSCRIPTION_PRICE_ID=`.
|
||||
- As well as a one-time payment product/credits-based environment variable, `CREDITS_PRICE_ID=`.
|
||||
- Note that if you change the names of the price IDs, you'll need to update your server code to match these names as well
|
||||
|
||||
## Create a Test Customer
|
||||
|
||||
To create a test customer, go to the test customers url [https://dashboard.stripe.com/test/customers](https://dashboard.stripe.com/test/customers).
|
||||
|
||||
- Click on the `Add a customer` button and fill in the relevant information for your test customer.
|
||||
:::note
|
||||
When filling in the test customer email address, use an address you have access to and will use when logging into your SaaS app. This is important because the email address is used to identify the customer when creating a subscription and allows you to manage your test user's payments/subscriptions via the test customer portal
|
||||
:::
|
||||
|
||||
## Get your Customer Portal Link
|
||||
|
||||
Go to https://dashboard.stripe.com/test/settings/billing/portal in the Stripe Dashboard and activate and copy the `Customer portal link`. Paste it in your `.env.client` file:
|
||||
|
||||
```ts title=".env.client"
|
||||
REACT_APP_STRIPE_CUSTOMER_PORTAL=<your-test-customer-portal-link>
|
||||
```
|
||||
|
||||
Your Stripe customer portal link is imported into `src/client/app/AccountPage.tsx` and used to redirect users to the Stripe customer portal when they click the `Manage Subscription` button.
|
||||
|
||||
```tsx title="src/client/app/AccountPage.tsx" {5} "import.meta.env.REACT_APP_STRIPE_CUSTOMER_PORTAL"
|
||||
function CustomerPortalButton() {
|
||||
const handleClick = () => {
|
||||
try {
|
||||
const schema = z.string().url();
|
||||
const customerPortalUrl = schema.parse(import.meta.env.REACT_APP_STRIPE_CUSTOMER_PORTAL);
|
||||
window.open(customerPortalUrl, '_blank');
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
:::danger[Client-side Environment Variables]
|
||||
Client-side environment variables, unlike server-side environment variables, should never be used to store sensitive information as they are injected at build time and are exposed to the client.
|
||||
:::
|
||||
|
||||
## Install the Stripe CLI
|
||||
|
||||
To install the Stripe CLI with homebrew, run the following command in your terminal:
|
||||
|
||||
```sh
|
||||
brew install stripe/stripe-cli/stripe
|
||||
```
|
||||
|
||||
or for other install scripts or OSes, follow the instructions [here](https://stripe.com/docs/stripe-cli#install).
|
||||
|
||||
Now, let's start the webhook server and get our webhook signing secret.
|
||||
|
||||
```sh
|
||||
stripe listen --forward-to localhost:3001/stripe-webhook
|
||||
```
|
||||
|
||||
You should see a message like this:
|
||||
|
||||
```sh
|
||||
> Ready! You are using Stripe API Version [2023-08-16]. Your webhook signing secret is whsec_8a... (^C to quit)
|
||||
```
|
||||
|
||||
copy this secret to your `.env.server` file under `STRIPE_WEBHOOK_SECRET=`.
|
||||
78
opensaas-sh/blog/src/content/docs/guides/stripe-testing.md
Normal file
78
opensaas-sh/blog/src/content/docs/guides/stripe-testing.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Stripe Testing
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
This guide will show you how to test and try out your checkout, payments, and webhooks locally.
|
||||
|
||||
First, make sure you've set up your Stripe account for local development. You can find the guide [here](/guides/stripe-integration).
|
||||
|
||||
## Testing Webhooks via the Stripe CLI
|
||||
|
||||
- In a new terminal window, run the following command:
|
||||
|
||||
```sh
|
||||
stripe login
|
||||
```
|
||||
|
||||
- start the Stripe CLI webhook forwarding on port 3001 where your Node server is running.
|
||||
|
||||
```sh
|
||||
stripe listen --forward-to localhost:3001/stripe-webhook
|
||||
```
|
||||
|
||||
remember to copy and paste the outputted webhook signing secret (`whsec_...`) into your `.env.server` file under `STRIPE_WEBHOOK_SECRET=` if you haven't already.
|
||||
|
||||
- In another terminal window, trigger a test event:
|
||||
|
||||
```sh
|
||||
stripe trigger payment_intent.succeeded
|
||||
```
|
||||
|
||||
The results of the event firing will be visible in the initial terminal window. You should see messages like this:
|
||||
|
||||
```sh
|
||||
...
|
||||
2023-11-21 09:31:09 --> invoice.paid [evt_1OEpMPILOQf67J5TjrUgRpk4]
|
||||
2023-11-21 09:31:09 <-- [200] POST http://localhost:3001/stripe-webhook [evt_1OEpMPILOQf67J5TjrUgRpk4]
|
||||
2023-11-21 09:31:10 --> invoice.payment_succeeded [evt_1OEpMPILOQf67J5T3MFBr1bq]
|
||||
2023-11-21 09:31:10 <-- [200] POST http://localhost:3001/stripe-webhook [evt_1OEpMPILOQf67J5T3MFBr1bq]
|
||||
2023-11-21 09:31:10 --> checkout.session.completed [evt_1OEpMQILOQf67J5ThTZ0999r]
|
||||
2023-11-21 09:31:11 <-- [200] POST http://localhost:3001/stripe-webhook [evt_1OEpMQILOQf67J5ThTZ0999r]
|
||||
```
|
||||
|
||||
For more info on testing webhooks, check out https://stripe.com/docs/webhooks#test-webhook
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
|
||||
## Testing Checkout and Payments via the Client
|
||||
|
||||
Make sure the **Stripe CLI is running** by following the steps above.
|
||||
You can then test the payment flow via the client by doing the following:
|
||||
|
||||
- Click on a Buy button on the for any of the products on the homepage. You should be redirected to the checkout page.
|
||||
- Fill in the form with the following test credit card number `4242 4242 4242 4242` and any future date for the expiration date and any 3 digits for the CVC.
|
||||
|
||||
- Click on the "Pay" button. You should be redirected to the success page.
|
||||
|
||||
- Check your terminal window for status messages and logs
|
||||
|
||||
- You can also check your Database via the DB Studio to see if the user entity has been updated by running:
|
||||
|
||||
```sh
|
||||
wasp db studio
|
||||
```
|
||||
|
||||

|
||||
|
||||
- Navigate to `localhost:5555` and click on the `users` table. You should see the `subscriptionStatus` is `active` for the user that just made the purchase.
|
||||
|
||||
:::note
|
||||
If you want to learn more about how a user's payment status, subscription status, and subscription tier affect a user's priveledges within the app, check out the [User Overview](/general/user-overview) reference.
|
||||
:::
|
||||
60
opensaas-sh/blog/src/content/docs/index.md
Normal file
60
opensaas-sh/blog/src/content/docs/index.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Introduction
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
## Welcome to your new SaaS App!
|
||||
|
||||
<!-- {/* TODO: add a screenshot of the app */} -->
|
||||
|
||||
You've decided to build a SaaS app with this template. Great choice! 🎉
|
||||
|
||||
This template is:
|
||||
|
||||
1. fully open-source
|
||||
2. completely free to use and distribute
|
||||
3. comes with a ton of features out of the box!
|
||||
|
||||
Check it out in action here: [OpenSaaS.sh](https://opensaas.sh)
|
||||
Check out the Code: [Open SaaS GitHub Repo](https://github.com/wasp-lang/open-saas)
|
||||
|
||||
:::tip[FREE & OPEN-SOURCE!? 🌟]
|
||||
That's right. Use this template however you like. No strings attached.
|
||||
|
||||
If you find this template useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp). It helps us to keep bringing you open-source software just like this!
|
||||
:::
|
||||
|
||||
## What's inside?
|
||||
|
||||
The template itself is built on top of some very powerful tools and frameworks, including:
|
||||
|
||||
- 🐝 [Wasp](https://wasp-lang.dev) - a full-stack React, NodeJS, Prisma framework with superpowers
|
||||
- 🚀 [Astro](https://starlight.astro.build/) - Astro's lightweight "Starlight" template for documentation and blog
|
||||
- 💸 [Stripe](https://stripe.com) - for products and payments
|
||||
- 📈 [Plausible](https://plausible.io) or [Google](https://analytics.google.com/) Analytics
|
||||
- 🤖 [OpenAI](https://openai.com) - OpenAI API integrated into the app or [Replicate](https://replicate.com/) (coming soon 👀)
|
||||
- 📦 [AWS S3](https://aws.amazon.com/s3/) - for file uploads
|
||||
- 📧 [SendGrid](https://sendgrid.com), [MailGun](https://mailgun.com), or SMTP - for email sending
|
||||
- 💅 [TailwindCSS](https://tailwindcss.com) - for styling
|
||||
- 🧑💼 [TailAdmin](https://tailadmin.com/) - admin dashboard & components for TailwindCSS
|
||||
|
||||
Because we're using Wasp as the full-stack framework, we can leverage a lot of its features to build our SaaS in record time, including:
|
||||
|
||||
- 🔐 [Full-stack Authentication](https://wasp-lang.dev/docs/auth/overview) - Email verified + social Auth in a few lines of code.
|
||||
- ⛑ [End-to-end Type Safety](https://wasp-lang.dev/docs/data-model/operations/overview) - Type your backend functions and get inferred types on the front-end automatically, without the need to install or configure any third-party libraries. Oh, and type-safe Links, too!
|
||||
- 🤖 [Jobs](https://wasp-lang.dev/docs/advanced/jobs) - Run cron jobs in the background or set up queues simply by defining a function in the config file.
|
||||
- 🚀 [One-command Deploy](https://wasp-lang.dev/docs/advanced/deployment/overview) - Easily deploy via the CLI to [Fly.io](https://fly.io), or to other providers like [Railway](https://railway.app) and [Netlify](https://netlify.com).
|
||||
|
||||
You also get access to Wasp's diverse, helpful community if you get stuck or need help.
|
||||
- 🤝 [Wasp Discord](https://discord.gg/rzdnErX)
|
||||
|
||||
:::caution["Work In Progress"]
|
||||
We've tried to get as many of the core features of a SaaS app into this template as possible, but there still might be some missing features or functionality.
|
||||
|
||||
We could always use some help tying up loose ends, so consider [contributing](https://github.com/wasp-lang/open-saas/blob/main/CONTRIBUTING.md)!
|
||||
:::
|
||||
|
||||
In the next sections, we'll get our SaaS app started and tour its features. Let's get started!
|
||||
227
opensaas-sh/blog/src/content/docs/start/getting-started.md
Normal file
227
opensaas-sh/blog/src/content/docs/start/getting-started.md
Normal file
@@ -0,0 +1,227 @@
|
||||
---
|
||||
title: Getting Started
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
This guide will help you get your new SaaS app up and running.
|
||||
|
||||
## Install Wasp
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
You must have Node.js (and NPM) installed on your machine and available in `PATH` to use Wasp.
|
||||
Your version of Node.js must be >= 18.
|
||||
|
||||
To switch easily between Node.js versions, we recommend using [nvm](https://github.com/nvm-sh/nvm).
|
||||
|
||||
:::note[Installing and using nvm]
|
||||
<details>
|
||||
<summary>
|
||||
Need help with nvm?
|
||||
</summary>
|
||||
<div>
|
||||
|
||||
Install nvm via your OS package manager (`apt`, `pacman`, `homebrew`, ...) or via the [nvm](https://github.com/nvm-sh/nvm#install--update-script) install script.
|
||||
|
||||
Then, install a version of Node.js that you need:
|
||||
|
||||
```shell
|
||||
nvm install 20
|
||||
```
|
||||
|
||||
Finally, whenever you need to ensure a specific version of Node.js is used, run:
|
||||
|
||||
```shell
|
||||
nvm use 20
|
||||
```
|
||||
|
||||
to set the Node.js version for the current shell session.
|
||||
|
||||
You can run
|
||||
|
||||
```shell
|
||||
node -v
|
||||
```
|
||||
|
||||
to check the version of Node.js currently being used in this shell session.
|
||||
|
||||
Check NVM repo for more details: https://github.com/nvm-sh/nvm.
|
||||
|
||||
</div>
|
||||
</details>
|
||||
:::
|
||||
|
||||
|
||||
### Linux and macOS
|
||||
|
||||
Open your terminal and run:
|
||||
|
||||
```shell
|
||||
curl -sSL https://get.wasp-lang.dev/installer.sh | sh
|
||||
```
|
||||
|
||||
:::caution[Bad CPU type in executable]
|
||||
<details>
|
||||
<summary>
|
||||
Are you getting this error on a Mac (Apple Silicon)?
|
||||
</summary>
|
||||
Given that the wasp binary is built for x86 and not for arm64 (Apple Silicon), you'll need to install <a href='https://support.apple.com/en-us/HT211861'>Rosetta on your Mac</a> if you are using a Mac with Mx (M1, M2, ...). Rosetta is a translation process that enables users to run applications designed for x86 on arm64 (Apple Silicon). To install Rosetta, run the following command in your terminal
|
||||
|
||||
```bash
|
||||
softwareupdate --install-rosetta
|
||||
```
|
||||
Once Rosetta is installed, you should be able to run Wasp without any issues.
|
||||
:::
|
||||
|
||||
</details>
|
||||
|
||||
### Windows
|
||||
|
||||
In order to use Wasp on Windows, you need to install WSL2 (Windows Subsystem for Linux) and a Linux distribution of your choice. We recommend using Ubuntu.
|
||||
|
||||
**You can refer to this [article](https://wasp-lang.dev/blog/2023/11/21/guide-windows-development-wasp-wsl) for a step by step guide to using Wasp in the WSL environment.** If you need further help, reach out to us on [Discord](https://discord.gg/rzdnErX).
|
||||
|
||||
Once in WSL2, run the following command in your **WSL2 environment**:
|
||||
```sh
|
||||
curl -sSL https://get.wasp-lang.dev/installer.sh | sh
|
||||
```
|
||||
|
||||
:::caution[WSL2 and file system issues]
|
||||
<details>
|
||||
<summary>
|
||||
Are you getting file system issues using WSL2?
|
||||
</summary>
|
||||
If you are using WSL2, make sure that your Wasp project is not on the Windows file system, <b>but instead on the Linux file system</b>. Otherwise, Wasp won't be able to detect file changes, due to this <a href='https://github.com/microsoft/WSL/issues/4739'>issue in WSL2</a>.
|
||||
</details>
|
||||
:::
|
||||
|
||||
### Finalize Installation
|
||||
|
||||
Run the following command to verify that Wasp was installed correctly:
|
||||
|
||||
```shell
|
||||
wasp version
|
||||
```
|
||||
|
||||
Also be sure to install the Wasp VSCode extension to get the best DX, e.g. syntax highlighting, code scaffolding, autocomplete, etc.
|
||||
|
||||
:::tip[Installing the Wasp VSCode Extension]
|
||||
You can install the Wasp VSCode extension by searching for "Wasp" in the Extensions tab in VSCode, or by visiting the 🐝 [Wasp VSCode Extension](https://marketplace.visualstudio.com/items?itemName=wasp-lang.wasp) 🧑💻 homepage
|
||||
:::
|
||||
|
||||
## Setting up your SaaS app
|
||||
|
||||
### Cloning the OpenSaaS template
|
||||
|
||||
From the directory where you'd like to create your new project run:
|
||||
```sh
|
||||
wasp new
|
||||
```
|
||||
|
||||
Then select option `[3] saas` from the list of templates after entering the name of your project.
|
||||
|
||||
This will clone a **clean copy of the Open SaaS template** into a new directory! 🎉
|
||||
|
||||
### Start your DB
|
||||
|
||||
Before you start your app, you need to have a Postgres Database connected and running. With Wasp, that's super easy!
|
||||
|
||||
First, make sure you have **Docker installed and running**. If not, download and install it [here](https://www.docker.com/products/docker-desktop/)
|
||||
|
||||
With Docker running, open a new terminal window/tab and position yourself in the `app` directory:
|
||||
|
||||
```sh
|
||||
cd app
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
wasp start db
|
||||
```
|
||||
|
||||
This will start and connect your app to a Postgres database for you. No need to do anything else! 🤯 Just make sure to leave this terminal window open in the background while developing. Once you terminate the process, your DB will no longer be available to your app.
|
||||
|
||||
Now let's create our very first database migration, to ensure the database has a correct schema. Open a new terminal tab/window and run the following command:
|
||||
|
||||
```sh
|
||||
wasp db migrate-dev
|
||||
```
|
||||
|
||||
This might take a bit since this is the first time you are running it and it needs to install all the
|
||||
dependencies for your Wasp project.
|
||||
|
||||
In the future, you will also want to run `wasp db migrate-dev` whenever you make changes to your Prisma schema (Entities),
|
||||
to apply those schema changes to the database.
|
||||
|
||||
Additionally, if you want to see or manage your DB via Prisma's DB Studio GUI, run:
|
||||
|
||||
```sh
|
||||
wasp db studio
|
||||
```
|
||||
|
||||
### Start your app
|
||||
|
||||
At this point, you should be positioned in the `app/` directory and have the database running in another terminal session.
|
||||
|
||||
Next, copy the `.env.server.example` file to `.env.server`.
|
||||
|
||||
```sh
|
||||
cp .env.server.example .env.server
|
||||
```
|
||||
|
||||
`.env.server` is where API keys for services like Stripe, email sender, and similar go, and this is where you will want to put them in later.
|
||||
For now, you can leave it as it is (dummy API keys), this will be enough to run the app.
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
wasp start
|
||||
```
|
||||
|
||||
This will install all the dependencies and start the app (client and server) for you :)!
|
||||
|
||||
If the app doesn't open automatically in your browser, you can open it manually by visiting `http://localhost:3000` in your browser.
|
||||
|
||||
At this point, you should have:
|
||||
- your database running in one terminal session, likely on port `5432`.
|
||||
- your app running in another terminal session, the client likely on port `3000`, and the server likely on port `3001`.
|
||||
|
||||
#### Run Blog and Docs
|
||||
|
||||
This SaaS app comes with a docs and blog section built with the [Starlight template on top of the Astro](https://starlight.astro.build) framework. You can use this as a starting point for your own blog and documentation, if necessary.
|
||||
|
||||
If you do not need this, you can simply delete the `blog` folder from the root of the project.
|
||||
|
||||
If you want to run the Starlight docs and blog, first navigate to the `blog` folder:
|
||||
|
||||
```sh
|
||||
cd ../blog
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
Then start the development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Check the instructions in the terminal for the link to open the blog, it will typically be `https://localhost:4321/`.
|
||||
|
||||
## What's next?
|
||||
|
||||
Awesome! We have our new app ready and we know how to run both it and the blog/docs! Now, in the next section, we'll give you a quick "guided tour" of the different parts of the app we created and understand how it works.
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
265
opensaas-sh/blog/src/content/docs/start/guided-tour.md
Normal file
265
opensaas-sh/blog/src/content/docs/start/guided-tour.md
Normal file
@@ -0,0 +1,265 @@
|
||||
---
|
||||
title: Guided Tour
|
||||
banner:
|
||||
content: |
|
||||
⚠️ Open SaaS is now running on <a href='https://wasp-lang.dev'>Wasp v0.13</a>! If you're running an older version of Open SaaS, please follow the
|
||||
<a href="https://wasp-lang.dev/docs/migrate-from-0-12-to-0-13">migration instructions here</a> ⚠️
|
||||
---
|
||||
|
||||
Let's get to know our new SaaS app.
|
||||
|
||||
First, we'll take a look at the project's file structure, then dive into its main features and how you can get started customizing them.
|
||||
|
||||
:::caution[HOLD UP! ✋]
|
||||
|
||||
If you haven't already, now would be the right time to [explore our demo app](https://opensaas.sh) in your browser:
|
||||
- [ ] explore the landing page
|
||||
- [ ] log in to the demo app
|
||||
- [ ] make a test purchase
|
||||
- [ ] check out the admin dashboard
|
||||
- [ ] check out your account settings
|
||||
- [ ] check out the blog
|
||||
:::
|
||||
|
||||
|
||||
|
||||
## Getting acquainted with the codebase
|
||||
Now that you've gotten a feel for the app and how it works, let's dive into the codebase.
|
||||
|
||||
At the root of our project, you will see two folders:
|
||||
```sh
|
||||
.
|
||||
├── app
|
||||
└── blog
|
||||
```
|
||||
|
||||
`app` contains the Wasp project files, which is your full-stack React + NodeJS + Prisma app along with a Wasp config file, `main.wasp`, which will be explained in more detail below.
|
||||
|
||||
`blog` contains the [Astro Starlight template](https://starlight.astro.build/) for the blog and documentation section.
|
||||
|
||||
Let's check out what's in the `app` folder in more detail:
|
||||
|
||||
:::caution[v0.11 and below]
|
||||
If you are using a version of the OpenSaaS template with Wasp `v0.11.x` or below, you may see a slightly different file structure. But don't worry, the vast majority of the code and features are the same! 😅
|
||||
:::
|
||||
|
||||
```sh
|
||||
.
|
||||
├── main.wasp # Wasp Config file. You define your app structure here.
|
||||
├── .wasp/ # Output dir for Wasp. DON'T MODIFY THESE FILES!
|
||||
├── public/ # Public assets dir, e.g. www.yourdomain.com/banner.png
|
||||
├── src/ # Your code goes here.
|
||||
│ ├── client/ # Your client code (React) goes here.
|
||||
│ ├── server/ # Your server code (NodeJS) goes here.
|
||||
│ ├── shared/ # Your shared (runtime independent) code goes here.
|
||||
│ └── .waspignore
|
||||
├── .env.server # Dev environment variables for your server code.
|
||||
├── .env.client # Dev environment variables for your client code.
|
||||
├── .prettierrc # Prettier configuration.
|
||||
├── tailwind.config.js # TailwindCSS configuration.
|
||||
├── package.json
|
||||
├── package-lock.json
|
||||
└── .wasproot
|
||||
```
|
||||
|
||||
:::tip[File Structure]
|
||||
Note that since Wasp v0.12, the `src` folder does not need to be organized between `client` and `server` code. You can organize your code however you like, e.g. by feature, but we've chosen to keep the traditional structure for this template.
|
||||
:::
|
||||
|
||||
### The Wasp Config file
|
||||
|
||||
This template at its core is a Wasp project, where [Wasp](https://wasp-lang.dev) is a full-stack web app framework that let’s you write your app in React, NodeJS, and Prisma and will manage the "boilerplatey" work for you, allowing you to just take care of the fun stuff!
|
||||
|
||||
[Wasp's secret sauce](https://wasp-lang.dev/docs) is its use of a config file (`main.wasp`) and compiler which takes your code and outputs the client app, server app and deployment code for you.
|
||||
|
||||
In this template, we've already defined a number of things in the `main.wasp` config file, including:
|
||||
|
||||
- Auth
|
||||
- Routes and Pages
|
||||
- Prisma Database Models
|
||||
- Operations (data read and write functions)
|
||||
- Background Jobs
|
||||
- Email Sending
|
||||
|
||||
By defining these things in the config file, Wasp continuously handles the boilerplate necessary with putting all these features together. You just need to focus on the business logic of your app.
|
||||
|
||||
Wasp abstracts away some things that you would normally be used to doing during development, so don't be surprised if you don't see some of the things you're used to seeing.
|
||||
|
||||
:::note
|
||||
It's possible to learn Wasp's feature set simply through using this template, but if you find yourself unsure how to implement a Wasp-specific feature and/or just want to learn more, a great starting point is the intro tutorial in the [Wasp docs](https://wasp-lang.dev/docs) which takes ~20 minutes.
|
||||
:::
|
||||
|
||||
### Client
|
||||
|
||||
The `src/client` folder contains all the code that runs in the browser. It's a standard React app, with a few Wasp-specific things sprinkled in.
|
||||
|
||||
```sh
|
||||
.
|
||||
└── client
|
||||
├── admin # Admin dashboard pages and components
|
||||
├── app # Your user-facing app that sits behind the paywall/login.
|
||||
├── auth # All auth-related pages and components.
|
||||
├── components # Your shared React components.
|
||||
├── hooks # Your shared React hooks.
|
||||
├── landing-page # Landing page related code
|
||||
├── static # Assets that you need access to in your code, e.g. import logo from 'static/logo.png'
|
||||
├── App.tsx # Main app component to wrap all child components. Useful for global state, navbars, etc.
|
||||
└── Main.css
|
||||
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
The `src/server` folder contains all the code that runs on the server. Wasp compiles everything into a NodeJS server for you.
|
||||
|
||||
All you have to do is define your server-side functions in the `main.wasp` file, write the logic in a function within `src/server` and Wasp will generate the boilerplate code for you.
|
||||
|
||||
```sh
|
||||
└── server
|
||||
├── auth # Some small auth-related functions to customize the auth flow.
|
||||
├── file-upload # File upload utility functions.
|
||||
├── payments # Payments utility functions.
|
||||
├── scripts # Scripts to run via Wasp, e.g. database seeding.
|
||||
├── webhooks # The webhook handler for Stripe.
|
||||
├── workers # Functions that run in the background as Wasp Jobs, e.g. daily stats calculation.
|
||||
├── actions.ts # Your server-side write/mutation functions.
|
||||
├── queries.ts # Your server-side read functions.
|
||||
└── types.ts
|
||||
```
|
||||
|
||||
## Main Features
|
||||
|
||||
### Auth
|
||||
|
||||
This template comes with a fully functional auth flow out of the box. It takes advantages of Wasp's built-in [Auth features](https://wasp-lang.dev/docs/auth/overview), which do the dirty work of rolling your own full-stack auth for you!
|
||||
|
||||
```js title="main.wasp"
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
email: {
|
||||
//...
|
||||
},
|
||||
google: {},
|
||||
github: {},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/",
|
||||
},
|
||||
```
|
||||
|
||||
By defining the auth structure in your `main.wasp` file, Wasp manages all the necessary code for you, including:
|
||||
- Email verified login with reset password
|
||||
- Social login with Google and/or GitHub
|
||||
- Auth-related databse entities for user credentials, sessions, and social logins
|
||||
- Custom-generated AuthUI components for login, signup, and reset password
|
||||
- Auth hooks for fetching user data
|
||||
|
||||
<!-- TODO: add pic of AuthUI components -->
|
||||
|
||||
We've set the template up with Wasp's `email`, `google`, and `gitHub` methods, which are all battle-tested and suitable for production.
|
||||
|
||||
You can get started developing your app with the `email` method right away!
|
||||
|
||||
:::caution[Dummy Email Provider]
|
||||
Note that the `email` method relies on an `emailSender` (configured at `app.emailSender` in the `main.wasp` file), a service which sends emails to verify users and reset passwords.
|
||||
|
||||
For development purposes, Wasp provides a `Dummy` email sender which Open SaaS comes with as the default. This provider *does not* actually send any confirmation emails to the specified email address, but instead logs all email verification links/tokens to the console! You can then follow these links to verify the user and continue with the sign-up process.
|
||||
|
||||
```tsx title="main.wasp"
|
||||
emailSender: {
|
||||
provider: Dummy, // logs all email verification links/tokens to the server's console
|
||||
defaultFrom: {
|
||||
name: "Open SaaS App",
|
||||
email: "me@example.com"
|
||||
},
|
||||
},
|
||||
```
|
||||
:::
|
||||
|
||||
We will explain more about these auth methods, and how to properly integrate them into your app, in the [Authentication Guide](/guides/authentication).
|
||||
|
||||
### Subscription Payments with Stripe
|
||||
|
||||
No SaaS is complete without payments, specifically subscription payments. That's why this template comes with a fully functional Stripe integration.
|
||||
|
||||
Let's take a quick look at how payments are handled in this template.
|
||||
|
||||
1. a user clicks the `BUY` button and a **Stripe Checkout session** is created on the server
|
||||
2. the user is redirected to the Stripe Checkout page where they enter their payment info
|
||||
3. the user is redirected back to the app and the Stripe Checkout session is completed
|
||||
4. Stripe sends a webhook event to the server with the payment info
|
||||
5. The app server's **webhook handler** handles the event and updates the user's subscription status
|
||||
|
||||
The logic for creating the Stripe Checkout session is defined in the `src/server/actions.ts` file. [Actions](https://wasp-lang.dev/docs/data-model/operations/actions) are your server-side functions that are used to write or update data to the database. Once they're defined in the `main.wasp` file, you can easily call them on the client-side:
|
||||
|
||||
a) define the action in the `main.wasp` file
|
||||
```js title="main.wasp"
|
||||
action stripePayment {
|
||||
fn: import { stripePayment } from "@src/server/actions.js",
|
||||
entities: [User]
|
||||
}
|
||||
```
|
||||
|
||||
b) implement the action in the `src/server/actions.ts` file
|
||||
```js title="src/server/actions.ts"
|
||||
export const stripePayment = async (tier, context) => {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
c) call the action on the client-side
|
||||
```js title="src/client/app/SubscriptionPage.tsx"
|
||||
import { stripePayment } from "wasp/client/operations";
|
||||
|
||||
const handleBuyClick = async (tierId) => {
|
||||
const stripeResults = await stripePayment(tierId);
|
||||
};
|
||||
```
|
||||
|
||||
The webhook handler is defined in the `src/server/webhooks/stripe.ts` file. Unlike Actions and Queries in Wasp which are only to be used internally, we define the webhook handler in the `main.wasp` file as an API endpoint in order to expose it externally to Stripe
|
||||
|
||||
```js title="main.wasp"
|
||||
api stripeWebhook {
|
||||
fn: import { stripeWebhook } from "@src/server/webhooks/stripe.js",
|
||||
httpRoute: (POST, "/stripe-webhook")
|
||||
entities: [User],
|
||||
}
|
||||
```
|
||||
|
||||
Within the webhook handler, we look for specific events that Stripe sends us to let us know which payment was completed and for which user. Then we update the user's subscription status in the database.
|
||||
|
||||
To learn more about configuring the app to handle your products and payments, check out the [Stripe Integration guide](/guides/stripe-integration).
|
||||
|
||||
:::tip[Star our Repo on GitHub! 🌟]
|
||||
We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free!
|
||||
|
||||
If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp)
|
||||
:::
|
||||
|
||||
|
||||
### Analytics and Admin Dashboard
|
||||
|
||||
Keeping an eye on your metrics is crucial for any SaaS. That's why we've built an administrator's dashboard where you can view your app's stats, user data, and Stripe revenue all in one place.
|
||||
|
||||
<!-- TODO: add pic of admin dash -->
|
||||
|
||||
To do that, we've leveraged Wasp's [Jobs feature](https://wasp-lang.dev/docs/advanced/jobs) to run a cron job that calculates your daily stats. The app stats, such as page views and sources, can be pulled from either Plausible or Google Analytics. All you have to do is create a project with the analytics provider of your choice and import the respective pre-built helper functions!
|
||||
|
||||
```js title="main.wasp"
|
||||
job dailyStatsJob {
|
||||
executor: PgBoss,
|
||||
perform: {
|
||||
fn: import { calculateDailyStats } from "@src/server/workers/calculateDailyStats.js"
|
||||
},
|
||||
schedule: {
|
||||
cron: "0 * * * *" // runs every hour
|
||||
},
|
||||
entities: [User, DailyStats, Logs, PageViewSource]
|
||||
}
|
||||
```
|
||||
|
||||
For more info on integrating Plausible or Google Analytics, check out the [Analytics guide](/guides/analytics).
|
||||
|
||||
## What's next?
|
||||
|
||||
And that concludes our guided tour! For next steps, we recommend ...
|
||||
Reference in New Issue
Block a user