Skip to content

Commit

Permalink
Merge pull request #405 from RBND-studio/dev
Browse files Browse the repository at this point in the history
Merge dev
  • Loading branch information
VojtechVidra committed Jun 19, 2024
2 parents f7b197e + e802bc1 commit 6850a13
Show file tree
Hide file tree
Showing 24 changed files with 429 additions and 247 deletions.
8 changes: 4 additions & 4 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@radix-ui/react-slot": "^1.0.2",
"@rbnd/react-dark-mode": "^2.0.1",
"@supabase/ssr": "^0.3.0",
"@supabase/supabase-js": "^2.43.4",
"@supabase/supabase-js": "^2.43.5",
"@visx/axis": "^3.10.1",
"@visx/event": "^3.3.0",
"@visx/group": "^3.3.0",
Expand All @@ -35,10 +35,10 @@
"icons": "workspace:*",
"monaco-editor": "^0.49.0",
"next": "14.2.3",
"posthog-js": "^1.137.0",
"posthog-js": "^1.139.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"react-hook-form": "^7.52.0",
"shared": "workspace:*",
"sharp": "^0.33.4",
"swr": "^2.2.5",
Expand All @@ -48,7 +48,7 @@
"devDependencies": {
"@next/eslint-plugin-next": "14.2.3",
"@pandacss/dev": "0.39.2",
"@types/node": "^20.12.12",
"@types/node": "^20.14.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"eslint-config-custom": "workspace:*",
Expand Down
25 changes: 25 additions & 0 deletions apps/app/src/app/(dashboard)/accept-invite/[inviteId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DashboardError } from "components/ui/dashboard-error";
import { api } from "lib/api";
import { load } from "lib/load";
import { redirect } from "next/navigation";
import { routes } from "routes";

type Props = {
params: { inviteId: string };
};

export default async function AcceptInvitePage({
params: { inviteId },
}: Props): Promise<JSX.Element> {
try {
const res = await load(api["POST /invites/:inviteId/accept"](inviteId));
redirect(routes.organization({ organizationId: res.organization_id }));
} catch (err) {
return (
<DashboardError
title="Error accepting invite"
errorMessage={err instanceof Error ? err.message : "Something went wrong"}
/>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useSend } from "hooks/use-send";
import { api } from "lib/api";
import { useRouter } from "next/navigation";
import type { FC } from "react";
import { t } from "translations";
import { Button, toast } from "ui";
Expand All @@ -12,6 +13,7 @@ type Props = {
};

export const InviteResend: FC<Props> = ({ email, organizationId }) => {
const router = useRouter();
const { send, loading } = useSend();
const handleResend = async (): Promise<void> => {
const res = await send(
Expand All @@ -20,6 +22,7 @@ export const InviteResend: FC<Props> = ({ email, organizationId }) => {
);
if (res.error) return;
toast.success(t.toasts.inviteSent);
router.refresh();
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,21 @@ export const OrganizationMembers: FC<Props> = ({ users, org }) => {
</Text>
{/* TODO: @opesicka make this nicer */}
<Flex direction="column" gap="space12" mb="space16">
{pending_invites.map((invite) => (
<Flex alignItems="center" gap="space8" key={invite.id}>
<Text>{invite.email}</Text>
<Text>Expires {timeFromNow(invite.expires_at)}</Text>
<InviteResend email={invite.email} organizationId={org.id} />
<InviteDelete inviteId={invite.id} />
</Flex>
))}
{pending_invites.map((invite) => {
const expired = new Date(invite.expires_at) < new Date();
return (
<Flex alignItems="center" gap="space8" key={invite.id}>
<Text>{invite.email}</Text>
{expired ? (
<Text color="danger">Expired</Text>
) : (
<Text>Expires {timeFromNow(invite.expires_at)}</Text>
)}
<InviteResend email={invite.email} organizationId={org.id} />
<InviteDelete inviteId={invite.id} />
</Flex>
);
})}
</Flex>
</>
) : null}
Expand Down
26 changes: 26 additions & 0 deletions apps/app/src/components/ui/dashboard-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Flex } from "@flows/styled-system/jsx";
import Link from "next/link";
import { type FC } from "react";
import { routes } from "routes";
import { Button, Text } from "ui";

type Props = {
title: string;
errorMessage: string;
};

export const DashboardError: FC<Props> = ({ errorMessage, title }) => {
return (
<Flex align="center" justifyContent="center" direction="column" height="100vh">
<Text mb="space8" variant="titleL">
{title}
</Text>
<Text color="muted" mb="space24">
{errorMessage}
</Text>
<Button asChild>
<Link href={routes.home}>Go back</Link>
</Button>
</Flex>
);
};
2 changes: 1 addition & 1 deletion apps/app/src/components/ui/page-error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const PageError: FC<Props> = ({ error, reset, title }) => {
});

return (
<Flex align="center" direction="column">
<Flex align="center" justifyContent="center" direction="column" height="100vh">
<Text className={css({ mb: "space16" })} variant="titleL">
{title}
</Text>
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@types/cors": "^2.8.17",
"@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.12.12",
"@types/node": "^20.14.5",
"@types/supertest": "^6.0.2",
"concurrently": "^8.2.2",
"dotenv": "^16.4.5",
Expand Down
8 changes: 1 addition & 7 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,7 @@ import { ProjectsModule } from "./projects/projects.module";
import { SdkModule } from "./sdk/sdk.module";
import { UsersModule } from "./users/users.module";

const publicRoutes: string[] = [
"/sdk/flows",
"/sdk/events",
"/sdk/events/:eventId",
"/sdk/flows/:flowId",
"/sdk/flows/:flowId/draft",
];
const publicRoutes: string[] = ["/v(.)/sdk/(.*)", "/sdk/(.*)"];

const isTest = process.env.NODE_ENV === "test";

Expand Down
4 changes: 3 additions & 1 deletion apps/backend/src/email/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ export class EmailService {

async sendInvite({
organizationName,
inviteId,
email,
}: {
inviteId: string;
organizationName: string;
email: string;
}): Promise<ReturnType<LoopsClient["sendTransactionalEmail"]>> {
return this.loops.sendTransactionalEmail("clpxmw7h70012jo0pp0pe0hb5", email, {
orgName: organizationName,
acceptUrl: process.env.BACKEND_APP_URL,
acceptUrl: `${process.env.BACKEND_APP_URL}/accept-invite/${inviteId}`,
});
}

Expand Down
10 changes: 7 additions & 3 deletions apps/backend/src/lib/origin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export const isLocalhost = (origin: string): boolean =>
["http://localhost", "https://localhost", "http://127.0.0.1", "https://127.0.0.1"].some((o) =>
origin.startsWith(o),
export const isLocalhost = (origin: string): boolean => {
const url = new URL(origin);
const originWithoutPort = `${url.protocol}//${url.hostname}`;

return ["http://localhost", "https://localhost", "http://127.0.0.1", "https://127.0.0.1"].some(
(o) => o === originWithoutPort,
);
};
7 changes: 7 additions & 0 deletions apps/backend/src/lib/too-many-requests-exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { HttpException, HttpStatus } from "@nestjs/common";

export class TooManyRequestsException extends HttpException {
constructor(message?: string) {
super(message ?? "Too Many Requests", HttpStatus.TOO_MANY_REQUESTS);
}
}
5 changes: 4 additions & 1 deletion apps/backend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ValidationPipe } from "@nestjs/common";
import { ValidationPipe, VersioningType } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import type { NestFastifyApplication } from "@nestjs/platform-fastify";
import { FastifyAdapter } from "@nestjs/platform-fastify";
Expand All @@ -11,6 +11,9 @@ async function bootstrap(): Promise<void> {
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter(), {
rawBody: true,
});
app.enableVersioning({
type: VersioningType.URI,
});

app.useGlobalPipes(
new ValidationPipe({
Expand Down
6 changes: 3 additions & 3 deletions apps/backend/src/metadata.ts

Large diffs are not rendered by default.

11 changes: 1 addition & 10 deletions apps/backend/src/organizations/organizations.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,16 +240,6 @@ describe("Invite user", () => {
organizationsController.inviteUser({ userId: "userId" }, "org1", { email: "email" }),
).rejects.toThrow("User already in organization");
});
it("should throw with user already invited", async () => {
db.query.userInvite.findFirst.mockResolvedValue({ id: "inviteId" });
await expect(
organizationsController.inviteUser({ userId: "userId" }, "org1", { email: "email" }),
).resolves.toBeUndefined();
expect(emailService.sendInvite).toHaveBeenCalledWith({
email: "email",
organizationName: "org",
});
});
it("should create invite and send email", async () => {
await expect(
organizationsController.inviteUser({ userId: "userId" }, "org1", { email: "email" }),
Expand All @@ -258,6 +248,7 @@ describe("Invite user", () => {
expect(emailService.sendInvite).toHaveBeenCalledWith({
email: "email",
organizationName: "org",
inviteId: "inviteId",
});
});
});
Expand Down
39 changes: 21 additions & 18 deletions apps/backend/src/organizations/organizations.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
subscriptions,
userInvite,
} from "db";
import { and, desc, eq, exists, gt, inArray, sql } from "drizzle-orm";
import { and, desc, eq, exists, inArray, sql } from "drizzle-orm";
import { alias } from "drizzle-orm/pg-core";

import type { Auth } from "../auth";
Expand Down Expand Up @@ -271,23 +271,29 @@ export class OrganizationsService {
);
if (userAlreadyInOrg) throw new ConflictException("User already in organization");

const existingInvite = await this.databaseService.db.query.userInvite.findFirst({
where: and(
eq(userInvite.organization_id, organizationId),
eq(userInvite.email, email),
gt(userInvite.expires_at, sql`now()`),
),
let invite = await this.databaseService.db.query.userInvite.findFirst({
columns: { id: true },
where: and(eq(userInvite.organization_id, organizationId), eq(userInvite.email, email)),
});

if (!existingInvite) {
await this.databaseService.db.insert(userInvite).values({
email,
organization_id: organizationId,
expires_at: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 7 days
});
const expires_at = new Date(Date.now() + 1000 * 60 * 60 * 24 * 7); // 7 days

if (invite) {
await this.databaseService.db
.update(userInvite)
.set({ expires_at })
.where(eq(userInvite.id, invite.id));
} else {
const newInvites = await this.databaseService.db
.insert(userInvite)
.values({ email, organization_id: organizationId, expires_at })
.returning({ id: userInvite.id });
const newInvite = newInvites.at(0);
if (!newInvite) throw new InternalServerErrorException();
invite = newInvite;
}

await this.emailService.sendInvite({ email, organizationName: org.name });
await this.emailService.sendInvite({ email, organizationName: org.name, inviteId: invite.id });
}

async leaveOrganization({
Expand Down Expand Up @@ -377,10 +383,7 @@ export class OrganizationsService {
columns: {},
}),
this.databaseService.db.query.userInvite.findMany({
where: and(
eq(userInvite.organization_id, organizationId),
gt(userInvite.expires_at, sql`now()`),
),
where: eq(userInvite.organization_id, organizationId),
columns: {
email: true,
id: true,
Expand Down
Loading

0 comments on commit 6850a13

Please sign in to comment.