mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-03-18 22:02:19 +01:00
414 lines
14 KiB
TypeScript
414 lines
14 KiB
TypeScript
app OpenSaaS {
|
|
wasp: {
|
|
version: "^0.12.0"
|
|
},
|
|
title: "My Open SaaS App",
|
|
head: [
|
|
"<meta property='og:type' content='website' />",
|
|
"<meta property='og:title' content='My Open SaaS App' />",
|
|
"<meta property='og:url' content='https://opensaas.sh' />",
|
|
"<meta property='og:description' content='I made a SaaS App. Buy my stuff.' />",
|
|
"<meta property='og:image' content='https://opensaas.sh/public-banner.png' />",
|
|
"<meta name='twitter:image' content='https://opensaas.sh/public-banner.png' />",
|
|
"<meta name='twitter:image:width' content='800' />",
|
|
"<meta name='twitter:image:height' content='400' />",
|
|
"<meta name='twitter:card' content='summary_large_image' />",
|
|
// you can put your analytics scripts here
|
|
"<script defer data-domain='<your-site-id>' src='https://plausible.io/js/script.js'></script>",
|
|
// plausible has script extension `script.local.js` for local development
|
|
"<script defer data-domain='<your-site-id>' src='https://plausible.io/js/script.local.js'></script>",
|
|
// google analytics only needs one script and will automatically detect if you are in dev mode
|
|
"<!-- Google tag (gtag.js) --><script>...</script>"
|
|
],
|
|
// 🔐 Auth out of the box! https://wasp-lang.dev/docs/auth/overview
|
|
auth: {
|
|
userEntity: User,
|
|
methods: {
|
|
usernameAndPassword: { // !IMPORTANT: this method is only suitable for dev/testing. Use social or email methods in production.
|
|
userSignupFields: import { getUsernameAndPasswordUserFields } from "@src/server/auth/setUsername.js",
|
|
},
|
|
// google: { // Guide for setting up Auth via Google https://wasp-lang.dev/docs/auth/social-auth/overview
|
|
// userSignupFields: import { getGoogleUserFields } from "@src/server/auth/setUsername.js",
|
|
// configFn: import { getGoogleAuthConfig } from "@src/server/auth/setUsername.js",
|
|
// },
|
|
// gitHub: {
|
|
// userSignupFields: import { getGitHubUserFields } from "@src/server/auth/setUsername.js",
|
|
// configFn: import { getGitHubAuthConfig } from "@src/server/auth/setUsername.js",
|
|
// },
|
|
// email: {
|
|
// fromField: {
|
|
// name: "Open SaaS App",
|
|
// // make sure this address is the same you registered your SendGrid or MailGun account with!
|
|
// email: "vince@wasp-lang.dev"
|
|
// },
|
|
// emailVerification: {
|
|
// clientRoute: EmailVerificationRoute,
|
|
// getEmailContentFn: import { getVerificationEmailContent } from "@src/server/auth/email.js",
|
|
// },
|
|
// passwordReset: {
|
|
// clientRoute: PasswordResetRoute,
|
|
// getEmailContentFn: import { getPasswordResetEmailContent } from "@src/server/auth/email.js",
|
|
// },
|
|
// userSignupFields: import { getEmailUserFields } from "@src/server/auth/setUsername.js",
|
|
// },
|
|
},
|
|
onAuthFailedRedirectTo: "/login",
|
|
onAuthSucceededRedirectTo: "/demo-app",
|
|
},
|
|
db: {
|
|
system: PostgreSQL,
|
|
seeds: [
|
|
import { devSeedUsers } from "@src/server/scripts/usersSeed.js",
|
|
]
|
|
},
|
|
client: {
|
|
rootComponent: import App from "@src/client/App",
|
|
},
|
|
emailSender: {
|
|
provider: SendGrid,
|
|
defaultFrom: {
|
|
name: "Open SaaS App",
|
|
// make sure this address is the same you registered your SendGrid or MailGun account with!
|
|
email: "me@example.com"
|
|
},
|
|
},
|
|
}
|
|
|
|
/* 💽 Wasp defines DB entities via Prisma Database Models:
|
|
* https://wasp-lang.dev/docs/data-model/entities
|
|
*/
|
|
|
|
entity User {=psl
|
|
id Int @id @default(autoincrement())
|
|
email String? @unique
|
|
username String? @unique
|
|
createdAt DateTime @default(now())
|
|
lastActiveTimestamp DateTime @default(now())
|
|
isAdmin Boolean @default(false)
|
|
stripeId String?
|
|
checkoutSessionId String?
|
|
hasPaid Boolean @default(false)
|
|
subscriptionTier String?
|
|
subscriptionStatus String?
|
|
sendEmail Boolean @default(false)
|
|
datePaid DateTime?
|
|
credits Int @default(3)
|
|
gptResponses GptResponse[]
|
|
contactFormMessages ContactFormMessage[]
|
|
tasks Task[]
|
|
files File[]
|
|
psl=}
|
|
|
|
entity GptResponse {=psl
|
|
id String @id @default(uuid())
|
|
content String
|
|
user User @relation(fields: [userId], references: [id])
|
|
userId Int
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
psl=}
|
|
|
|
entity Task {=psl
|
|
id String @id @default(uuid())
|
|
description String
|
|
time String @default("1")
|
|
isDone Boolean @default(false)
|
|
user User @relation(fields: [userId], references: [id])
|
|
userId Int
|
|
createdAt DateTime @default(now())
|
|
psl=}
|
|
|
|
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=}
|
|
|
|
// TODO: add functionality to allow users to send messages to admin
|
|
// and make them accessible via the admin dashboard
|
|
entity ContactFormMessage {=psl
|
|
id String @id @default(uuid())
|
|
content String
|
|
user User @relation(fields: [userId], references: [id])
|
|
userId Int
|
|
createdAt DateTime @default(now())
|
|
isRead Boolean @default(false)
|
|
repliedAt DateTime?
|
|
psl=}
|
|
|
|
entity DailyStats {=psl
|
|
id Int @id @default(autoincrement())
|
|
date DateTime @default(now()) @unique
|
|
totalViews Int @default(0)
|
|
prevDayViewsChangePercent String @default("0")
|
|
userCount Int @default(0)
|
|
paidUserCount Int @default(0)
|
|
userDelta Int @default(0)
|
|
paidUserDelta Int @default(0)
|
|
totalRevenue Float @default(0)
|
|
totalProfit Float @default(0)
|
|
sources PageViewSource[]
|
|
psl=}
|
|
|
|
entity PageViewSource {=psl
|
|
date DateTime @default(now())
|
|
name String
|
|
visitors Int
|
|
dailyStats DailyStats? @relation(fields: [dailyStatsId], references: [id])
|
|
dailyStatsId Int?
|
|
@@id([date, name])
|
|
psl=}
|
|
|
|
entity Logs {=psl
|
|
id Int @id @default(autoincrement())
|
|
createdAt DateTime @default(now())
|
|
message String
|
|
level String
|
|
psl=}
|
|
|
|
/* 📡 These are the Wasp Routes (You can protect them easily w/ 'authRequired: true');
|
|
* https://wasp-lang.dev/docs/tutorial/pages
|
|
*/
|
|
|
|
route LandingPageRoute { path: "/", to: LandingPage }
|
|
page LandingPage {
|
|
component: import LandingPage from "@src/client/landing-page/LandingPage"
|
|
}
|
|
|
|
route LoginRoute { path: "/login", to: LoginPage }
|
|
page LoginPage {
|
|
component: import Login from "@src/client/auth/LoginPage"
|
|
}
|
|
|
|
route SignupRoute { path: "/signup", to: SignupPage }
|
|
page SignupPage {
|
|
component: import { Signup } from "@src/client/auth/SignupPage"
|
|
}
|
|
|
|
// route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordResetPage }
|
|
// page RequestPasswordResetPage {
|
|
// component: import { RequestPasswordReset } from "@src/client/auth/RequestPasswordReset",
|
|
// }
|
|
|
|
// route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage }
|
|
// page PasswordResetPage {
|
|
// component: import { PasswordReset } from "@src/client/auth/PasswordReset",
|
|
// }
|
|
|
|
// route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage }
|
|
// page EmailVerificationPage {
|
|
// component: import { EmailVerification } from "@src/client/auth/EmailVerification",
|
|
// }
|
|
|
|
route DemoAppRoute { path: "/demo-app", to: DemoAppPage }
|
|
page DemoAppPage {
|
|
authRequired: true,
|
|
component: import DemoAppPage from "@src/client/app/DemoAppPage"
|
|
}
|
|
|
|
route PricingPageRoute { path: "/pricing", to: PricingPage }
|
|
page PricingPage {
|
|
component: import PricingPage from "@src/client/app/PricingPage"
|
|
}
|
|
|
|
route AccountRoute { path: "/account", to: AccountPage }
|
|
page AccountPage {
|
|
authRequired: true,
|
|
component: import Account from "@src/client/app/AccountPage"
|
|
}
|
|
|
|
route CheckoutRoute { path: "/checkout", to: CheckoutPage }
|
|
page CheckoutPage {
|
|
authRequired: true,
|
|
component: import Checkout from "@src/client/app/CheckoutPage"
|
|
}
|
|
|
|
route FileUploadRoute { path: "/file-upload", to: FileUploadPage }
|
|
page FileUploadPage {
|
|
authRequired: true,
|
|
component: import FileUpload from "@src/client/app/FileUploadPage"
|
|
}
|
|
|
|
route AdminRoute { path: "/admin", to: DashboardPage }
|
|
page DashboardPage {
|
|
authRequired: true,
|
|
component: import Dashboard from "@src/client/admin/pages/DashboardPage"
|
|
}
|
|
|
|
route AdminUsersRoute { path: "/admin/users", to: AdminUsersPage }
|
|
page AdminUsersPage {
|
|
authRequired: true,
|
|
component: import AdminUsers from "@src/client/admin/pages/Users"
|
|
}
|
|
|
|
route AdminSettingsRoute { path: "/admin/settings", to: AdminSettingsPage }
|
|
page AdminSettingsPage {
|
|
authRequired: true,
|
|
component: import AdminSettings from "@src/client/admin/pages/Settings"
|
|
}
|
|
|
|
route AdminChartsRoute { path: "/admin/chart", to: AdminChartsPage }
|
|
page AdminChartsPage {
|
|
authRequired: true,
|
|
component: import AdminCharts from "@src/client/admin/pages/Chart"
|
|
}
|
|
|
|
route AdminMessagesRoute { path: "/admin/messages", to: AdminMessagesPage }
|
|
page AdminMessagesPage {
|
|
authRequired: true,
|
|
component: import AdminMessages from "@src/client/admin/pages/Messages"
|
|
}
|
|
|
|
route AdminFormElementsRoute { path: "/admin/forms/form-elements", to: AdminFormElementsPage }
|
|
page AdminFormElementsPage {
|
|
authRequired: true,
|
|
component: import AdminForms from "@src/client/admin/pages/Form/FormElements"
|
|
}
|
|
|
|
route AdminFormLayoutsRoute { path: "/admin/forms/form-layouts", to: AdminFormLayoutsPage }
|
|
page AdminFormLayoutsPage {
|
|
authRequired: true,
|
|
component: import AdminForms from "@src/client/admin/pages/Form/FormLayout"
|
|
}
|
|
|
|
route AdminCalendarRoute { path: "/admin/calendar", to: AdminCalendarPage }
|
|
page AdminCalendarPage {
|
|
authRequired: true,
|
|
component: import AdminCalendar from "@src/client/admin/pages/Calendar"
|
|
}
|
|
|
|
route AdminUIAlertsRoute { path: "/admin/ui/alerts", to: AdminUIAlertsPage }
|
|
page AdminUIAlertsPage {
|
|
authRequired: true,
|
|
component: import AdminUI from "@src/client/admin/pages/UiElements/Alerts"
|
|
}
|
|
|
|
route AdminUIButtonsRoute { path: "/admin/ui/buttons", to: AdminUIButtonsPage }
|
|
page AdminUIButtonsPage {
|
|
authRequired: true,
|
|
component: import AdminUI from "@src/client/admin/pages/UiElements/Buttons"
|
|
}
|
|
|
|
/* ⛑ These are the Wasp Operations, which allow the client and server to interact:
|
|
* https://wasp-lang.dev/docs/data-model/operations/overview
|
|
*/
|
|
|
|
// 📝 Actions
|
|
|
|
action generateGptResponse {
|
|
fn: import { generateGptResponse } from "@src/server/actions.js",
|
|
entities: [User, Task, GptResponse]
|
|
}
|
|
|
|
action createTask {
|
|
fn: import { createTask } from "@src/server/actions.js",
|
|
entities: [Task]
|
|
}
|
|
|
|
action deleteTask {
|
|
fn: import { deleteTask } from "@src/server/actions.js",
|
|
entities: [Task]
|
|
}
|
|
|
|
action updateTask {
|
|
fn: import { updateTask } from "@src/server/actions.js",
|
|
entities: [Task]
|
|
}
|
|
|
|
action stripePayment {
|
|
fn: import { stripePayment } from "@src/server/actions.js",
|
|
entities: [User]
|
|
}
|
|
|
|
action updateCurrentUserLastActiveTimestamp {
|
|
fn: import { updateCurrentUserLastActiveTimestamp } from "@src/server/actions.js",
|
|
entities: [User]
|
|
}
|
|
|
|
action updateHasPaidByUserId {
|
|
fn: import { updateHasPaidByUserId } from "@src/server/actions.js",
|
|
entities: [User]
|
|
}
|
|
|
|
action createFile {
|
|
fn: import { createFile } from "@src/server/actions.js",
|
|
entities: [User, File]
|
|
}
|
|
|
|
|
|
// 📚 Queries
|
|
|
|
query getGptResponses {
|
|
fn: import { getGptResponses } from "@src/server/queries.js",
|
|
entities: [User, GptResponse]
|
|
}
|
|
|
|
query getAllTasksByUser {
|
|
fn: import { getAllTasksByUser } from "@src/server/queries.js",
|
|
entities: [Task]
|
|
}
|
|
|
|
query getAllFilesByUser {
|
|
fn: import { getAllFilesByUser } from "@src/server/queries.js",
|
|
entities: [User, File]
|
|
}
|
|
|
|
query getDownloadFileSignedURL {
|
|
fn: import { getDownloadFileSignedURL } from "@src/server/queries.js",
|
|
entities: [User, File]
|
|
}
|
|
|
|
query getDailyStats {
|
|
fn: import { getDailyStats } from "@src/server/queries.js",
|
|
entities: [User, DailyStats]
|
|
}
|
|
|
|
query getPaginatedUsers {
|
|
fn: import { getPaginatedUsers } from "@src/server/queries.js",
|
|
entities: [User]
|
|
}
|
|
|
|
/*
|
|
* 📡 These are custom Wasp API Endpoints. Use them for callbacks, webhooks, etc.
|
|
* https://wasp-lang.dev/docs/advanced/apis
|
|
*/
|
|
|
|
api stripeWebhook {
|
|
fn: import { stripeWebhook } from "@src/server/webhooks/stripe.js",
|
|
entities: [User],
|
|
middlewareConfigFn: import { stripeMiddlewareFn } from "@src/server/webhooks/stripe.js",
|
|
httpRoute: (POST, "/stripe-webhook")
|
|
}
|
|
|
|
/* 🕵️♂️ These are the Wasp Jobs. Use them to set up recurring tasks and/or queues:
|
|
* https://wasp-lang.dev/docs/advanced/jobs
|
|
*/
|
|
|
|
job emailChecker {
|
|
executor: PgBoss,
|
|
perform: {
|
|
fn: import { checkAndQueueEmails } from "@src/server/workers/checkAndQueueEmails.js"
|
|
},
|
|
schedule: {
|
|
cron: "0 7 * * 1" // at 7:00 am every Monday
|
|
},
|
|
entities: [User]
|
|
}
|
|
|
|
job dailyStatsJob {
|
|
executor: PgBoss,
|
|
perform: {
|
|
fn: import { calculateDailyStats } from "@src/server/workers/calculateDailyStats.js"
|
|
},
|
|
schedule: {
|
|
cron: "0 * * * *" // every hour. useful in production
|
|
// cron: "* * * * *" // every minute. useful for debugging
|
|
},
|
|
entities: [User, DailyStats, Logs, PageViewSource]
|
|
}
|