chore: prepare 2.1 release (#2753)

This commit is contained in:
Matti Nannt
2024-06-11 17:18:27 +02:00
committed by GitHub
parent 864b3a34e9
commit 8c7b5891a2
19 changed files with 14944 additions and 12209 deletions

View File

@@ -87,6 +87,7 @@ EMAIL_VERIFICATION_DISABLED=1
PASSWORD_RESET_DISABLED=1
# Signup. Disable the ability for new users to create an account.
# Note: This variable is only available to the SaaS setup of Formbricks Cloud. Signup is disable by default for self-hosting.
# SIGNUP_DISABLED=1
# Email login. Disable the ability for users to login with email.

View File

@@ -28,7 +28,6 @@ These variables are present inside your machines docker-compose file. Restart
| PRIVACY_URL | URL for privacy policy. | optional | |
| TERMS_URL | URL for terms of service. | optional | |
| IMPRINT_URL | URL for imprint. | optional | |
| SIGNUP_DISABLED | Disables the ability for new users to create an account if set to 1. | optional | |
| EMAIL_AUTH_DISABLED | Disables the ability for users to signup or login via email and password if set to 1. | optional | |
| PASSWORD_RESET_DISABLED | Disables password reset functionality if set to 1. | optional | |
| EMAIL_VERIFICATION_DISABLED | Disables email verification if set to 1. | optional | |
@@ -176,10 +175,10 @@ An example configuration for a FusionAuth OpenID Connect in Formbricks would loo
<Col>
<CodeGroup title="Formbricks Env for FusionAuth OIDC">
```yml {{ title: ".env" }}
```yml {{ title: ".env" }}
OIDC_CLIENT_ID=59cada54-56d4-4aa8-a5e7-5823bbe0e5b7 OIDC_CLIENT_SECRET=4f4dwP0ZoOAqMW8fM9290A7uIS3E8Xg29xe1umhlB_s
OIDC_ISSUER=http://localhost:9011 OIDC_DISPLAY_NAME=FusionAuth OIDC_SIGNING_ALGORITHM=HS256
```
OIDC_ISSUER=http://localhost:9011 OIDC_DISPLAY_NAME=FusionAuth OIDC_SIGNING_ALGORITHM=HS256
```
</CodeGroup>
</Col>

View File

@@ -8,6 +8,112 @@ export const metadata = {
# Migration Guide
## v2.1
Formbricks v2.1 introduces more options for creating No-Code Actions and lays the foundation for easier self-hosting of Formbricks starting with an Onboarding for fresh instances.
<Note>
To improve the user experience in self-hosting instances and to simplify setup, we are moving to a single
organization approach for self-hosting instances with this release. This will allow self-hosters to
centrally manage their instance and more easily restrict access to the instance. We will soon introduce a
new permissions system that will allow more granular access to projects and other resources within an
organization. If you have created multiple organizations in the past, you will still be able to switch
between them in the UI, but don't have an option to create new organizations.
</Note>
### Steps to Migrate
This guide is for users who are self-hosting Formbricks using our one-click setup. If you are using a different setup, you might adjust the commands accordingly.
To run all these steps, please navigate to the `formbricks` folder where your `docker-compose.yml` file is located.
1. **Backup your Database**: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database:
<Col>
<CodeGroup title="Backup Postgres">
```bash
docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.1_$(date +%Y%m%d_%H%M%S).dump
```
</CodeGroup>
</Col>
<Note>
If you run into “No such container”, use `docker ps` to find your container name, e.g.
`formbricks_postgres_1`.
</Note>
<Note>
If you prefer storing the backup as an `*.sql` file remove the `-Fc` (custom format) option. In case of a
restore scenario you will need to use `psql` then with an empty `formbricks` database.
</Note>
2. Pull the latest version of Formbricks:
<Col>
<CodeGroup title="Stop the containers">
```bash
docker compose pull
```
</CodeGroup>
</Col>
3. Stop the running Formbricks instance & remove the related containers:
<Col>
<CodeGroup title="Stop the containers">
```bash
docker compose down
```
</CodeGroup>
</Col>
4. Restarting the containers with the latest version of Formbricks:
<Col>
<CodeGroup title="Restart the containers">
```bash
docker compose up -d
```
</CodeGroup>
</Col>
5. Now let's migrate the data to the latest schema:
<Note>To find your Docker Network name for your Postgres Database, find it using `docker network ls`</Note>
<Col>
<CodeGroup title="Migrate the data">
```bash
docker pull ghcr.io/formbricks/data-migrations:latest && \
docker run --rm \
--network=formbricks_default \
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
-e UPGRADE_TO_VERSION="v2.1" \
ghcr.io/formbricks/data-migrations:latest
```
</CodeGroup>
</Col>
The above command will migrate your data to the latest schema. This is a crucial step to migrate your existing data to the new structure. Only if the script runs successful, changes are made to the database. The script can safely run multiple times.
6. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
### Changes in Environment Variables
- `SIGNUP_DISABLED` is now deprecated since self-hosting instaces have signup disabled by default and new users can only be invited by the organization owner or admin.
- `DEFAULT_TEAM_ID` got renamed to `DEFAULT_ORGANIZATION_ID`
- `DEFAULT_TEAM_ROLE` got renamed to `DEFAULT_ORGANIZATION_ROLE`
## v2.0
Formbricks v2.0 comes with huge features such as Multi-Language Surveys and Advanced Styling for Surveys. We have also shipped various optimisations, bug fixes & smaller fixes on the way to make your experience more seamless. This guide will help you migrate your existing Formbricks instance to v2.0 without any hassles or build errors.

View File

@@ -3,7 +3,6 @@
import { formbricksLogout } from "@/app/lib/formbricks";
import { Session } from "next-auth";
import { useState } from "react";
import { ProfileAvatar } from "@formbricks/ui/Avatars";
import { Button } from "@formbricks/ui/Button";
import { DeleteAccountModal } from "@formbricks/ui/DeleteAccountModal";
@@ -39,7 +38,7 @@ export const DeleteAccount = ({
open={isModalOpen}
setOpen={setModalOpen}
session={session}
IS_FORMBRICKS_CLOUD={IS_FORMBRICKS_CLOUD}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
formbricksLogout={formbricksLogout}
/>
<p className="text-sm text-slate-700">

View File

@@ -2,7 +2,6 @@ import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { Testimonial } from "@/app/(auth)/auth/components/Testimonial";
import { SignupForm } from "@/app/(auth)/auth/signup/components/SignupForm";
import { notFound } from "next/navigation";
import { getIsMultiOrgEnabled } from "@formbricks/ee/lib/service";
import {
AZURE_OAUTH_ENABLED,
@@ -19,10 +18,11 @@ import {
WEBAPP_URL,
} from "@formbricks/lib/constants";
const Page = async () => {
const Page = async ({ searchParams }: { searchParams: { [key: string]: string | string[] | undefined } }) => {
const inviteToken = searchParams["inviteToken"] ?? null;
const isMultOrgEnabled = await getIsMultiOrgEnabled();
if (!SIGNUP_ENABLED || !isMultOrgEnabled) {
if (!inviteToken && (!SIGNUP_ENABLED || !isMultOrgEnabled)) {
notFound();
}

View File

@@ -1,7 +1,6 @@
import type { Session } from "next-auth";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { ONBOARDING_DISABLED } from "@formbricks/lib/constants";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/environment/service";
@@ -26,7 +25,8 @@ const Page = async () => {
}
const userOrganizations = await getOrganizationsByUserId(session.user.id);
if (!userOrganizations || userOrganizations.length === 0) {
if (userOrganizations.length === 0) {
return redirect("/setup/organization/create");
}

View File

@@ -2,7 +2,7 @@
import { Organization } from "@prisma/client";
import { getServerSession } from "next-auth";
import { getIsMultiOrgEnabled } from "@formbricks/ee/lib/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { gethasNoOrganizations } from "@formbricks/lib/instance/service";
import { createMembership } from "@formbricks/lib/membership/service";
@@ -16,7 +16,9 @@ export const createOrganizationAction = async (organizationName: string): Promis
if (!session) throw new AuthorizationError("Not authorized");
const hasNoOrganizations = await gethasNoOrganizations();
if (!hasNoOrganizations) {
const isMultiOrgEnabled = await getIsMultiOrgEnabled();
if (!hasNoOrganizations && !isMultiOrgEnabled) {
throw new OperationNotAllowedError("This action can only be performed on a fresh instance.");
}

View File

@@ -7,30 +7,29 @@ import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { z } from "zod";
import { ZOrganization } from "@formbricks/types/organizations";
import { Button } from "@formbricks/ui/Button";
import { FormControl, FormError, FormField, FormItem, FormProvider } from "@formbricks/ui/Form";
import { Input } from "@formbricks/ui/Input";
const ZCreateFirstOrganizationFormSchema = ZOrganization.pick({ name: true });
type TCreateFirstOrganizationForm = z.infer<typeof ZCreateFirstOrganizationFormSchema>;
const ZCreateOrganizationFormSchema = ZOrganization.pick({ name: true });
type TCreateOrganizationForm = z.infer<typeof ZCreateOrganizationFormSchema>;
export const CreateFirstOrganization = () => {
export const CreateOrganization = () => {
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const form = useForm<TCreateFirstOrganizationForm>({
const form = useForm<TCreateOrganizationForm>({
defaultValues: {
name: "",
},
mode: "onChange",
resolver: zodResolver(ZCreateFirstOrganizationFormSchema),
resolver: zodResolver(ZCreateOrganizationFormSchema),
});
const organizationName = form.watch("name");
const onSubmit: SubmitHandler<TCreateFirstOrganizationForm> = async (data) => {
const onSubmit: SubmitHandler<TCreateOrganizationForm> = async (data) => {
try {
setIsSubmitting(true);
const organizationName = data.name.trim();

View File

@@ -3,25 +3,24 @@
import { formbricksLogout } from "@/app/lib/formbricks";
import { Session } from "next-auth";
import React, { useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@formbricks/ui/Alert";
import { Button } from "@formbricks/ui/Button";
import { DeleteAccountModal } from "@formbricks/ui/DeleteAccountModal";
interface RemovedFromOrganizationProps {
session: Session;
IS_FORMBRICKS_CLOUD: boolean;
isFormbricksCloud: boolean;
}
export const RemovedFromOrganization = ({ session, IS_FORMBRICKS_CLOUD }: RemovedFromOrganizationProps) => {
export const RemovedFromOrganization = ({ session, isFormbricksCloud }: RemovedFromOrganizationProps) => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="space-y-4">
<Alert variant="warning">
<AlertTitle>No membership found!</AlertTitle>
<AlertDescription>
Unfortunately, you have been removed from the organization. If you believe this was a mistake,
please reach out to the organization owner.
You are not a member of any organization at this time. If you believe this is a mistake, please
reach out to the organization owner.
</AlertDescription>
</Alert>
<hr className="my-4 border-slate-200" />
@@ -32,7 +31,7 @@ export const RemovedFromOrganization = ({ session, IS_FORMBRICKS_CLOUD }: Remove
open={isModalOpen}
setOpen={setIsModalOpen}
session={session}
IS_FORMBRICKS_CLOUD={IS_FORMBRICKS_CLOUD}
isFormbricksCloud={isFormbricksCloud}
formbricksLogout={formbricksLogout}
/>
<Button variant="darkCTA" onClick={() => setIsModalOpen(true)}>

View File

@@ -2,15 +2,13 @@ import { RemovedFromOrganization } from "@/app/setup/organization/create/compone
import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { notFound } from "next/navigation";
import { getIsMultiOrgEnabled } from "@formbricks/ee/lib/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { gethasNoOrganizations } from "@formbricks/lib/instance/service";
import { getOrganizationsByUserId } from "@formbricks/lib/organization/service";
import { AuthenticationError } from "@formbricks/types/errors";
import { CreateFirstOrganization } from "./components/CreateFirstOrganiztion";
import { CreateOrganization } from "./components/CreateOrganization";
export const metadata: Metadata = {
title: "Create Organization",
@@ -26,15 +24,15 @@ const Page = async () => {
const isMultiOrgEnabled = await getIsMultiOrgEnabled();
const userOrganizations = await getOrganizationsByUserId(session.user.id);
if (hasNoOrganizations || isMultiOrgEnabled) {
return <CreateOrganization />;
}
if (!hasNoOrganizations && userOrganizations.length === 0 && !isMultiOrgEnabled) {
return <RemovedFromOrganization session={session} IS_FORMBRICKS_CLOUD={IS_FORMBRICKS_CLOUD} />;
return <RemovedFromOrganization session={session} isFormbricksCloud={IS_FORMBRICKS_CLOUD} />;
}
if (userOrganizations.length !== 0 || (!hasNoOrganizations && !isMultiOrgEnabled)) {
return notFound();
}
return <CreateFirstOrganization />;
return notFound();
};
export default Page;

View File

@@ -1,6 +1,6 @@
{
"name": "@formbricks/web",
"version": "2.0.3",
"version": "2.1.0",
"private": true,
"scripts": {
"clean": "rimraf .turbo node_modules .next",

View File

@@ -56,10 +56,6 @@ x-environment: &environment
# Set the below to 0 to enable Password Reset (will required Email Configuration)
PASSWORD_RESET_DISABLED: 1
# Set the below to 1 to disable signups. The first user will be created in the Onboarding process and all other users must be invited.
# Note: Signup cannot be enabled on self-hosting instances.
SIGNUP_DISABLED: 1
# Set the below to 1 to disable logins with email
# EMAIL_AUTH_DISABLED:

View File

@@ -204,7 +204,7 @@ echo "🔗 To edit more variables and deeper config, go to the formbricks/docker
echo "🚨 Make sure you have set up the DNS records as well as inbound rules for the domain name and IP address of this instance."
echo ""
echo "🎉 All done! Check the status of Formbricks & Traefik with 'cd formbricks && sudo docker compose ps.'"
echo "🎉 All done! Please setup your Formbricks instance by visiting your domain at https://$domain_name. You can check the status of Formbricks & Traefik with 'cd formbricks && sudo docker compose ps.'"
END

View File

@@ -33,7 +33,8 @@
"data-migration:refactor-actions": "ts-node ./data-migrations/20240501111944_refactors_actions_and_removes_inline_triggers/data-migration.ts",
"data-migration:mls-welcomeCard-fix": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration-welcomeCard-fix.ts",
"data-migration:v2.0": "pnpm data-migration:mls && pnpm data-migration:styling && pnpm data-migration:styling-fix && pnpm data-migration:website-surveys && pnpm data-migration:userId && pnpm data-migration:mls-welcomeCard-fix && pnpm data-migration:refactor-actions",
"data-migration:extended-noCodeActions": "ts-node ./data-migrations/20240524053239_extends_no_code_action_schema/data-migration.ts"
"data-migration:extended-noCodeActions": "ts-node ./data-migrations/20240524053239_extends_no_code_action_schema/data-migration.ts",
"data-migration:v2.1": "pnpm data-migration:extended-noCodeActions"
},
"dependencies": {
"@prisma/client": "^5.14.0",

File diff suppressed because one or more lines are too long

View File

@@ -4,9 +4,7 @@ import AzureAD from "next-auth/providers/azure-ad";
import CredentialsProvider from "next-auth/providers/credentials";
import GitHubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";
import { prisma } from "@formbricks/database";
import { createAccount } from "./account/service";
import { verifyPassword } from "./auth/utils";
import {
@@ -29,7 +27,6 @@ import {
import { verifyToken } from "./jwt";
import { createMembership } from "./membership/service";
import { createOrganization, getOrganization } from "./organization/service";
import { createProduct } from "./product/service";
import { createUser, getUserByEmail, updateUser } from "./user/service";
export const authOptions: NextAuthOptions = {
@@ -281,31 +278,7 @@ export const authOptions: NextAuthOptions = {
return true;
}
// Without default organization assignment
else {
const organization = await createOrganization({ name: userProfile.name + "'s Organization" });
await createMembership(organization.id, userProfile.id, { role: "owner", accepted: true });
await createAccount({
...account,
userId: userProfile.id,
});
const product = await createProduct(organization.id, { name: "My Product" });
const updatedNotificationSettings = {
...userProfile.notificationSettings,
alert: {
...userProfile.notificationSettings?.alert,
},
weeklySummary: {
...userProfile.notificationSettings?.weeklySummary,
[product.id]: true,
},
};
await updateUser(userProfile.id, {
notificationSettings: updatedNotificationSettings,
});
return true;
}
return true;
}
return true;

View File

@@ -36,7 +36,7 @@
"tailwind-merge": "^2.3.0"
},
"devDependencies": {
"@formbricks/config-typescript": "*",
"@formbricks/config-typescript": "workspace:*",
"@types/jsonwebtoken": "^9.0.6",
"@types/mime-types": "^2.1.4",
"@types/ungap__structured-clone": "^1.2.0",

View File

@@ -4,7 +4,6 @@ import { Session } from "next-auth";
import { signOut } from "next-auth/react";
import { Dispatch, SetStateAction, useState } from "react";
import toast from "react-hot-toast";
import { DeleteDialog } from "../DeleteDialog";
import { Input } from "../Input";
import { deleteUserAction } from "./actions";
@@ -13,7 +12,7 @@ interface DeleteAccountModalProps {
open: boolean;
setOpen: Dispatch<SetStateAction<boolean>>;
session: Session;
IS_FORMBRICKS_CLOUD: boolean;
isFormbricksCloud: boolean;
formbricksLogout: () => void;
}
@@ -21,7 +20,7 @@ export const DeleteAccountModal = ({
setOpen,
open,
session,
IS_FORMBRICKS_CLOUD,
isFormbricksCloud,
formbricksLogout,
}: DeleteAccountModalProps) => {
const [deleting, setDeleting] = useState(false);
@@ -36,7 +35,7 @@ export const DeleteAccountModal = ({
await deleteUserAction();
await formbricksLogout();
// redirect to account deletion survey in Formbricks Cloud
if (IS_FORMBRICKS_CLOUD) {
if (isFormbricksCloud) {
await signOut({ redirect: true });
window.location.replace("https://app.formbricks.com/s/clri52y3z8f221225wjdhsoo2");
} else {

26814
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff