remixed v0

This commit is contained in:
m5r
2022-05-14 12:22:06 +02:00
parent 9275d4499b
commit 98b89ae0f7
338 changed files with 22549 additions and 44628 deletions

View File

@ -0,0 +1,110 @@
-- CreateEnum
CREATE TYPE "MembershipRole" AS ENUM ('OWNER', 'USER');
-- CreateEnum
CREATE TYPE "GlobalRole" AS ENUM ('SUPERADMIN', 'CUSTOMER');
-- CreateEnum
CREATE TYPE "TokenType" AS ENUM ('RESET_PASSWORD', 'INVITE_MEMBER');
-- CreateTable
CREATE TABLE "Organization" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ NOT NULL,
"slug" TEXT NOT NULL,
"name" TEXT NOT NULL,
"stripeCustomerId" TEXT,
"stripeSubscriptionId" TEXT,
"stripePriceId" TEXT,
"stripeCurrentPeriodEnd" TIMESTAMP(3),
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Membership" (
"id" TEXT NOT NULL,
"role" "MembershipRole" NOT NULL,
"organizationId" TEXT NOT NULL,
"userId" TEXT,
"invitedEmail" TEXT,
CONSTRAINT "Membership_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ NOT NULL,
"fullName" TEXT NOT NULL,
"email" TEXT NOT NULL,
"hashedPassword" TEXT,
"role" "GlobalRole" NOT NULL DEFAULT E'CUSTOMER',
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ NOT NULL,
"expiresAt" TIMESTAMPTZ,
"data" TEXT NOT NULL,
"userId" TEXT,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Token" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ NOT NULL,
"hashedToken" TEXT NOT NULL,
"type" "TokenType" NOT NULL,
"expiresAt" TIMESTAMPTZ NOT NULL,
"sentTo" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"membershipId" TEXT NOT NULL,
CONSTRAINT "Token_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Organization_slug_key" ON "Organization"("slug");
-- CreateIndex
CREATE UNIQUE INDEX "Organization_stripeCustomerId_key" ON "Organization"("stripeCustomerId");
-- CreateIndex
CREATE UNIQUE INDEX "Organization_stripeSubscriptionId_key" ON "Organization"("stripeSubscriptionId");
-- CreateIndex
CREATE UNIQUE INDEX "Membership_organizationId_invitedEmail_key" ON "Membership"("organizationId", "invitedEmail");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Token_membershipId_key" ON "Token"("membershipId");
-- CreateIndex
CREATE UNIQUE INDEX "Token_hashedToken_type_key" ON "Token"("hashedToken", "type");
-- AddForeignKey
ALTER TABLE "Membership" ADD CONSTRAINT "Membership_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Membership" ADD CONSTRAINT "Membership_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Token" ADD CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Token" ADD CONSTRAINT "Token_membershipId_fkey" FOREIGN KEY ("membershipId") REFERENCES "Membership"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,115 @@
/*
Warnings:
- You are about to drop the column `slug` on the `Organization` table. All the data in the column will be lost.
- You are about to drop the column `stripeCurrentPeriodEnd` on the `Organization` table. All the data in the column will be lost.
- You are about to drop the column `stripeCustomerId` on the `Organization` table. All the data in the column will be lost.
- You are about to drop the column `stripePriceId` on the `Organization` table. All the data in the column will be lost.
- You are about to drop the column `stripeSubscriptionId` on the `Organization` table. All the data in the column will be lost.
*/
-- CreateEnum
CREATE TYPE "SubscriptionStatus" AS ENUM ('active', 'trialing', 'past_due', 'paused', 'deleted');
-- CreateEnum
CREATE TYPE "Direction" AS ENUM ('Inbound', 'Outbound');
-- CreateEnum
CREATE TYPE "MessageStatus" AS ENUM ('Queued', 'Sending', 'Sent', 'Failed', 'Delivered', 'Undelivered', 'Receiving', 'Received', 'Accepted', 'Scheduled', 'Read', 'PartiallyDelivered', 'Canceled', 'Error');
-- CreateEnum
CREATE TYPE "CallStatus" AS ENUM ('Queued', 'Ringing', 'InProgress', 'Completed', 'Busy', 'Failed', 'NoAnswer', 'Canceled');
-- DropIndex
DROP INDEX "Organization_slug_key";
-- DropIndex
DROP INDEX "Organization_stripeCustomerId_key";
-- DropIndex
DROP INDEX "Organization_stripeSubscriptionId_key";
-- AlterTable
ALTER TABLE "Organization" DROP COLUMN "slug",
DROP COLUMN "stripeCurrentPeriodEnd",
DROP COLUMN "stripeCustomerId",
DROP COLUMN "stripePriceId",
DROP COLUMN "stripeSubscriptionId";
-- CreateTable
CREATE TABLE "Subscription" (
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ NOT NULL,
"paddleSubscriptionId" INTEGER NOT NULL,
"paddlePlanId" INTEGER NOT NULL,
"paddleCheckoutId" TEXT NOT NULL,
"status" "SubscriptionStatus" NOT NULL,
"updateUrl" TEXT NOT NULL,
"cancelUrl" TEXT NOT NULL,
"currency" TEXT NOT NULL,
"unitPrice" DOUBLE PRECISION NOT NULL,
"nextBillDate" DATE NOT NULL,
"lastEventTime" TIMESTAMP NOT NULL,
"cancellationEffectiveDate" DATE,
"organizationId" TEXT NOT NULL,
CONSTRAINT "Subscription_pkey" PRIMARY KEY ("paddleSubscriptionId")
);
-- CreateTable
CREATE TABLE "Message" (
"id" TEXT NOT NULL,
"sentAt" TIMESTAMPTZ NOT NULL,
"content" TEXT NOT NULL,
"from" TEXT NOT NULL,
"to" TEXT NOT NULL,
"direction" "Direction" NOT NULL,
"status" "MessageStatus" NOT NULL,
"phoneNumberId" TEXT NOT NULL,
CONSTRAINT "Message_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PhoneCall" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"from" TEXT NOT NULL,
"to" TEXT NOT NULL,
"status" "CallStatus" NOT NULL,
"direction" "Direction" NOT NULL,
"duration" TEXT NOT NULL,
"phoneNumberId" TEXT NOT NULL,
CONSTRAINT "PhoneCall_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PhoneNumber" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"number" TEXT NOT NULL,
"hasFetchedMessages" BOOLEAN,
"hasFetchedCalls" BOOLEAN,
"organizationId" TEXT NOT NULL,
CONSTRAINT "PhoneNumber_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Subscription_paddleSubscriptionId_key" ON "Subscription"("paddleSubscriptionId");
-- CreateIndex
CREATE UNIQUE INDEX "PhoneNumber_organizationId_id_key" ON "PhoneNumber"("organizationId", "id");
-- AddForeignKey
ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Message" ADD CONSTRAINT "Message_phoneNumberId_fkey" FOREIGN KEY ("phoneNumberId") REFERENCES "PhoneNumber"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PhoneCall" ADD CONSTRAINT "PhoneCall_phoneNumberId_fkey" FOREIGN KEY ("phoneNumberId") REFERENCES "PhoneNumber"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PhoneNumber" ADD CONSTRAINT "PhoneNumber_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

199
prisma/schema.prisma Normal file
View File

@ -0,0 +1,199 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Organization {
id String @id @default(cuid())
createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz
name String
memberships Membership[]
phoneNumbers PhoneNumber[]
subscriptions Subscription[] // many subscriptions to keep a history
}
model Subscription {
createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz
paddleSubscriptionId Int @id @unique
paddlePlanId Int
paddleCheckoutId String
status SubscriptionStatus
updateUrl String
cancelUrl String
currency String
unitPrice Float
nextBillDate DateTime @db.Date
lastEventTime DateTime @db.Timestamp
cancellationEffectiveDate DateTime? @db.Date
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
}
enum SubscriptionStatus {
active
trialing // not used
past_due
paused // not used
deleted
}
model Membership {
id String @id @default(cuid())
role MembershipRole
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String?
// When the user joins, we will clear out the email and set the user.
invitedEmail String?
invitationToken Token?
@@unique([organizationId, invitedEmail])
}
enum MembershipRole {
OWNER
USER
}
// The owners of the SaaS (you) can have a SUPERADMIN role to access all data
enum GlobalRole {
SUPERADMIN
CUSTOMER
}
model User {
id String @id @default(cuid())
createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz
fullName String
email String @unique
hashedPassword String?
role GlobalRole @default(CUSTOMER)
memberships Membership[]
tokens Token[]
sessions Session[]
}
model Session {
id String @id @default(cuid())
createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz
expiresAt DateTime? @db.Timestamptz
data String
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String?
}
model Token {
id String @id @default(cuid())
createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz
hashedToken String
type TokenType
expiresAt DateTime @db.Timestamptz
sentTo String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
membership Membership @relation(fields: [membershipId], references: [id], onDelete: Cascade)
membershipId String @unique
@@unique([hashedToken, type])
}
enum TokenType {
RESET_PASSWORD
INVITE_MEMBER
}
model Message {
id String @id
sentAt DateTime @db.Timestamptz
content String
from String
to String
direction Direction
status MessageStatus
phoneNumber PhoneNumber @relation(fields: [phoneNumberId], references: [id], onDelete: Cascade)
phoneNumberId String
}
enum Direction {
Inbound
Outbound
}
enum MessageStatus {
Queued
Sending
Sent
Failed
Delivered
Undelivered
Receiving
Received
Accepted
Scheduled
Read
PartiallyDelivered
Canceled
Error
}
model PhoneCall {
id String @id
createdAt DateTime @default(now()) @db.Timestamptz
from String
to String
status CallStatus
direction Direction
duration String
phoneNumber PhoneNumber @relation(fields: [phoneNumberId], references: [id], onDelete: Cascade)
phoneNumberId String
}
enum CallStatus {
Queued
Ringing
InProgress
Completed
Busy
Failed
NoAnswer
Canceled
}
model PhoneNumber {
id String @id
createdAt DateTime @default(now()) @db.Timestamptz
number String
isFetchingMessages Boolean?
isFetchingCalls Boolean?
messages Message[]
phoneCalls PhoneCall[]
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
@@unique([organizationId, id])
}

49
prisma/seed.ts Normal file
View File

@ -0,0 +1,49 @@
import crypto from "crypto";
import { GlobalRole, MembershipRole } from "@prisma/client";
import db from "~/utils/db.server";
import { hashPassword } from "~/utils/auth.server";
import slugify from "~/utils/slugify";
async function seed() {
const email = "remixtape@admin.local";
const orgName = "Get Psyched";
const orgSlug = slugify(orgName);
const password = crypto.randomBytes(8).toString("hex");
// cleanup the existing database
await db.user.delete({ where: { email } }).catch(() => {});
await db.organization.delete({ where: { slug: orgSlug } }).catch(() => {});
await db.user.create({
data: {
email,
fullName: "Admin",
hashedPassword: await hashPassword(password),
role: GlobalRole.SUPERADMIN,
memberships: {
create: {
role: MembershipRole.OWNER,
organization: {
create: { name: orgName, slug: orgSlug },
},
},
},
},
});
console.log("\nDatabase has been seeded. 🌱");
console.log("You can log into the newly-seeded admin account with the following credentials:");
console.log(`\x1B[1m\x1B[4memail\x1B[0m: ${email}`);
console.log(`\x1B[1m\x1B[4mpassword:\x1B[0m ${password}`);
}
seed()
.catch((error) => {
console.error(error);
process.exit(1);
})
.finally(async () => {
await db.$disconnect();
});