Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New era #717

Merged
merged 4 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/gql/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
NODE_ENV=development
DATABASE_URL=mysql://kampus:kampus@localhost:3306/kampus?schema=public

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
CLERK_WEBHOOK_SECRET=
102 changes: 102 additions & 0 deletions apps/gql/app/api/webhooks/clerk/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { headers } from "next/headers";
import { type WebhookEvent } from "@clerk/nextjs/server";
import { Webhook } from "svix";

import { createClients } from "~/clients";
import { env } from "~/env";

export const dynamic = "force-dynamic";

const { prisma: db } = createClients();

export async function POST(req: Request) {
// Get the headers
const headerPayload = headers();
const svix_id = headerPayload.get("svix-id");
const svix_timestamp = headerPayload.get("svix-timestamp");
const svix_signature = headerPayload.get("svix-signature");

// If there are no headers, error out
if (!svix_id || !svix_timestamp || !svix_signature) {
return new Response("Error occured -- no svix headers", {
status: 400,
});
}

// Get the body
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload = await req.json();
const body = JSON.stringify(payload);

// Create a new SVIX instance with your secret.
const wh = new Webhook(env.CLERK_WEBHOOK_SECRET);

let evt: WebhookEvent;

// Verify the payload with the headers
try {
evt = wh.verify(body, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
}) as WebhookEvent;
} catch (err) {
console.error("Error verifying webhook:", err);
return new Response("Error occured", {
status: 400,
});
}

if (evt) {
// 👉 Parse the incoming event body into a ClerkWebhook object
try {
// 👉 `webhook.type` is a string value that describes what kind of event we need to handle

// 👉 If the type is "user.updated" the important values in the database will be updated in the users table
if (evt.type === "user.updated") {
const email = evt.data.email_addresses[0]?.email_address;
if (email) {
await db.user.update({
where: { id: evt.data.id },
data: {
username: evt.data.username || "",
name: `${evt.data.first_name ?? ""} ${evt.data.last_name ?? ""}`.trim() || null,
image: evt.data.image_url,
email,
},
});
}
}

// 👉 If the type is "user.created" create a record in the users table
if (evt.type === "user.created") {
const email = evt.data.email_addresses[0]?.email_address;
if (email) {
await db.user.create({
data: {
id: evt.data.id,
username: evt.data.username || "",
name: `${evt.data.first_name ?? ""} ${evt.data.last_name ?? ""}`.trim() || null,
image: evt.data.image_url,
email,
},
});
}
}

// 👉 If the type is "user.deleted", delete the user record and associated blocks
if (evt.type === "user.deleted") {
await db.user.delete({
where: { id: evt.data.id },
});
}

return new Response("", { status: 201 });
} catch (err) {
console.error(err);
return new Response("Error occured -- processing webhook data", {
status: 500,
});
}
}
}
10 changes: 8 additions & 2 deletions apps/gql/app/graphql/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { currentUser } from "@clerk/nextjs";
import { createSchema, createYoga } from "graphql-yoga";

import { createActions } from "~/actions";
Expand All @@ -11,19 +12,24 @@ import { type KampusGQLContext } from "~/schema/types";
const typeDefs = readFileSync(join(process.cwd(), "schema/schema.graphql"), "utf8").toString();
const clients = createClients();

async function getUserSession() {
const user = await currentUser();
return user ? { user: { id: user.id } } : null;
}

const { handleRequest } = createYoga<KampusGQLContext>({
schema: createSchema<KampusGQLContext>({ typeDefs, resolvers }),
logging: "debug",
graphiql: true,
context: () => {
context: async () => {
const loaders = createLoaders(clients);
const actions = createActions(clients);

return {
loaders,
actions,
pasaport: {
session: null,
session: await getUserSession(),
},
} satisfies KampusGQLContext;
},
Expand Down
5 changes: 5 additions & 0 deletions apps/gql/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export const env = parseEnv(
NODE_ENV: process.env.NODE_ENV,
DATABASE_URL: process.env.DATABASE_URL,
KAMPUS_ENV: process.env.KAMPUS_ENV,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
CLERK_WEBHOOK_SECRET: process.env.CLERK_WEBHOOK_SECRET,
},
{
NODE_ENV: z.enum(["development", "test", "production"]),
Expand All @@ -14,5 +17,7 @@ export const env = parseEnv(
.url()
.default("mysql://kampus:kampus@localhost:3306/kampus?schema=public&connect_timeout=300"),
KAMPUS_ENV: z.enum(["development", "test", "production"]).default("development"),
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().default("clerk-pub-key"),
CLERK_WEBHOOK_SECRET: z.string().default("clerk-webhook-secret"),
}
);
37 changes: 0 additions & 37 deletions apps/gql/fly.toml

This file was deleted.

10 changes: 10 additions & 0 deletions apps/gql/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { authMiddleware } from "@clerk/nextjs";

export default authMiddleware({
// "/" will be accessible to all users
publicRoutes: ["/", "/graphql", "/api/webhooks(.*)"],
});

export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|graphql)(.*)"],
};
2 changes: 2 additions & 0 deletions apps/gql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"vitest-mock-extended": "1.1.3"
},
"dependencies": {
"@clerk/nextjs": "4.27.5",
"@graphql-tools/schema": "9.0.19",
"@kampus/gql-utils": "*",
"@kampus/prisma": "*",
Expand All @@ -40,6 +41,7 @@
"graphql-scalars": "1.21.3",
"graphql-yoga": "3.9.1",
"object-hash": "3.0.0",
"svix": "1.15.0",
"znv": "0.3.2",
"zod": "3.21.4"
}
Expand Down
2 changes: 1 addition & 1 deletion apps/gql/schema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export interface KampusGQLContext {
loaders: DataLoaders;
actions: DataActions;
pasaport: {
session: null | { id: string; user: { id: string } };
session: null | { user: { id: string } };
};
}
3 changes: 3 additions & 0 deletions apps/kampus/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ NEXT_PUBLIC_GQL_URL=http://gql.localhost.kamp.us:4000/graphql
NEXT_PUBLIC_KAMPUS_ENV=localhost

RESEND_API_KEY="get-your-own-resend-api-key"

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
16 changes: 16 additions & 0 deletions apps/kampus/app/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import { type PropsWithChildren } from "react";
import { Theme, ThemePanel } from "@radix-ui/themes";

import "@radix-ui/themes/styles.css";

import { ThemeProvider as KampusThemeProvider } from "@kampus/ui";

export function ThemeProvider(props: PropsWithChildren) {
return (
<KampusThemeProvider attribute="class" defaultTheme="system" enableSystem>
<Theme>{props.children}</Theme>
</KampusThemeProvider>
);
}
21 changes: 11 additions & 10 deletions apps/kampus/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@ import { RelayEnvironmentProvider } from "~/features/relay/RelayEnvironmentProvi

import "./globals.css";

import { ThemeProvider } from "@kampus/ui";
import { cn } from "~/../../packages/ui/utils";
import { trTR } from "@clerk/localizations";
import { ClerkProvider } from "@clerk/nextjs";

import { ThemeProvider } from "./ThemeProvider";

const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });

export const metadata = {
title: "kamp.us",
description: "topluluk",
description: "dijital enstitü",
};

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className={cn(
"min-h-screen bg-background font-sans antialiased",
inter.variable
)}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<RelayEnvironmentProvider>{children}</RelayEnvironmentProvider>
<html lang="tr">
<body className={inter.variable}>
<ThemeProvider>
<ClerkProvider localization={trTR}>
<RelayEnvironmentProvider>{children}</RelayEnvironmentProvider>
</ClerkProvider>
<Toaster />
</ThemeProvider>
</body>
Expand Down
5 changes: 2 additions & 3 deletions apps/kampus/app/odin/mufredat/[[...lesson]]/OdinLesson.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import { graphql, usePreloadedQuery } from "react-relay";

import { type SerializablePreloadedQuery } from "@kampus/relay";
import useSerializablePreloadedQuery from "@kampus/relay/use-serializable-preloaded-query";

import { type SerializablePreloadedQuery } from "~/features/relay";
import useSerializablePreloadedQuery from "~/features/relay/use-serializable-preloaded-query";
import { type OdinLessonQuery } from "~/app/odin/mufredat/[[...lesson]]/__generated__/OdinLessonQuery.graphql";
import { OdinLessonActions } from "./OdinLessonActions";
import { OdinLessonBody } from "./OdinLessonBody";
Expand Down
3 changes: 1 addition & 2 deletions apps/kampus/app/odin/mufredat/[[...lesson]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import loadSerializableQuery from "@kampus/relay/load-serializable-query";

import loadSerializableQuery from "~/features/relay/load-serializable-query";
import OdinLessonQueryNode, {
type OdinLessonQuery,
} from "~/app/odin/mufredat/[[...lesson]]/__generated__/OdinLessonQuery.graphql";
Expand Down
19 changes: 12 additions & 7 deletions apps/kampus/app/pano/@modal/(.)post/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
"use client";

import { useRouter } from "next/navigation";

import { Dialog, DialogContent } from "@kampus/ui";
import { Dialog, Theme } from "@radix-ui/themes";

import { CreatePanoPostForm } from "~/app/pano/CreatePanoPostForm";

export default function CreatePost({ searchParams }: { searchParams: { conn: string } }) {
const router = useRouter();

return (
<Dialog open onOpenChange={() => router.back()}>
<DialogContent className="sm:max-w-[425px]">
<CreatePanoPostForm connectionID={searchParams.conn} onCompleted={() => router.back()} />
</DialogContent>
</Dialog>
<Theme accentColor="amber">
<Dialog.Root open onOpenChange={() => router.back()}>
<Dialog.Content className="sm:max-w-[425px]">
<Dialog.Title>Yeni Pano Girdisi</Dialog.Title>
<Dialog.Description size="2" mb="4">
pano&apos;yu zenginleştir :)
</Dialog.Description>
<CreatePanoPostForm connectionID={searchParams.conn} onCompleted={() => router.back()} />
</Dialog.Content>
</Dialog.Root>
</Theme>
);
}
Loading
Loading