diff --git a/.devcontainer/stripe-to-invoice/post-install.sh b/.devcontainer/stripe-to-invoice/post-install.sh index e69de29..482fcc1 100644 --- a/.devcontainer/stripe-to-invoice/post-install.sh +++ b/.devcontainer/stripe-to-invoice/post-install.sh @@ -0,0 +1 @@ +cv stripe_to_invoice && npm install; \ No newline at end of file diff --git a/db/atlas/stripe_invoice/migrations/20260221120000_add_subscriptions.sql b/db/atlas/stripe_invoice/migrations/20260221120000_add_subscriptions.sql new file mode 100644 index 0000000..282f834 --- /dev/null +++ b/db/atlas/stripe_invoice/migrations/20260221120000_add_subscriptions.sql @@ -0,0 +1,18 @@ +-- Create subscriptions table to track user subscriptions +CREATE TABLE subscriptions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE, + stripe_subscription_id TEXT UNIQUE, + stripe_customer_id TEXT, + status TEXT NOT NULL DEFAULT 'trialing', + current_period_start TIMESTAMPTZ, + current_period_end TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + canceled_at TIMESTAMPTZ +); + +-- Create index for faster lookups +CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id); +CREATE INDEX idx_subscriptions_stripe_subscription_id ON subscriptions(stripe_subscription_id); +CREATE INDEX idx_subscriptions_status ON subscriptions(status); diff --git a/db/atlas/stripe_invoice/migrations/20260221120100_add_payments.sql b/db/atlas/stripe_invoice/migrations/20260221120100_add_payments.sql new file mode 100644 index 0000000..37b7f97 --- /dev/null +++ b/db/atlas/stripe_invoice/migrations/20260221120100_add_payments.sql @@ -0,0 +1,17 @@ +-- Create payments table to track subscription payments +CREATE TABLE payments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + stripe_invoice_id TEXT NOT NULL UNIQUE, + amount BIGINT NOT NULL, + currency TEXT NOT NULL, + status TEXT NOT NULL, + paid_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Create indexes for faster lookups +CREATE INDEX idx_payments_user_id ON payments(user_id); +CREATE INDEX idx_payments_stripe_invoice_id ON payments(stripe_invoice_id); +CREATE INDEX idx_payments_status ON payments(status); diff --git a/postiz/postiz.yaml b/postiz/postiz.yaml index 64960a9..fb770d3 100644 --- a/postiz/postiz.yaml +++ b/postiz/postiz.yaml @@ -1,3 +1,4 @@ +# This didn't work but can be kept so I remember to deploy # ====================================================== # POSTIZ FULL STACK (KUBERNETES - SINGLE FILE) # ====================================================== @@ -227,6 +228,7 @@ spec: # POSTIZ APP # ------------------------- --- +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -244,6 +246,7 @@ spec: containers: - name: postiz image: ghcr.io/gitroomhq/postiz-app:latest + env: - name: MAIN_URL value: https://postiz.juntekim.com @@ -251,23 +254,48 @@ spec: value: https://postiz.juntekim.com - name: NEXT_PUBLIC_BACKEND_URL value: https://postiz.juntekim.com/api + - name: BACKEND_INTERNAL_URL + value: http://postiz:5000 + - name: JWT_SECRET - value: change-me + value: super-long-random-string-here + - name: DATABASE_URL value: postgresql://postiz-user:postiz-password@postiz-postgres:5432/postiz-db-local + - name: REDIS_URL value: redis://postiz-redis:6379 + - name: TEMPORAL_ADDRESS value: temporal:7233 + + - name: IS_GENERAL + value: "true" + - name: DISABLE_REGISTRATION + value: "false" + - name: RUN_CRON + value: "true" + - name: API_LIMIT + value: "30" + - name: STORAGE_PROVIDER value: local - name: UPLOAD_DIRECTORY value: /uploads - name: NEXT_PUBLIC_UPLOAD_DIRECTORY value: /uploads + ports: - containerPort: 5000 + volumeMounts: + - name: postiz-uploads + mountPath: /uploads + + volumes: + - name: postiz-uploads + emptyDir: {} + --- apiVersion: v1 kind: Service diff --git a/stripe_to_invoice/lib/schema/index.ts b/stripe_to_invoice/lib/schema/index.ts index 21ce321..85cfb23 100644 --- a/stripe_to_invoice/lib/schema/index.ts +++ b/stripe_to_invoice/lib/schema/index.ts @@ -5,4 +5,6 @@ export * from "./stripeAccounts"; export * from "./xeroConnections"; export * from "./processedStripeEvents"; export * from "./sessions"; +export * from "./subscriptions"; +export * from "./payments"; diff --git a/stripe_to_invoice/lib/schema/payments.ts b/stripe_to_invoice/lib/schema/payments.ts new file mode 100644 index 0000000..c151ebf --- /dev/null +++ b/stripe_to_invoice/lib/schema/payments.ts @@ -0,0 +1,40 @@ +import { + pgTable, + uuid, + text, + timestamp, + bigint, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; + +export const payments = pgTable("payments", { + id: uuid("id").defaultRandom().primaryKey(), + userId: uuid("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + + // Stripe invoice ID + stripeInvoiceId: text("stripe_invoice_id").notNull().unique(), + + // Amount in cents + amount: bigint("amount", { mode: "number" }).notNull(), + + // Currency (USD, GBP, etc) + currency: text("currency").notNull(), + + // Invoice status: "draft", "open", "paid", "void", "uncollectible" + status: text("status").notNull(), + + // When the payment was made + paidAt: timestamp("paid_at", { withTimezone: true }), + + // When the record was created + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + // When the record was updated + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), +}); diff --git a/stripe_to_invoice/lib/schema/subscriptions.ts b/stripe_to_invoice/lib/schema/subscriptions.ts new file mode 100644 index 0000000..833f164 --- /dev/null +++ b/stripe_to_invoice/lib/schema/subscriptions.ts @@ -0,0 +1,43 @@ +import { + pgTable, + uuid, + text, + timestamp, + boolean, + integer, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; + +export const subscriptions = pgTable("subscriptions", { + id: uuid("id").defaultRandom().primaryKey(), + userId: uuid("user_id") + .notNull() + .unique() + .references(() => users.id, { onDelete: "cascade" }), + + // Stripe subscription ID + stripeSubscriptionId: text("stripe_subscription_id").unique(), + + // Stripe customer ID + stripeCustomerId: text("stripe_customer_id"), + + // Subscription status: "active", "trialing", "past_due", "canceled", "unpaid" + status: text("status").notNull().default("trialing"), + + // Current period start/end + currentPeriodStart: timestamp("current_period_start", { withTimezone: true }), + currentPeriodEnd: timestamp("current_period_end", { withTimezone: true }), + + // When subscription was created + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + // When subscription was last updated + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + // When subscription was canceled (if applicable) + canceledAt: timestamp("canceled_at", { withTimezone: true }), +});