fix build issues, fix deployment issues in Dockerfile and docker-compose, move new form modal to formList

This commit is contained in:
Matthias Nannt
2022-06-24 12:45:55 +09:00
parent 29bd1973f2
commit 784f58686a
18 changed files with 174 additions and 523 deletions
+4 -2
View File
@@ -11,7 +11,9 @@ FROM node:16-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN yarn prisma:generate && yarn build && yarn install --production --ignore-scripts --prefer-offline
RUN yarn prisma generate
RUN yarn tsc prisma/seed.ts
RUN yarn build && yarn install --production --ignore-scripts --prefer-offline
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
@@ -23,7 +25,7 @@ RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
COPY --from=builder /app/next.config.js ./
# COPY --from=builder /app/public ./public
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
+3 -3
View File
@@ -91,7 +91,7 @@ yarn dev
## Deployment
The easiest way to deploy snoopForms yourself on your own machine is using Docker. This requires Docker and docker-compose on your system to work.
The easiest way to deploy snoopForms yourself on your own machine is using Docker. This requires Docker and the docker compose plugin on your system to work.
Clone the repository:
@@ -109,11 +109,11 @@ cp .env.example .env && nano .env
```
Start the docker-compose process to build and spin up the snoopForms container as well as the postgres database.
Start the docker compose process to build and spin up the snoopForms container as well as the postgres database.
```
docker-compose up -d
docker compose up -d
```
+133 -121
View File
@@ -8,17 +8,18 @@ import {
import { DotsHorizontalIcon, TrashIcon } from "@heroicons/react/solid";
import Link from "next/link";
import Router from "next/router";
import { Fragment } from "react";
import { Fragment, useState } from "react";
import { createForm, useForms } from "../lib/forms";
import { classNames } from "../lib/utils";
import NewFormModal from "./form/NewFormModal";
import EmptyPageFiller from "./layout/EmptyPageFiller";
export default function FormList() {
const { forms, mutateForms } = useForms();
const [openNewFormModal, setOpenNewFormModal] = useState(false);
const newForm = async () => {
const form = await createForm();
await Router.push(`/forms/${form.id}/welcome`);
setOpenNewFormModal(true);
};
const deleteForm = async (form, formIdx) => {
@@ -35,127 +36,138 @@ export default function FormList() {
}
};
{
console.log(JSON.stringify(forms, null, 2));
}
return (
<div>
{forms &&
(forms.length === 0 ? (
<div className="mt-5 text-center">
<EmptyPageFiller
onClick={() => newForm()}
alertText="You don't have any forms yet."
hintText="Start by creating a form."
buttonText="create form"
borderStyles="border-4 border-dotted border-red"
button={true}
>
<DocumentAddIcon className="w-24 h-24 mx-auto text-ui-gray-medium stroke-thin" />
</EmptyPageFiller>
</div>
) : (
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 place-content-stretch">
<button onClick={() => newForm()}>
<li className="col-span-1">
<div className="overflow-hidden font-light text-white rounded-md shadow bg-snoopfade">
<div className="px-4 py-8 sm:p-14">
<PlusIcon className="mx-auto w-14 h-14 stroke-thin" />
create form
</div>
</div>
</li>
</button>
{forms
.sort((a, b) => b.updatedAt - a.updatedAt)
.map((form, formIdx) => (
<li key={form.id} className="col-span-1 realative ">
<div className="flex flex-col justify-between h-full bg-white rounded-md shadow">
<div className="px-4 py-5 text-lg sm:p-6">{form.name}</div>
<Link href={`/forms/${form.id}`}>
<a className="absolute w-full h-full" />
</Link>
<div className="divide-y divide-ui-gray-light ">
<div className="inline-flex px-2 py-1 mb-2 ml-4 text-sm rounded-sm bg-ui-gray-light text-ui-gray-dark">
{form.formType == "NOCODE" ? (
<div className="flex">
<ViewGridAddIcon className="w-4 h-4 my-auto mr-1" />
No-Code
</div>
) : (
<div className="flex">
<TerminalIcon className="w-4 h-4 my-auto mr-1" />
Code
</div>
)}
</div>
<div className="flex justify-between px-4 py-2 text-right sm:px-6">
<p className="text-xs text-ui-gray-medium ">
{form.submissionSessions?.length} responses
</p>
<Menu
as="div"
className="relative inline-block text-left"
>
{({ open }) => (
<>
<div>
<Menu.Button className="flex items-center p-2 -m-2 rounded-full text-red">
<span className="sr-only">Open options</span>
<DotsHorizontalIcon
className="w-5 h-5"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
static
className="absolute left-0 w-56 px-1 mt-2 origin-top-right bg-white rounded-sm shadow-lg"
>
<div className="py-1">
<Menu.Item>
{({ active }) => (
<button
onClick={() =>
deleteForm(form, formIdx)
}
className={classNames(
active
? "bg-ui-gray-light rounded-sm text-ui-black"
: "text-ui-gray-dark",
"flex px-4 py-2 text-sm w-full"
)}
>
<TrashIcon
className="w-5 h-5 mr-3 text-ui-gray-dark"
aria-hidden="true"
/>
<span>Delete Form</span>
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</>
)}
</Menu>
</div>
<>
<div>
{forms &&
(forms.length === 0 ? (
<div className="mt-5 text-center">
<EmptyPageFiller
onClick={() => newForm()}
alertText="You don't have any forms yet."
hintText="Start by creating a form."
buttonText="create form"
borderStyles="border-4 border-dotted border-red"
button={true}
>
<DocumentAddIcon className="w-24 h-24 mx-auto text-ui-gray-medium stroke-thin" />
</EmptyPageFiller>
</div>
) : (
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 place-content-stretch">
<button onClick={() => newForm()}>
<li className="col-span-1">
<div className="overflow-hidden font-light text-white rounded-md shadow bg-snoopfade">
<div className="px-4 py-8 sm:p-14">
<PlusIcon className="mx-auto w-14 h-14 stroke-thin" />
create form
</div>
</div>
</li>
))}
</ul>
))}
</div>
</button>
{forms
.sort((a, b) => b.updatedAt - a.updatedAt)
.map((form, formIdx) => (
<li key={form.id} className="relative col-span-1 realative ">
<div className="flex flex-col justify-between h-full bg-white rounded-md shadow">
<div className="px-4 py-5 text-lg sm:p-6">
{form.name}
</div>
<Link href={`/forms/${form.id}`}>
<a className="absolute w-full h-full" />
</Link>
<div className="divide-y divide-ui-gray-light ">
<div className="inline-flex px-2 py-1 mb-2 ml-4 text-sm rounded-sm bg-ui-gray-light text-ui-gray-dark">
{form.formType == "NOCODE" ? (
<div className="flex">
<ViewGridAddIcon className="w-4 h-4 my-auto mr-1" />
No-Code
</div>
) : (
<div className="flex">
<TerminalIcon className="w-4 h-4 my-auto mr-1" />
Code
</div>
)}
</div>
<div className="flex justify-between px-4 py-2 text-right sm:px-6">
<p className="text-xs text-ui-gray-medium ">
{form._count?.submissionSessions} responses
</p>
<Menu
as="div"
className="relative inline-block text-left"
>
{({ open }) => (
<>
<div>
<Menu.Button className="flex items-center p-2 -m-2 rounded-full text-red">
<span className="sr-only">
Open options
</span>
<DotsHorizontalIcon
className="w-5 h-5"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
static
className="absolute left-0 w-56 px-1 mt-2 origin-top-right bg-white rounded-sm shadow-lg"
>
<div className="py-1">
<Menu.Item>
{({ active }) => (
<button
onClick={() =>
deleteForm(form, formIdx)
}
className={classNames(
active
? "bg-ui-gray-light rounded-sm text-ui-black"
: "text-ui-gray-dark",
"flex px-4 py-2 text-sm w-full"
)}
>
<TrashIcon
className="w-5 h-5 mr-3 text-ui-gray-dark"
aria-hidden="true"
/>
<span>Delete Form</span>
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</>
)}
</Menu>
</div>
</div>
</div>
</li>
))}
</ul>
))}
</div>
<NewFormModal open={openNewFormModal} setOpen={setOpenNewFormModal} />
</>
);
}
+4
View File
@@ -123,6 +123,10 @@ export default function Builder({ formId }) {
return <Loading />;
}
{
console.log("loaded nocode");
}
return (
<>
<SecondNavBar>
-209
View File
@@ -1,209 +0,0 @@
/* This example requires Tailwind CSS v2.0+ */
import { Dialog, RadioGroup, Transition } from "@headlessui/react";
import { CheckCircleIcon, XIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router";
import { Fragment, useState } from "react";
import { persistForm, useForm } from "../../lib/forms";
import { createNoCodeForm } from "../../lib/noCodeForm";
import { classNames } from "../../lib/utils";
import Loading from "../Loading";
import StandardButton from "../StandardButton";
import { BsPlus } from "react-icons/bs";
const formTypes = [
{
id: "NOCODE",
title: "No-Code Builder",
description:
"Use the Notion-like builder to build your form without a single line of code.",
},
{
id: "CODE",
title: "Code",
description:
"Use the snoopReact library to code the form yourself and manage the data here.",
additionalDescription: "",
},
];
type FormOnboardingModalProps = {
open: boolean;
formId: string;
};
export default function FormOnboardingModal({
open,
formId,
}: FormOnboardingModalProps) {
const router = useRouter();
const { form, mutateForm, isLoadingForm } = useForm(formId);
const [name, setName] = useState(form.name);
const [formType, setFormType] = useState(formTypes[0]);
const submitForm = async (e) => {
e.preventDefault();
const updatedForm = {
...form,
name,
finishedOnboarding: true,
formType: formType.id,
};
await persistForm(updatedForm);
mutateForm(updatedForm);
if (updatedForm.formType === "NOCODE") {
await createNoCodeForm(formId);
}
router.push(`/forms/${formId}/form`);
};
if (isLoadingForm) {
return <Loading />;
}
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
static
className="fixed inset-0 z-10 overflow-y-auto"
open={open}
onClose={() => {}}
>
<div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 transition-opacity bg-ui-gray-medium bg-opacity-10 backdrop-blur" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<form
onSubmit={(e) => submitForm(e)}
className="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-md shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6"
>
<div className="flex flex-row justify-between">
<h2 className="flex-none pb-4 text-xl font-bold text-ui-gray-dark">
Create new form
</h2>
<XIcon className="flex-initial w-6 h-6 stroke-1 text-ui-gray-light" />
</div>
<div>
<label
htmlFor="email"
className="text-sm font-light text-ui-gray-dark"
>
Name your form
</label>
<div className="mt-2">
<input
type="text"
name="name"
className="block w-full p-2 mb-8 border-none rounded bg-ui-gray-light focus:ring-2 focus:ring-red sm:text-sm placeholder:font-extralight placeholder:text-ui-gray-medium"
placeholder="e.g. Customer Research Survey"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
</div>
<RadioGroup value={formType} onChange={setFormType}>
<RadioGroup.Label className="text-sm font-light text-ui-gray-dark">
How do you build your form?
</RadioGroup.Label>
<div className="grid grid-cols-1 mt-4 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
{formTypes.map((formType) => (
<RadioGroup.Option
key={formType.id}
value={formType}
className={({ checked, active }) =>
classNames(
checked ? "border-transparent" : "",
active
? "border-red ring-2 ring-red"
: "bg-ui-gray-lighter",
"relative bg-white border rounded shadow-sm p-4 flex cursor-pointer focus:outline-none"
)
}
>
{({ checked, active }) => (
<>
<span className="flex flex-1">
<span className="flex flex-col">
<RadioGroup.Label
as="span"
className="block font-bold text-md text-ui-gray-dark"
>
{formType.title}
</RadioGroup.Label>
<RadioGroup.Description
as="span"
className="flex items-center mt-1 text-xs whitespace-pre-wrap text-ui-gray-dark"
>
{formType.description}
</RadioGroup.Description>
</span>
</span>
<CheckCircleIcon
className={classNames(
!checked ? "hidden" : "",
"h-5 w-5 text-red"
)}
aria-hidden="true"
/>
<div
className={classNames(
checked ? "hidden" : "",
"h-4 w-4 rounded-full border-2 border-ui-gray-light"
)}
aria-hidden="true"
/>
<span
className={classNames(
active ? "border" : "border-2",
checked ? "border-red" : "border-transparent",
"absolute -inset-px rounded pointer-events-none"
)}
aria-hidden="true"
/>
</>
)}
</RadioGroup.Option>
))}
</div>
</RadioGroup>
<div className="mt-5 sm:mt-6">
<StandardButton fullwidth type="submit">
create form
<BsPlus className="w-6 h-6 ml-1"></BsPlus>
</StandardButton>
</div>
</form>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
}
+1 -1
View File
@@ -23,7 +23,7 @@ export default function Layout({ children }) {
}
return (
<div className="min-h-screen bg-bggray">
<div className="min-h-screen bg-white">
<Disclosure as="nav" className="bg-white shadow-sm">
{({ open }) => (
<>
+1 -8
View File
@@ -1,18 +1,11 @@
import React from "react";
import { classNames } from "../../lib/utils";
interface Props {
children: React.ReactNode;
onClick?: () => void;
[key: string]: any;
}
// button component, consuming props
const SecondNavBar: React.FC<Props> = ({
children,
onClick = () => {},
...rest
}) => {
const SecondNavBar: React.FC<Props> = ({ children }) => {
return (
<div className="relative z-10 flex flex-shrink-0 h-16 py-2 bg-ui-gray-lighter">
<div className="flex items-center justify-center flex-1 px-4 py-2">
+2 -2
View File
@@ -5,11 +5,11 @@ import Link from "next/link";
interface Props {
children: React.ReactNode;
onClick?: () => void;
itemLabel: string;
itemLabel?: string;
disabled?: boolean;
link?: boolean;
outbound?: boolean;
href: string;
href?: string;
}
// button component, consuming props
+6 -1
View File
@@ -9,7 +9,12 @@ services:
snoopforms:
build: .
command: [sh, -c, "yarn prisma:migrate && yarn start"]
command:
[
sh,
-c,
"yarn prisma migrate deploy && yarn prisma db seed && yarn start",
]
depends_on:
- postgres
ports:
+2 -2
View File
@@ -35,12 +35,12 @@ export const persistForm = async (form) => {
}
};
export const createForm = async () => {
export const createForm = async (form = {}) => {
try {
const res = await fetch(`/api/forms`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
body: JSON.stringify(form),
});
return await res.json();
} catch (error) {
+2 -1
View File
@@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"seed:prod": "node prisma/seed.js"
},
"dependencies": {
"@editorjs/editorjs": "^2.24.3",
+3 -3
View File
@@ -24,9 +24,9 @@ export default async function handle(
owner: {
select: { name: true },
},
submissionSessions: {
select: { id: true },
}
_count: {
select: { submissionSessions: true },
},
},
});
res.json(formData);
-5
View File
@@ -17,11 +17,6 @@ export default function FormPage() {
return <Loading />;
}
if (!form.finishedOnboarding) {
router.push(`/forms/${formId}/welcome`);
return <div></div>;
}
if (form.formType === "NOCODE") {
return (
<>
+4 -7
View File
@@ -1,16 +1,13 @@
import { DocumentSearchIcon } from "@heroicons/react/outline";
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import { FaDiscord, FaReact, FaVuejs } from "react-icons/fa";
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
import Loading from "../../../components/Loading";
import StandardButton from "../../../components/StandardButton";
import { useForm } from "../../../lib/forms";
import { DocumentSearchIcon } from "@heroicons/react/outline";
import Link from "next/link";
import SecondNavBar from "../../../components/layout/SecondNavBar";
import SecondNavBarItem from "../../../components/layout/SecondNavBarItem";
import { FaReact, FaVuejs } from "react-icons/fa";
import { FaDiscord } from "react-icons/fa";
import Loading from "../../../components/Loading";
import { useForm } from "../../../lib/forms";
export default function ReactPage() {
const router = useRouter();
-36
View File
@@ -1,36 +0,0 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import FormOnboardingModal from "../../../components/form/FormOnboardingModal";
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
import Loading from "../../../components/Loading";
import { useForm } from "../../../lib/forms";
export default function WelcomePage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
if (isLoadingForm) {
return <Loading />;
}
if (!form.finishedOnboarding) {
return (
<LayoutFormBasics title={form.title} formId={formId} currentStep="form">
<FormOnboardingModal open={true} formId={formId} />
</LayoutFormBasics>
);
} else {
router.push(`/forms/${formId}`);
return <Loading />;
}
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};
@@ -1,116 +0,0 @@
-- CreateEnum
CREATE TYPE "FormType" AS ENUM ('CODE', 'NOCODE');
-- CreateEnum
CREATE TYPE "PipelineType" AS ENUM ('WEBHOOK');
-- CreateTable
CREATE TABLE "Form" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"ownerId" INTEGER NOT NULL,
"formType" "FormType" NOT NULL DEFAULT E'NOCODE',
"name" TEXT NOT NULL,
"published" BOOLEAN NOT NULL DEFAULT false,
"finishedOnboarding" BOOLEAN NOT NULL DEFAULT false,
"schema" JSONB NOT NULL,
CONSTRAINT "Form_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "NoCodeForm" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"formId" TEXT NOT NULL,
"pages" JSONB NOT NULL DEFAULT '[]',
"pagesDraft" JSONB NOT NULL DEFAULT '[]',
CONSTRAINT "NoCodeForm_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pipeline" (
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"type" "PipelineType" NOT NULL,
"formId" TEXT NOT NULL,
"data" JSONB NOT NULL,
CONSTRAINT "Pipeline_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SubmissionSession" (
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"formId" TEXT NOT NULL,
"userFingerprint" TEXT NOT NULL,
CONSTRAINT "SubmissionSession_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SessionEvent" (
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"submissionSessionId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"data" JSONB NOT NULL,
CONSTRAINT "SessionEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "users" (
"id" SERIAL NOT NULL,
"name" TEXT,
"email" TEXT,
"email_verified" TIMESTAMP(3),
"password" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "verification_requests" (
"id" SERIAL NOT NULL,
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "verification_requests_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "NoCodeForm_formId_key" ON "NoCodeForm"("formId");
-- CreateIndex
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
-- CreateIndex
CREATE UNIQUE INDEX "verification_requests_token_key" ON "verification_requests"("token");
-- AddForeignKey
ALTER TABLE "Form" ADD CONSTRAINT "Form_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "NoCodeForm" ADD CONSTRAINT "NoCodeForm_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Pipeline" ADD CONSTRAINT "Pipeline_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SubmissionSession" ADD CONSTRAINT "SubmissionSession_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SessionEvent" ADD CONSTRAINT "SessionEvent_submissionSessionId_fkey" FOREIGN KEY ("submissionSessionId") REFERENCES "SubmissionSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-1
View File
@@ -25,7 +25,6 @@ model Form {
formType FormType @default(NOCODE)
name String
published Boolean @default(false)
finishedOnboarding Boolean @default(false)
schema Json
submissionSessions SubmissionSession[]
pipelines Pipeline[]
+9 -5
View File
@@ -9,7 +9,7 @@ async function main() {
const passwordHash = await hash(process.env.ADMIN_PASSWORD, 12);
if (typeof passwordHash === "string") {
const userData: Prisma.UserCreateInput[] = [
const users: Prisma.UserCreateInput[] = [
{
name: "Admin",
email: process.env.ADMIN_EMAIL,
@@ -17,11 +17,15 @@ async function main() {
},
];
for (const u of userData) {
const user = await prisma.user.create({
data: u,
for (const user of users) {
const userRes = await prisma.user.upsert({
where: {
email: user.email,
},
update: {},
create: user,
});
console.log(`Created user with id: ${user.id}`);
console.log(`Created user with id: ${userRes.id}`);
}
console.log(`Seeding finished.`);
}