Skip to content

Commit

Permalink
Updates to Next.js 14 and migrate the backend to Supabase and Drizzle…
Browse files Browse the repository at this point in the history
… ORM
  • Loading branch information
deepsingh132 committed Jun 18, 2024
1 parent 520c393 commit 6edbbc9
Show file tree
Hide file tree
Showing 82 changed files with 4,077 additions and 1,616 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ yarn-error.log*

# vercel
.vercel

# Supabase
.branches
.temp
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["denoland.vscode-deno"]
}
10 changes: 10 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"deno.enablePaths": [
"supabase/functions"
],
"deno.lint": true,
"deno.unstable": true,
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
}
10 changes: 10 additions & 0 deletions @types/Post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ type Post = {
createdAt?: string;
updatedAt?: string;
};

type CommentType = {
userId: string;
content: string;
username: string;
timestamp: string;
userImg: string;
url: string;
name: string;
};
19 changes: 19 additions & 0 deletions @types/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type User = {
_id: string;
name: string;
username: string;
email: string;
image: string | null;
bio: string | null;
followers: string[] | null;
following: string[] | null;
verified: boolean | null;
role: string | null;
likes: string[] | null;
createdAt: Date | null;
updatedAt: Date | null;

// For the frontend
id: string | null | undefined;
accessToken: string | null | undefined;
};
26 changes: 26 additions & 0 deletions @types/jsonb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* FIXME: Custom type for JSONB column (Due to a bug which causes JSONB to be inserted as a string from the database)
* NOTE: The below code is a HACK/Workaround, not to be used in production if the bug is fixed in the future
* TODO: Remove this file and use the JSONB type directly from drizzle-orm/pg-core
*/

import { customType } from 'drizzle-orm/pg-core';

const jsonb = customType<{ data: any }>({
dataType() {
return 'jsonb';
},
toDriver(val) {
return val as any;
},
fromDriver(value) {
if (typeof value === 'string') {
try {
return JSON.parse(value) as any;
} catch {}
}
return value as any;
},
});

export default jsonb;
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,28 @@ ArtSphere aims to bridge gaps in art and culture, creating an inclusive virtual

## Table of Contents 📜

- Tech Stack
- Features
- Getting Started
- Contributing
- License
- Contact

## Tech Stack 🛠️

ArtSphere is built with cutting-edge technologies to provide a seamless and engaging user experience:

- Next.js 14
- TypeScript
- Tailwind CSS
- Supabase
- PostgreSQL (Supabase)
- Drizzle-ORM
- NextAuth
- JsonWebToken (JWT)
- Recoil
- SWR (Data Fetching)

## Features 🌟

ArtSphere offers a plethora of features designed to inspire and empower artists:
Expand Down
7 changes: 7 additions & 0 deletions __tests__/Feed.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import { SWRConfig } from "swr";
import { server } from "@/__mocks__/server";
import { HttpResponse, http } from "msw";
import { backendUrl } from "@/app/utils/config/backendUrl";
import crypto from "crypto";

Object.defineProperty(global, "crypto", {
value: {
randomUUID: () => crypto.randomUUID(),
},
});

global.confirm = jest.fn(() => true); // mock window.confirm

Expand Down
71 changes: 2 additions & 69 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,6 @@
import { connectMongoDB } from "@/libs/mongodb";
import User from "@/models/User";
import { authOptions } from "@/lib/authOptions";
import NextAuth from "next-auth/next";
import GoogleProvider from "next-auth/providers/google";
import SignToken from "@/app/api/auth/jwt";
import { NextAuthOptions } from "next-auth";

export const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
maxAge: 7 * 24 * 60 * 60, // 7 days
},
jwt: {
maxAge: 7 * 24 * 60 * 60,
},

providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID || "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
}),
],
callbacks: {
async jwt({ token, user, account }) {
if (account?.provider === "google") {
try {
await connectMongoDB();
const userExists = await User.findOne({ email: user.email });

if (!userExists) {
// Create the user document and add it to the token
const newUser = await User.create({
name: user.name,
username: user.email?.split("@")[0] || user.name,
image: user?.image || "",
email: user.email,
});
token._id = newUser._id; // Include the MongoDB ObjectId in the token
const data = {
email: newUser.email,
_id: newUser._id,
};
const tokenString = await SignToken(data);
token.accessToken = tokenString;
} else {
const data = {
email: userExists.email,
_id: userExists._id,
};
const tokenString = await SignToken(data);
token.accessToken = tokenString;
token._id = userExists._id;
}
} catch (error) {
console.error(error);
}
}

return token;
},

async session({ session, token }) {
session.user.id = token._id as string;
session.user.accessToken = token.accessToken as string;
session.user.expires = token.exp as number;
return session;
},
},
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
export { handler as GET, handler as POST };
5 changes: 3 additions & 2 deletions app/api/auth/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { UUID } from "crypto";
import jwt from "jsonwebtoken";

const SignToken = async (data: { email: any; _id: any }) => {
const SignToken = async (data: { email: string; _id: string }) => {
const token = await jwt.sign(
{
email: data?.email,
_id: data?._id,
},
process.env.JWT_SECRET,
{ expiresIn: "10d" }
);
) as string;
return token;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { connectMongoDB } from "@/libs/mongodb";
import Events from "@/models/Events";
import { db } from "@/db";
import { eq, sql } from "drizzle-orm";
import { events as Event } from "@/models/Events";
import { NextResponse } from "next/server";
import User from "@/models/User";
import mongoose from "mongoose";
import { users as User } from "@/models/User";
import crypto from "crypto";

// Public route
// Get event by id
export async function GET(request: Request) {
const slug = request.url.split("/")[3];
await connectMongoDB();
const event = await Events.findOne({ slug });
const [event] = await db.select().from(Event).where(eq(Event._id, slug));
if (!event) {
return NextResponse.json({ message: "No event found" }, { status: 404 });
}
return NextResponse.json({ event }, { status: 200 });
}

function generateToken(length) {
function generateToken(length: number) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let token = "";
Expand All @@ -29,15 +28,17 @@ function generateToken(length) {
}

export async function POST(request: Request) {
await connectMongoDB();
const token = request.headers.get("Authorization");
if (!token) {
const username = request.url.split("/")[5];
const userId = request.headers.get("userId");

if (!userId) {
return NextResponse.json({ message: "Not Authorized!" }, { status: 401 });
}

const user = await User.findOne({ username: request.url.split("/")[5] });
if (!user) {
return NextResponse.json({ message: "No user found" }, { status: 404 });
const [user] = await db.select().from(User).where(eq(User.username, username));

if (!user || user._id !== userId) {
return NextResponse.json({ message: "Unauthorized" }, { status: 404 });
}

if (user.role !== "organizer") {
Expand All @@ -59,9 +60,10 @@ export async function POST(request: Request) {
location,
coordinates,
attendees,
link
} = data;

const organizerId = new mongoose.Types.ObjectId(userId);
const organizerId = user._id;

if (!title || !date || !location || !userId) {
return NextResponse.json(
Expand All @@ -70,7 +72,15 @@ export async function POST(request: Request) {
);
}

const eventExists = await Events.findOne({ title });
if (userId !== user._id) {
return NextResponse.json(
{ message: "User not authorized to create event" },
{ status: 401 }
);
}

// Check if event already exists by title
const [eventExists] = await db.select().from(Event).where(eq(Event.title, title));
if (eventExists) {
return NextResponse.json(
{ message: "Event already exists" },
Expand All @@ -79,17 +89,26 @@ export async function POST(request: Request) {
}

const token = generateToken(10);
const event = await Events.create({

// remove quotes from coordinates
if (coordinates) {
coordinates.lat = parseFloat(coordinates?.lat) || 0;
coordinates.lng = parseFloat(coordinates?.lng) || 0;
}

const [event] = await db.insert(Event).values({
title,
description,
image,
date,
location,
coordinates,
token,
link,
coordinates: coordinates || { lat: 0, lng: 0 },
organizer: organizerId,
attendees,
token,
});
}).returning();

return NextResponse.json({ event }, { status: 200 });
} catch (error) {
console.error("Error creating event: ", error);
Expand Down
Loading

0 comments on commit 6edbbc9

Please sign in to comment.