set up docker files for OSS self hosting and deving

This commit is contained in:
John Yeo
2025-06-16 16:18:53 +01:00
parent dda910c238
commit a321a357ef
62 changed files with 11164 additions and 13081 deletions

55
.dockerignore Normal file
View File

@@ -0,0 +1,55 @@
# Dependencies
node_modules/
**/node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
**/dist/
**/build/
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# OS files
.DS_Store
Thumbs.db
# Git
.git
.gitignore
# IDE
.vscode/
.idea/
*.swp
*.swo
# Logs
logs/
*.log
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# Docker
Dockerfile*
docker-compose*
.dockerignore
# Tests
**/tests/
**/*.test.*
**/*.spec.*

15
commands.sh Normal file
View File

@@ -0,0 +1,15 @@
RESTART EVERYTHING
docker system prune -a --volumes
# DB
docker compose -f docker-compose.db.yml up --build
# Dev
docker compose -f docker-compose.dev.yml down
docker volume rm main-repo_shared-node-modules main-repo_root-node-modules main-repo_vite-node-modules
docker compose -f docker-compose.dev.yml build --no-cache
docker compose -f docker-compose.dev.yml up
# Prod
docker compose -f docker-compose.prod.yml up --build
---

95
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,95 @@
services:
valkey:
image: docker.io/bitnami/valkey:8.0
environment:
- ALLOW_EMPTY_PASSWORD=yes
- VALKEY_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
volumes:
- valkey-data:/bitnami/valkey/data
healthcheck:
test: ['CMD', 'redis-cli', '-h', 'localhost', '-p', '6379', 'ping']
interval: 10s
timeout: 5s
retries: 5
# ports:
# - "6379:6379"
restart: unless-stopped
shared:
build:
dockerfile: docker/dev.dockerfile
context: .
target: shared
volumes:
# - ./shared:/app/shared
- shared-dist:/app/shared/dist
- root-node-modules:/app/node_modules
environment:
- NODE_ENV=development
restart: unless-stopped
# Vite frontend
vite:
build:
dockerfile: docker/dev.dockerfile
context: .
target: vite
ports:
- "3000:3000"
volumes:
# - ./vite:/app/vite
- shared-dist:/app/shared/dist
- root-node-modules:/app/node_modules
environment:
- NODE_ENV=development
depends_on:
- shared
restart: unless-stopped
# Main Express server
server:
build:
dockerfile: docker/dev.dockerfile
context: .
target: server
ports:
- "8080:8080"
volumes:
- ./server:/app/server
- shared-dist:/app/shared/dist
- root-node-modules:/app/node_modules
environment:
- NODE_ENV=development
- REDIS_URL=redis://valkey:6379
depends_on:
- shared
restart: unless-stopped
# BullMQ Workers
workers:
build:
dockerfile: docker/dev.dockerfile
context: .
target: workers
volumes:
# Mount server source for hot reload (workers use server code)
- ./server:/app/server
- shared-dist:/app/shared/dist
- root-node-modules:/app/node_modules
environment:
- NODE_ENV=development
- REDIS_URL=redis://valkey:6379
depends_on:
- shared
restart: unless-stopped
volumes:
# Shared package dist output
shared-dist:
valkey-data:
# Node modules volumes to avoid host/container conflicts
shared-node-modules:
server-node-modules:
vite-node-modules:
root-node-modules:

46
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,46 @@
version: "3.8"
services:
valkey:
image: docker.io/bitnami/valkey:8.0
environment:
- ALLOW_EMPTY_PASSWORD=yes
- VALKEY_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
volumes:
- valkey-data:/bitnami/valkey/data
healthcheck:
test: ['CMD', 'redis-cli', '-h', 'localhost', '-p', '6379', 'ping']
interval: 10s
timeout: 5s
retries: 5
ports:
- "6379:6379"
restart: unless-stopped
vite:
build:
context: .
dockerfile: docker/prod.dockerfile
target: vite-prod
ports:
- "3000:3000"
restart: always
server:
environment:
- REDIS_URL=redis://valkey:6379
build:
context: .
dockerfile: docker/prod.dockerfile
target: server-prod
ports:
- "8080:8080"
restart: always
workers:
environment:
- REDIS_URL=redis://valkey:6379
build:
context: .
dockerfile: docker/prod.dockerfile
target: workers-prod
restart: always

35
docker/dev.dockerfile Normal file
View File

@@ -0,0 +1,35 @@
# Multi-stage Dockerfile for Autumn development
FROM node:18-alpine AS base
WORKDIR /app
RUN npm install -g pnpm
COPY package*.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY shared/ ./shared/
COPY server/ ./server/
COPY vite/ ./vite/
RUN pnpm install
RUN npm install -g nodemon tsx
# Stage 2: /shared
FROM base AS shared
WORKDIR /app/shared
CMD ["pnpm", "run", "dev"]
# Stage 3: /vite
FROM base AS vite
WORKDIR /app/vite
EXPOSE 3000
CMD ["pnpm", "run", "dev"]
# Stage 4: /server
FROM base AS server
WORKDIR /app/server
EXPOSE 8080
CMD ["pnpm", "run", "dev"]
# Stage 5: Workers
FROM base AS workers
WORKDIR /app/server
CMD ["pnpm", "run", "workers"]

View File

@@ -0,0 +1,42 @@
version: '3.8'
services:
# Redis for caching and BullMQ
valkey:
image: docker.io/bitnami/valkey:8.0
environment:
- ALLOW_EMPTY_PASSWORD=yes
- VALKEY_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
volumes:
- valkey-data:/bitnami/valkey/data
healthcheck:
test: ['CMD', 'redis-cli', '-h', 'localhost', '-p', '6379', 'ping']
interval: 10s
timeout: 5s
retries: 5
# PostgreSQL database
postgres:
image: postgres:15-alpine
container_name: autumn-postgres
ports:
- "5432:5432"
environment:
POSTGRES_DB: autumn
POSTGRES_USER: postgres
POSTGRES_PASSWORD: autumn_dev_password
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
volumes:
valkey-data:
postgres-data:
networks:
autumn-network:

60
docker/prod.dockerfile Normal file
View File

@@ -0,0 +1,60 @@
# ---- Base dependencies ----
FROM node:18-alpine AS base
WORKDIR /app
RUN npm install -g pnpm serve typescript tsc tsc-alias tsx
COPY package*.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY shared/package*.json ./shared/
COPY server/package*.json ./server/
COPY vite/package*.json ./vite/
RUN pnpm install
# # ---- Build shared ----
# FROM base AS shared-build
# COPY shared/ ./shared/
# RUN pnpm -F shared build
# # ---- Build frontend (vite) ----
FROM base AS vite-build
COPY . .
WORKDIR /app
RUN pnpm run vite:build
# ---- Build backend (server) ----
FROM base AS server-build
COPY . .
WORKDIR /app
RUN pnpm run server:build
# ---- Production frontend image ----
FROM vite-build AS vite-prod
EXPOSE 3000
WORKDIR /app
CMD ["pnpm", "run", "vite:start"]
FROM server-build AS server-prod
EXPOSE 8080
WORKDIR /app
CMD ["pnpm", "run", "server:start"]
FROM server-build AS workers-prod
EXPOSE 8080
WORKDIR /app
CMD ["pnpm", "run", "server:workers"]
# # ---- Production backend image ----
# FROM node:18-alpine AS server-prod
# COPY --from=server-build /app/server/dist ./dist
# COPY --from=server-build /app/server/package.json ./
# COPY --from=server-build /app/server/node_modules ./node_modules
# EXPOSE 8080
# CMD ["pnpm", "run", "server:start"]
# # ---- Production workers image ----
# FROM node:18-alpine AS workers-prod
# COPY --from=server-build /app/server/dist ./dist
# COPY --from=server-build /app/server/package.json ./
# COPY --from=server-build /app/server/node_modules ./node_modules
# CMD ["pnpm", "run", "server:workers"]

View File

@@ -1,5 +0,0 @@
https://docs.fontawesome.com/web/use-with/react
https://docs.fontawesome.com/web/setup/packages#1-configure-access
In frontend, ran `npm link @useautumn/react`

12923
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,20 +2,16 @@
"name": "autumn",
"private": true,
"workspaces": [
"frontend",
"server",
"shared",
"tests",
"vite"
],
"type": "module",
"scripts": {
"frontend:build": "npm run build -w shared && npm run build -w frontend",
"frontend:start": "npm run start -w frontend",
"vite:build": "npm run build -w shared && npm run build -w vite",
"vite:start": "npm run start -w vite",
"server:build": "npm run build -w shared && npm run prod:build -w server",
"server:start": "npm run prod:start -w server",
"vite:build": "pnpm -F shared build && pnpm -F vite build",
"vite:start": "pnpm -F vite start",
"server:build": "pnpm -F shared build && pnpm -F server prod:build",
"server:start": "pnpm -F server prod:start",
"server:cron": "npm run cron:start -w server",
"server:workers": "npm run workers:start -w server",
"dev": "concurrently \"cd server && npm run dev\" \"cd vite && npm run dev\" \"redis-server\""

10695
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
packages:
- 'shared'
- 'server'
- 'vite'

3
run.sh Executable file
View File

@@ -0,0 +1,3 @@
if [[ $1 == *"docker-compose"* ]]; then
docker compose -f "$1" up
fi

View File

@@ -37,7 +37,7 @@
"dependencies": {
"@ai-sdk/anthropic": "^1.2.10",
"@anthropic-ai/sdk": "^0.32.1",
"@autumn/shared": "*",
"@autumn/shared": "workspace:*",
"@clerk/express": "^1.3.22",
"@date-fns/tz": "^1.2.0",
"@date-fns/utc": "^2.1.0",
@@ -50,7 +50,7 @@
"autumn-js": "^0.0.64",
"better-auth": "^1.2.9",
"body-parser": "^1.20.3",
"bullmq": "^5.31.1",
"bullmq": "^5.54.0",
"chai": "^5.1.2",
"chai-http": "^5.1.1",
"chalk": "^5.3.0",
@@ -78,12 +78,14 @@
"pino-pretty": "^13.0.0",
"postgres": "^3.4.7",
"posthog-node": "^4.17.2",
"react": "^18.2.0",
"recaseai": "^0.0.37",
"resend": "^4.1.1",
"stripe": "^17.5.0",
"svix": "^1.45.1",
"tsc-alias": "^1.8.16",
"ws": "^8.18.0"
"ws": "^8.18.0",
"zod": "^3.25.23"
},
"devDependencies": {
"@types/chai": "^5.0.1",
@@ -92,6 +94,9 @@
"@types/mocha": "^10.0.10",
"@types/node": "^22.13.4",
"@types/pg": "^8.11.10",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/ws": "^8.18.1",
"drizzle-kit": "^0.31.1",
"mocha": "^11.1.0",
"puppeteer": "^24.2.0",

View File

@@ -1,7 +1,7 @@
import express from "express";
import express, { Router } from "express";
import { Webhook } from "svix";
export const autumnWebhookRouter = express.Router();
export const autumnWebhookRouter: Router = express.Router();
const verifyAutumnWebhook = async (req: any, res: any) => {
const wh = new Webhook(process.env.AUTUMN_WEBHOOK_SECRET!);
@@ -54,5 +54,5 @@ autumnWebhookRouter.post(
success: true,
message: "Webhook received",
});
}
},
);

View File

@@ -89,7 +89,7 @@ export const createStripeMeteredPrice = async ({
const stripePrice = await stripeCli.prices.create({
...productData,
...priceAmountData,
currency: org.default_currency,
currency: org.default_currency || "usd",
nickname: `Autumn Price (${feature!.name}) [Placeholder]`,
recurring: {
...(billingIntervalToStripe(price.config!.interval!) as any),
@@ -194,7 +194,7 @@ export const createStripeArrearProrated = async ({
let stripePrice = await stripeCli.prices.create({
...productData,
currency: org.default_currency,
currency: org.default_currency || "usd",
...priceAmountData,
recurring: {
...(recurringData as any),

View File

@@ -26,7 +26,7 @@ export const createStripeFixedPrice = async ({
const stripePrice = await stripeCli.prices.create({
product: product.processor!.id,
unit_amount: amount,
currency: org.default_currency,
currency: org.default_currency!,
recurring: {
...(billingIntervalToStripe(config.interval!) as any),
},

View File

@@ -255,7 +255,7 @@ export const createStripeInArrearPrice = async ({
const stripePrice = await stripeCli.prices.create({
...productData,
...priceAmountData,
currency: org.default_currency,
currency: org.default_currency!,
recurring: {
...(billingIntervalToStripe(price.config!.interval!) as any),
meter: meter!.id,

View File

@@ -110,7 +110,7 @@ export const createStripePrepaid = async ({
...productData,
// unit_amount_decimal: (amount * 100).toString(),
unit_amount_decimal: unitAmountDecimalStr,
currency: org.default_currency,
currency: org.default_currency!,
});
config.stripe_product_id = stripePrice.product as string;
@@ -133,7 +133,7 @@ export const createStripePrepaid = async ({
stripePrice = await stripeCli.prices.create({
...productData,
currency: org.default_currency,
currency: org.default_currency!,
...priceAmountData,
recurring: {
...(recurringData as any),

View File

@@ -1,7 +1,10 @@
import express, { Router } from "express";
import stripe, { Stripe } from "stripe";
import chalk from "chalk";
import { OrgService } from "@/internal/orgs/OrgService.js";
import { AuthType, LoggerAction, Organization } from "@autumn/shared";
import express from "express";
import stripe, { Stripe } from "stripe";
import { handleCheckoutSessionCompleted } from "./webhookHandlers/handleCheckoutCompleted.js";
import { handleSubscriptionUpdated } from "./webhookHandlers/handleSubUpdated.js";
import { handleSubDeleted } from "./webhookHandlers/handleSubDeleted.js";
@@ -10,7 +13,6 @@ import { getStripeWebhookSecret } from "@/internal/orgs/orgUtils.js";
import { handleInvoicePaid } from "./webhookHandlers/handleInvoicePaid.js";
import { handleRequestError } from "@/utils/errorUtils.js";
import { handleInvoiceCreated } from "./webhookHandlers/handleInvoiceCreated/handleInvoiceCreated.js";
import chalk from "chalk";
import { handleInvoiceFinalized } from "./webhookHandlers/handleInvoiceFinalized.js";
import { handleSubscriptionScheduleCanceled } from "./webhookHandlers/handleSubScheduleCanceled.js";
import { createLogtailWithContext } from "../logtail/logtailUtils.js";
@@ -18,7 +20,7 @@ import { handleCusDiscountDeleted } from "./webhookHandlers/handleCusDiscountDel
import { ExtendedRequest } from "@/utils/models/Request.js";
import { createStripeCli } from "./utils.js";
export const stripeWebhookRouter = express.Router();
export const stripeWebhookRouter: Router = express.Router();
const logStripeWebhook = ({
req,

View File

@@ -1,9 +1,9 @@
import express from "express";
import express, { Router } from "express";
import { stripeWebhookRouter } from "../stripe/stripeWebhooks.js";
import { autumnWebhookRouter } from "../autumn/autumnWebhookRouter.js";
const webhooksRouter = express.Router();
const webhooksRouter: Router = express.Router();
webhooksRouter.use("/stripe", stripeWebhookRouter);

View File

@@ -1,22 +1,10 @@
import { handleFrontendReqError } from "@/utils/errorUtils.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import { member, organizations, user } from "@autumn/shared";
import {
and,
desc,
eq,
gt,
gte,
ilike,
inArray,
lt,
lte,
or,
} from "drizzle-orm";
import { and, desc, eq, gt, gte, ilike, inArray, lt, or } from "drizzle-orm";
import { Router } from "express";
import { getUserCount } from "./adminUtils/userAnalytics.js";
export const adminRouter = Router();
export const adminRouter: Router = Router();
adminRouter.get("/users", async (req: any, res: any) => {
try {

View File

@@ -21,7 +21,7 @@ import { analyticsMiddleware } from "@/middleware/analyticsMiddleware.js";
import rewardRouter from "./rewards/rewardRouter.js";
import expireRouter from "./customers/products/expireRouter.js";
const apiRouter = Router();
const apiRouter: Router = Router();
apiRouter.use(apiAuthMiddleware);
apiRouter.use(pricingMiddleware);

View File

@@ -11,7 +11,7 @@ import { routeHandler } from "@/utils/routerUtils.js";
import { FullCusProduct, CusProductStatus, ProductV2 } from "@autumn/shared";
import { Router } from "express";
export const componentRouter = Router();
export const componentRouter: Router = Router();
componentRouter.get("/pricing_table", async (req: any, res) =>
routeHandler({

View File

@@ -20,9 +20,8 @@ import { handleUpdateCustomer } from "../customers/handlers/handleUpdateCustomer
import { handleCreateBillingPortal } from "../customers/handlers/handleCreateBillingPortal.js";
import { handleGetCustomer } from "../customers/handlers/handleGetCustomer.js";
import { CusSearchService } from "@/internal/customers/CusSearchService.js";
import assert from "assert";
export const cusRouter = Router();
export const cusRouter: Router = Router();
cusRouter.post("/all/search", async (req: any, res: any) => {
try {

View File

@@ -6,7 +6,7 @@ import { CusProductStatus, ErrCode, FullCusProduct } from "@autumn/shared";
import { Router } from "express";
import { expireCusProduct } from "../../../customers/handlers/handleCusProductExpired.js";
const expireRouter = Router();
const expireRouter: Router = Router();
expireRouter.post("", async (req, res) =>
routeHandler({

View File

@@ -1,18 +1,15 @@
import { Router } from "express";
import { routeHandler } from "@/utils/routerUtils.js";
import { EntityService } from "./EntityService.js";
import { CusService } from "@/internal/customers/CusService.js";
import RecaseError from "@/utils/errorUtils.js";
import { ErrCode } from "@autumn/shared";
import { handleGetEntity } from "./handlers/handleGetEntity.js";
import { handlePostEntityRequest } from "../../entities/handlers/handleCreateEntity/handleCreateEntity.js";
import { handleDeleteEntity } from "./handlers/handleDeleteEntity.js";
export const entityRouter = Router({ mergeParams: true });
export const entityRouter: Router = Router({ mergeParams: true });
// List entityes
entityRouter.get("", (req, res) =>
entityRouter.get("", (req: any, res: any) =>
routeHandler({
req,
res,

View File

@@ -30,7 +30,7 @@ import { getOrCreateCustomer } from "@/internal/customers/cusUtils/getOrCreateCu
import { getCheckPreview } from "./getCheckPreview.js";
import { orgToVersion } from "@/utils/versionUtils.js";
export const entitledRouter = Router();
export const entitledRouter: Router = Router();
const getRequiredAndActualBalance = ({
cusEnts,

View File

@@ -30,7 +30,7 @@ import { DrizzleCli } from "@/db/initDrizzle.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import { getEventTimestamp } from "./eventUtils.js";
export const eventsRouter = Router();
export const eventsRouter: Router = Router();
const getEventAndCustomer = async ({
req,

View File

@@ -3,7 +3,6 @@ import {
CusProductStatus,
Customer,
ErrCode,
Event,
EventInsert,
FeatureType,
FeatureUsageType,
@@ -12,22 +11,17 @@ import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
import { generateId, nullish } from "@/utils/genUtils.js";
import { EventService } from "./EventService.js";
import { OrgService } from "@/internal/orgs/OrgService.js";
import { FeatureService } from "@/internal/features/FeatureService.js";
import { StatusCodes } from "http-status-codes";
import { QueueManager } from "@/queue/QueueManager.js";
import { JobName } from "@/queue/JobName.js";
import { getOrCreateCustomer } from "@/internal/customers/cusUtils/getOrCreateCustomer.js";
import { creditSystemContainsFeature } from "@/internal/features/creditSystemUtils.js";
import { addTaskToQueue } from "@/queue/queueUtils.js";
import { getOrgAndFeatures } from "@/internal/orgs/orgUtils.js";
import { getEventTimestamp } from "./eventUtils.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import { runUpdateBalanceTask } from "@/trigger/updateBalanceTask.js";
import { runUpdateUsageTask } from "@/trigger/updateUsageTask.js";
export const eventsRouter = Router();
export const usageRouter = Router();
export const eventsRouter: Router = Router();
export const usageRouter: Router = Router();
const getCusFeatureAndOrg = async ({
req,

View File

@@ -6,7 +6,7 @@ import RecaseError, {
import { handleUpdateFeature } from "./handlers/handleUpdateFeature.js";
import { Feature, FeatureResponseSchema, FeatureType } from "@autumn/shared";
import { CreateFeatureSchema } from "@autumn/shared";
import express from "express";
import express, { Router } from "express";
import { generateId } from "@/utils/genUtils.js";
import { ErrCode } from "@/errors/errCodes.js";
@@ -20,7 +20,7 @@ import { JobName } from "@/queue/JobName.js";
import { OrgService } from "@/internal/orgs/OrgService.js";
import { handleDeleteFeature } from "./handlers/handleDeleteFeature.js";
export const featureApiRouter = express.Router();
export const featureApiRouter: Router = express.Router();
export const validateFeature = (data: any) => {
let featureType = data.type;

View File

@@ -4,7 +4,7 @@ import { OrgService } from "@/internal/orgs/OrgService.js";
import { handleRequestError } from "@/utils/errorUtils.js";
import { Router } from "express";
export const invoiceRouter = Router();
export const invoiceRouter: Router = Router();
invoiceRouter.get("/:stripe_invoice_id/stripe", async (req: any, res: any) => {
try {

View File

@@ -17,7 +17,7 @@ import { handleCopyProduct } from "./handlers/handleCopyProduct.js";
import { handleCreateProduct } from "./handlers/handleCreateProduct.js";
import { handleListProducts } from "./handlers/handleListProducts.js";
export const productApiRouter = Router();
export const productApiRouter: Router = Router();
productApiRouter.get("", handleListProducts);

View File

@@ -8,13 +8,13 @@ import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionServ
import RecaseError from "@/utils/errorUtils.js";
import { generateId, notNullish } from "@/utils/genUtils.js";
import { routeHandler } from "@/utils/routerUtils.js";
import { Customer, ErrCode, RewardTriggerEvent } from "@autumn/shared";
import express from "express";
import { ReferralCode, RewardRedemption } from "@autumn/shared";
import { ErrCode, RewardTriggerEvent } from "@autumn/shared";
import express, { Router } from "express";
import { RewardRedemption } from "@autumn/shared";
import { OrgService } from "@/internal/orgs/OrgService.js";
import { RewardProgramService } from "@/internal/rewards/RewardProgramService.js";
export const referralRouter = express.Router();
export const referralRouter: Router = express.Router();
// 1. Get referral code
referralRouter.post("/code", (req, res) =>
@@ -238,7 +238,7 @@ referralRouter.post("/redeem", (req, res) =>
}),
);
export const redemptionRouter = express.Router();
export const redemptionRouter: Router = express.Router();
redemptionRouter.get("/:redemptionId", (req, res) =>
routeHandler({

View File

@@ -8,9 +8,9 @@ import {
ErrCode,
RewardTriggerEvent,
} from "@autumn/shared";
import express from "express";
import express, { Router } from "express";
export const rewardProgramRouter = express.Router();
export const rewardProgramRouter: Router = express.Router();
rewardProgramRouter.post("", (req, res) =>
routeHandler({

View File

@@ -1,4 +1,4 @@
import express from "express";
import express, { Router } from "express";
import {
CreateRewardSchema,
ErrCode,
@@ -19,7 +19,7 @@ import {
getRewardCat,
} from "@/internal/rewards/rewardUtils.js";
const rewardRouter = express.Router();
const rewardRouter: Router = express.Router();
rewardRouter.post("", async (req: any, res: any) => {
try {

View File

@@ -86,18 +86,18 @@ export const handleOneOffFunction = async ({
// Create invoice
logger.info("1. Creating invoice");
const stripeInvoice = await stripeCli.invoices.create({
customer: customer.processor.id,
customer: customer.processor.id!,
auto_advance: false,
currency: org.default_currency,
currency: org.default_currency!,
});
logger.info("2. Creating invoice items");
for (const invoiceItem of invoiceItems) {
await stripeCli.invoiceItems.create({
...invoiceItem,
customer: customer.processor.id,
customer: customer.processor.id!,
invoice: stripeInvoice.id,
});
} as any);
}
// Create invoice items

View File

@@ -42,7 +42,7 @@ import { processAttachBody } from "./attachUtils/attachParams/processAttachBody.
import { handleAttachPreview } from "./handleAttachPreview/handleAttachPreview.js";
import { handleAttach } from "./handleAttach.js";
export const attachRouter = Router();
export const attachRouter: Router = Router();
export const handlePrepaidErrors = async ({
attachParams,

View File

@@ -26,7 +26,7 @@ import { StatusCodes } from "http-status-codes";
import { cusProductToProduct } from "./cusProducts/cusProductUtils/convertCusProduct.js";
import { createOrgResponse } from "../orgs/orgUtils.js";
export const cusRouter = Router();
export const cusRouter: Router = Router();
cusRouter.get("/:customer_id/data", async (req: any, res: any) => {
try {

View File

@@ -30,7 +30,7 @@ export const getProductChargeText = ({
itemStrs.push(
formatCurrency({
amount: total,
defaultCurrency: org.default_currency,
defaultCurrency: org.default_currency!,
}),
);
}

View File

@@ -26,17 +26,17 @@ export const formatTiers = ({
if (tiers.length == 1) {
return formatCurrency({
amount: tiers[0].amount,
defaultCurrency: org.default_currency,
defaultCurrency: org.default_currency!,
});
}
let tiersStart = formatCurrency({
amount: tiers[0].amount,
defaultCurrency: org.default_currency,
defaultCurrency: org.default_currency!,
});
let tiersEnd = formatCurrency({
amount: tiers[tiers.length - 1].amount,
defaultCurrency: org.default_currency,
defaultCurrency: org.default_currency!,
});
return `${tiersStart} - ${tiersEnd}`;
@@ -58,13 +58,13 @@ export const getItemsHtml = ({
if (pricedItems.length == 1) {
html += `<br/><p style="font-size: 1.1em;"><strong>${formatCurrency({
amount: totalAmount,
defaultCurrency: org.default_currency,
defaultCurrency: org.default_currency!,
})}</strong></p>`;
} else {
html += `<br/><ul>${itemsToHtml({ items: pricedItems })}</ul>`;
html += `<br/><p style="font-size: 1.1em;">Total: ${formatCurrency({
amount: totalAmount,
defaultCurrency: org.default_currency,
defaultCurrency: org.default_currency!,
})}</p>`;
}

View File

@@ -10,7 +10,7 @@ import { CacheManager } from "@/external/caching/CacheManager.js";
import { CacheType } from "@/external/caching/cacheActions.js";
import { routeHandler } from "@/utils/routerUtils.js";
export const devRouter = Router();
export const devRouter: Router = Router();
devRouter.get("/data", withOrgAuth, async (req: any, res) => {
try {

View File

@@ -1,10 +1,10 @@
import express from "express";
import express, { Router } from "express";
import { FeatureService } from "./FeatureService.js";
export const featureRouter = express.Router();
export const featureRouter: Router = express.Router();
featureRouter.get("", async (req: any, res) => {
featureRouter.get("", async (req: any, res: any) => {
try {
let features = await FeatureService.getFromReq(req);
res.status(200).json({ features });

View File

@@ -15,7 +15,7 @@ import { autumnHandler } from "autumn-js/express";
import { withAdminAuth } from "./admin/withAdminAuth.js";
import { adminRouter } from "./admin/adminRouter.js";
const mainRouter = Router();
const mainRouter: Router = Router();
mainRouter.get("", async (req: any, res) => {
res.status(200).json({ message: "Hello World" });

View File

@@ -7,7 +7,7 @@ import {
UsagePriceConfig,
} from "@autumn/shared";
import { routeHandler } from "@/utils/routerUtils.js";
import express from "express";
import express, { Router } from "express";
import { constructMigrationJob } from "@/internal/migrations/migrationUtils.js";
import { MigrationService } from "@/internal/migrations/MigrationService.js";
import { JobName } from "@/queue/JobName.js";
@@ -20,7 +20,7 @@ import { isFreeProduct } from "@/internal/products/productUtils.js";
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
import { findPrepaidPrice } from "../products/prices/priceUtils/findPriceUtils.js";
export const migrationRouter = express.Router();
export const migrationRouter: Router = express.Router();
migrationRouter.post("", async (req: any, res: any) => {
return routeHandler({

View File

@@ -1,7 +1,3 @@
import { createClerkCli } from "@/external/clerkUtils.js";
import { saveOrgToDB } from "@/external/webhooks/clerkWebhooks.js";
import { auth } from "@/utils/auth.js";
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
import { routeHandler } from "@/utils/routerUtils.js";

View File

@@ -3,7 +3,6 @@ import { Router } from "express";
import { eq } from "drizzle-orm";
import { routeHandler } from "@/utils/routerUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import { createClerkCli } from "@/external/clerkUtils.js";
import { AppEnv } from "@autumn/shared";
import { parseChatResultFeatures } from "./parseChatFeatures.js";
import { parseChatProducts } from "./parseChatProducts.js";
@@ -14,7 +13,7 @@ import { EntitlementService } from "@/internal/products/entitlements/Entitlement
import { PriceService } from "@/internal/products/prices/PriceService.js";
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
export const onboardingRouter = Router();
export const onboardingRouter: Router = Router();
onboardingRouter.post("", async (req: Request, res: any) =>
routeHandler({

View File

@@ -1,4 +1,4 @@
import express from "express";
import express, { Router } from "express";
import Stripe from "stripe";
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
@@ -22,7 +22,7 @@ import { handleInvite } from "./handlers/handleInvite.js";
import { handleGetUploadUrl } from "./handlers/handleGetUploadUrl.js";
import { handleDeleteOrg } from "./handlers/handleDeleteOrg.js";
export const orgRouter = express.Router();
export const orgRouter: Router = express.Router();
orgRouter.get("/members", handleGetOrgMembers);
orgRouter.get("/upload_url", handleGetUploadUrl);
orgRouter.post("/invite", handleInvite as any);

View File

@@ -17,7 +17,7 @@ import { isFeaturePriceItem } from "./product-items/productItemUtils/getItemType
import RecaseError, { handleFrontendReqError } from "@/utils/errorUtils.js";
import { createOrgResponse } from "../orgs/orgUtils.js";
export const productRouter = Router({ mergeParams: true });
export const productRouter: Router = Router({ mergeParams: true });
productRouter.get("/data", async (req: any, res) => {
try {

View File

@@ -1,7 +1,6 @@
import RecaseError from "@/utils/errorUtils.js";
import { nullish } from "@/utils/genUtils.js";
import { numberWithCommas } from "tests/utils/general/numberUtils.js";
import { getFeatureName } from "@/internal/features/utils/displayUtils.js";
import {
@@ -13,6 +12,7 @@ import {
ErrCode,
Infinite,
FullCusProduct,
numberWithCommas,
} from "@autumn/shared";
import { isPriceItem } from "../product-items/productItemUtils/getItemType.js";
import { isFeaturePriceItem } from "../product-items/productItemUtils/getItemType.js";

View File

@@ -1,23 +1,7 @@
import { createSupabaseClient } from "@/external/supabaseUtils.js";
import { Router } from "express";
export const userRouter = Router();
export const userRouter: Router = Router();
userRouter.get("", async (req: any, res) => {
const supabase = createSupabaseClient();
res.status(200).json({ userId: req.userId });
// const { data, error } = await supabase
// .from("users")
// .select("id, email, initialized")
// .eq("id", req.userId)
// .single();
// if (error) {
// console.error("Error fetching user data:", error);
// res.status(500).json({ error: error.message });
// return;
// }
// res.status(200).json(data);
});

View File

@@ -1,6 +1,11 @@
import "dotenv/config";
import { Queue } from "bullmq";
import { Redis } from "ioredis";
const BACKUP_REDIS_URL = process.env.REDIS_BACKUP_URL || process.env.REDIS_URL;
const MAIN_REDIS_URL = process.env.REDIS_URL;
export class QueueManager {
private static instance: QueueManager;
private queue: Queue | null = null;
@@ -33,9 +38,7 @@ export class QueueManager {
}) {
// 1. Connect to redis
const redisUrl = useBackup
? process.env.REDIS_BACKUP_URL
: process.env.REDIS_URL;
const redisUrl = useBackup ? BACKUP_REDIS_URL : MAIN_REDIS_URL;
const connection = new Redis(redisUrl!, {
retryStrategy: (times) => {
@@ -47,7 +50,7 @@ export class QueueManager {
console.log(
`Redis connection error (${useBackup ? "backup" : "main"}): ${
error.message
}`
}`,
);
if (!keepConnection) {
@@ -90,7 +93,7 @@ export class QueueManager {
console.log("2. Initializing main & backup queues");
const mainQueue = new Queue("autumn", {
connection: {
url: process.env.REDIS_URL,
url: MAIN_REDIS_URL,
enableOfflineQueue: false,
retryStrategy: (times) => {
return 5000;
@@ -100,7 +103,7 @@ export class QueueManager {
const backupQueue = new Queue("autumn", {
connection: {
url: process.env.REDIS_BACKUP_URL,
url: BACKUP_REDIS_URL,
enableOfflineQueue: false,
},
});

View File

@@ -10,6 +10,7 @@ import sendOTPEmail from "@/internal/emails/sendOTPEmail.js";
import { sendOnboardingEmail } from "@/internal/emails/sendOnboardingEmail.js";
import { ADMIN_USER_IDs } from "./constants.js";
import { afterOrgCreated } from "./authUtils/afterOrgCreated.js";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // or "mysql", "sqlite"

View File

@@ -1,6 +1,3 @@
import { WebSocketServer } from "ws";
import http from "http";
export class WebsocketManager {
public channels: Map<string, Set<WebSocket>>;
public subscriptions: WeakMap<WebSocket, Set<string>>;

View File

@@ -1,9 +1,6 @@
import { WebSocketServer } from "ws";
import http from "http";
import { createSupabaseClient } from "@/external/supabaseUtils.js";
import { AppEnv, ErrCode } from "@autumn/shared";
import { OrgService } from "@/internal/orgs/OrgService.js";
import { WebSocketServer, WebSocket } from "ws";
export enum SbChannelEvent {
BalanceUpdated = "balance_updated",
@@ -74,7 +71,7 @@ class WebSocketRouter {
constructor(server: http.Server) {
this.wss = new WebSocketServer({ server });
this.wss.on("connection", (ws, req) =>
this.wss.on("connection", (ws: WebSocket, req: http.IncomingMessage) =>
this.handleConnection(ws as any, req as any),
);
}

View File

@@ -7,7 +7,8 @@
"target": "ES2020",
"moduleResolution": "NodeNext",
"module": "NodeNext",
"declaration": true,
"jsx": "react-jsx",
"rootDir": ".",
"baseUrl": ".",
"outDir": "./dist",
@@ -17,9 +18,10 @@
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["src/*"]
"@/*": ["src/*"],
"@emails/*": ["emails/*"]
}
},
"include": ["src"],
"include": ["src", "emails"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -5,8 +5,8 @@
},
"compilerOptions": {
"target": "ES2020",
"moduleResolution": "NodeNext", // or "node16"/"nodenext"
"module": "NodeNext", // or "node16"/"nodenext"
"moduleResolution": "NodeNext",
"module": "NodeNext",
// "declaration": true,
"jsx": "react-jsx",

View File

@@ -12,15 +12,18 @@
},
"scripts": {
"build": "tsc",
"dev": "nodemon --watch . --ext ts --ignore dist --exec 'pnpm run build'",
"db:generate": "NODE_OPTIONS='--import tsx' drizzle-kit generate --config drizzle.config.ts",
"db:migrate": "NODE_OPTIONS='--import tsx' drizzle-kit migrate --config drizzle.config.ts"
},
"dependencies": {
"decimal.js": "^10.5.0",
"drizzle-orm": "^0.43.1",
"drizzle-zod": "^0.8.1",
"drizzle-zod": "^0.8.2",
"zod": "^3.25.23"
},
"devDependencies": {
"nodemon": "^3.1.7",
"tsx": "^4.19.4",
"typescript": "^5.7.2"
}

View File

@@ -1,16 +1,17 @@
{
"name": "@autumn/frontend2",
"name": "@autumn/vite",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"dev": "vite --port 3000 --host",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"start": "serve -s dist"
},
"dependencies": {
"@autumn/shared": "*",
"@autumn/shared": "workspace:*",
"@clerk/clerk-react": "^5.24.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
@@ -32,10 +33,12 @@
"@wooorm/starry-night": "^3.6.0",
"autumn-js": "^0.0.41",
"axios": "^1.8.3",
"better-auth": "^1.2.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"decimal.js": "^10.5.0",
"hast-util-to-html": "^9.0.5",
"input-otp": "^1.4.2",
"lodash": "^4.17.21",
@@ -55,10 +58,12 @@
"react-router": "^7.3.0",
"sonner": "^2.0.1",
"svix-react": "^1.13.3",
"swr": "^2.3.3",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.13",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2"
"vaul": "^1.1.2",
"zod": "^3.25.23"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
@@ -75,11 +80,5 @@
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
},
"resolutions": {
"@rollup/rollup-linux-x64-gnu": "npm:@rollup/rollup-linux-x64-musl"
},
"overrides": {
"@rollup/rollup-linux-x64-gnu": "npm:@rollup/rollup-linux-x64-musl"
}
}

View File

@@ -1,6 +1,5 @@
import { Reward, CreateReward } from "@autumn/shared";
import { SupabaseClient } from "@supabase/supabase-js";
import { AxiosInstance } from "axios";
export class RewardService {

View File

@@ -76,7 +76,7 @@ export const columns: OrgColumnDef[] = [
width: "100%",
cell: ({ row }: { row: Row<Org> }) => {
const value = row.getValue("users");
return <span className="truncate">{value?.join(", ")}</span>;
return <span className="truncate">{(value as string[]).join(", ")}</span>;
},
},
];