mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
## Description - [x] Adds support for AWS SSO, which makes us (the team) able to use SST and update the components independently - [x] Splits the webpage into the landing page (Qwik), and Astro (the console) in charge of playing. This allows us to pass in Environment Variables to the console - ~Migrates the docs from Nuxt to Nextjs, and connects them to SST. This allows us to use Fumadocs _citation needed_ that's much more beautiful, and supports OpenApi~ - Cloudflare pages with github integration is not working on our new CF account. So we will have to push the pages deployment manually with Github actions - [x] Moves the current set up from my personal CF and AWS accounts to dedicated Nestri accounts - ## Related Issues <!-- List any related issues (e.g., "Closes #123", "Fixes #456") --> ## Type of Change - [ ] Bug fix (non-breaking change) - [x] New feature (non-breaking change) - [ ] Breaking change (fix or feature that changes existing functionality) - [x] Documentation update - [ ] Other (please describe): ## Checklist - [x] I have updated relevant documentation - [x] My code follows the project's coding style - [x] My changes generate no new warnings/errors ## Notes for Reviewers <!-- Point out areas you'd like reviewers to focus on, questions you have, or decisions that need discussion --> Please approve my PR 🥹 ## Screenshots/Demo <!-- If applicable, add screenshots or a GIF demo of your changes (especially for UI changes) --> ## Additional Context <!-- Add any other context about the pull request here -->
205 lines
6.2 KiB
TypeScript
205 lines
6.2 KiB
TypeScript
import { z } from "zod";
|
|
import databaseClient from "../database"
|
|
import { fn } from "../utils";
|
|
import { groupBy, map, pipe, values } from "remeda"
|
|
import { Common } from "../common";
|
|
import { Examples } from "../examples";
|
|
import { useCurrentUser } from "../actor";
|
|
import { id as createID } from "@instantdb/admin";
|
|
import { Email } from "../email";
|
|
import { Profiles } from "../profile";
|
|
|
|
export const SubscriptionFrequency = z.enum([
|
|
"fixed",
|
|
"daily",
|
|
"weekly",
|
|
"monthly",
|
|
"yearly",
|
|
]);
|
|
|
|
export type SubscriptionFrequency = z.infer<typeof SubscriptionFrequency>;
|
|
|
|
export namespace Subscriptions {
|
|
export const Info = z
|
|
.object({
|
|
id: z.string().openapi({
|
|
description: Common.IdDescription,
|
|
example: Examples.Subscription.id,
|
|
}),
|
|
checkoutID: z.string().openapi({
|
|
description: "The polar.sh checkout id",
|
|
example: Examples.Subscription.checkoutID,
|
|
}),
|
|
// productID: z.string().openapi({
|
|
// description: "ID of the product being subscribed to.",
|
|
// example: Examples.Subscription.productID,
|
|
// }),
|
|
// quantity: z.number().int().openapi({
|
|
// description: "Quantity of the subscription.",
|
|
// example: Examples.Subscription.quantity,
|
|
// }),
|
|
// frequency: SubscriptionFrequency.openapi({
|
|
// description: "Frequency of the subscription.",
|
|
// example: Examples.Subscription.frequency,
|
|
// }),
|
|
// next: z.string().or(z.number()).openapi({
|
|
// description: "Next billing date for the subscription.",
|
|
// example: Examples.Subscription.next,
|
|
// }),
|
|
canceledAt: z.string().or(z.number()).optional().openapi({
|
|
description: "Cancelled date for the subscription.",
|
|
example: Examples.Subscription.canceledAt,
|
|
}),
|
|
})
|
|
.openapi({
|
|
ref: "Subscription",
|
|
description: "Subscription to a Nestri product.",
|
|
example: Examples.Subscription,
|
|
});
|
|
|
|
export type Info = z.infer<typeof Info>;
|
|
|
|
export const list = fn(z.string().optional(), async (userID) => {
|
|
const db = databaseClient()
|
|
const user = userID ? userID : useCurrentUser().id
|
|
|
|
const query = {
|
|
subscriptions: {
|
|
$: {
|
|
where: {
|
|
owner: user,
|
|
canceledAt: { $isNull: true }
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
const res = await db.query(query)
|
|
|
|
const response = res.subscriptions
|
|
if (!response || response.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const result = pipe(
|
|
response,
|
|
groupBy(x => x.id),
|
|
values(),
|
|
map((group): Info => ({
|
|
id: group[0].id,
|
|
// next: group[0].next,
|
|
// frequency: group[0].frequency as any,
|
|
// quantity: group[0].quantity,
|
|
// productID: group[0].productID,
|
|
checkoutID: group[0].checkoutID,
|
|
}))
|
|
)
|
|
|
|
return result
|
|
})
|
|
|
|
export const create = fn(Info.omit({ id: true, canceledAt: true }), async (input) => {
|
|
// const id = createID()
|
|
const id = createID()
|
|
const db = databaseClient()
|
|
const user = useCurrentUser()
|
|
|
|
//Use the polar.sh ID
|
|
await db.transact(db.tx.subscriptions[id]!.update({
|
|
// next: input.next,
|
|
// frequency: input.frequency,
|
|
// quantity: input.quantity,
|
|
checkoutID: input.checkoutID,
|
|
}).link({ owner: user.id }))
|
|
const res = await db.auth.getUser({ id: user.id })
|
|
const profile = await Profiles.fromOwnerID(user.id)
|
|
if (profile) {
|
|
await Email.sendWelcome(res.email, profile.username)
|
|
}
|
|
|
|
})
|
|
|
|
export const remove = fn(z.string(), async (id) => {
|
|
const db = databaseClient()
|
|
|
|
await db.transact(db.tx.subscriptions[id]!.update({
|
|
canceledAt: new Date().toISOString()
|
|
}))
|
|
})
|
|
|
|
export const fromID = fn(z.string(), async (id) => {
|
|
const db = databaseClient()
|
|
const user = useCurrentUser()
|
|
const query = {
|
|
subscriptions: {
|
|
$: {
|
|
where: {
|
|
id,
|
|
//Make sure they can only get subscriptions they own
|
|
owner: user.id,
|
|
canceledAt: { $isNull: true }
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
const res = await db.query(query)
|
|
|
|
const response = res.subscriptions
|
|
if (!response || response.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const result = pipe(
|
|
response,
|
|
groupBy(x => x.id),
|
|
values(),
|
|
map((group): Info => ({
|
|
id: group[0].id,
|
|
checkoutID: group[0].checkoutID,
|
|
// next: group[0].next,
|
|
// frequency: group[0].frequency as any,
|
|
// quantity: group[0].quantity,
|
|
// productID: group[0].productID,
|
|
}))
|
|
)
|
|
|
|
return result[0]
|
|
})
|
|
|
|
export const fromCheckoutID = fn(z.string(), async (id) => {
|
|
const db = databaseClient()
|
|
const user = useCurrentUser()
|
|
const query = {
|
|
subscriptions: {
|
|
$: {
|
|
where: {
|
|
id,
|
|
//Make sure they can only get subscriptions they own
|
|
checkoutID: id,
|
|
canceledAt: { $isNull: true }
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
const res = await db.query(query)
|
|
|
|
const response = res.subscriptions
|
|
if (!response || response.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const result = pipe(
|
|
response,
|
|
groupBy(x => x.id),
|
|
values(),
|
|
map((group): Info => ({
|
|
id: group[0].id,
|
|
checkoutID: group[0].checkoutID,
|
|
}))
|
|
)
|
|
|
|
return result[0]
|
|
})
|
|
} |