Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
yuzakki committed Feb 14, 2024
0 parents commit 7cc5b4d
Show file tree
Hide file tree
Showing 142 changed files with 14,860 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Next Project: Social Media Platform

# Tech:

- Mern Stack (React, NodeJS, Express, MongoDB)
- Build with NEXTJS latest Version [Typescript]

# Tools & Libs:

- zod
- react hook form

# Features:

- Login / Sign Up ✅
- User Profile ✅
- Manage User Profile (create and customize profile, include pictures, cover photo, bio section) ✅
- News Feed (display latest posts from users in home page) ✅
- Post Creation (enable user to create post with: text, images, links..) ✅
- Show Suggested Users (show suggested users to follow) ✅
- Responsive Design ✅

- Dark / Light Mode
- Followers System (follow / unfollow / following list on user profile)
- Notifications (notify users about new followers, likes, comments)
- Real-Time Updates (implement real-time updates for notifications, news feed and messages using techs like webSocket)
- Messaging System (create private messaging system for users to send direct messages to their followers)
- Search Functionality (implement a search feature to find users)

# Styling:

- TailwindCSS
- Shadcn
21 changes: 21 additions & 0 deletions actions/account/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use server';

import { revalidatePath } from 'next/cache';
import { connectToDb } from '@/lib/db';
import { UserModel } from '@/models/userModel';
import { currentUser } from '@/actions/auth/current-user';

export async function updateMyAccount(data: any) {
connectToDb();

const { user } = await currentUser();
if (!user) return;

await UserModel.findOneAndUpdate({ _id: user?._id }, data, {
upsert: true,
});

revalidatePath('/profile');
revalidatePath('/profile/info');
return { success: 'Your info have been updated!' };
}
46 changes: 46 additions & 0 deletions actions/auth/current-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use server';

import { cookies } from 'next/headers';
import jwt from 'jsonwebtoken';

import { connectToDb } from '@/lib/db';
import { IUser, UserModel } from '@/models/userModel';

export async function currentUser() {
connectToDb();

const token = cookies().get('jwt')?.value;

if (token) {
try {
// 1) Verify token
const decoded: any = jwt.verify(
token!,
process.env.JWT_SECRET! as string
);

// 2) Check if user still exists
const currentUser = await UserModel.findById(decoded.id);
if (!currentUser) {
return {
error: 'The user belonging to this token does no longer exist.',
};
}

const plainObject: IUser | null = JSON.parse(JSON.stringify(currentUser));

// THERE IS A LOGGED IN USER
return { user: plainObject };
} catch (error: any) {
if (error instanceof jwt.TokenExpiredError) {
return { error: 'Your token has expired. Please log in again.' };
}

if (error instanceof jwt.JsonWebTokenError) {
return { error: 'Token verification failed, Please log in again!' };
}
}
}

return { error: 'You are not logged in! Please log in to get access' };
}
45 changes: 45 additions & 0 deletions actions/auth/forgot-password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use server';

import { z } from 'zod';

import { ForgotPasswordSchema } from '@/schemas/auth';
import { connectToDb } from '@/lib/db';
import { UserModel } from '@/models/userModel';
import { sendPasswordResetEmail } from '@/lib/email-sender';

export async function forgotPassword({
email,
}: z.infer<typeof ForgotPasswordSchema>) {
connectToDb();

// 1) Get user based on POSTed email
const user = await UserModel.findOne({ email });
if (!user) {
return { error: 'There is no user with provided email.' };
}

// 2) Generate the random reset token
const resetToken = user.createPasswordResetToken();
await user.save({ validateBeforeSave: false });

// 3) Send it to user's email
try {
const resetURL = `${process.env.CLIENT_DOMAIN}/auth/reset-password/${resetToken}`;

await sendPasswordResetEmail({
email: user?.email,
name: user?.firstName || user?.username,
resetURL,
});

return { success: 'Reset password sent!, Check your email' };
} catch (error) {
console.log(error);

user.passwordResetToken = undefined;
user.passwordResetExpires = undefined;
await user.save({ validateBeforeSave: false });

return { error: 'There was an error sending the email. Try again later!' };
}
}
54 changes: 54 additions & 0 deletions actions/auth/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use server';

import { z } from 'zod';
import { cookies } from 'next/headers';
import { revalidatePath } from 'next/cache';
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';
import { redirect } from 'next/navigation';

import { connectToDb } from '@/lib/db';
import { LoginSchema } from '@/schemas';
import { UserModel } from '@/models/userModel';
import { signToken } from '@/lib/utils';

const JWT_COOKIE_EXPIRES_IN: number = Number(
process.env.JWT_COOKIE_EXPIRES_IN!
);

export async function login({ email, password }: z.infer<typeof LoginSchema>) {
connectToDb();

// 1) Validate input
if (!email || !password) {
return { error: 'Please provide email and password' };
}

// 2) Creating a new user in the database
const user = await UserModel.findOne({ email }).select('+password');

// 3) Check if the user exists and validate password
if (!user || !(await user.correctPassword(password, user.password))) {
return { error: 'Incorrect email or password' };
}

// 4) Send token to client
const token = signToken(user._id);

let cookieOptions: ResponseCookie = {
name: 'jwt',
value: token,
expires: new Date(Date.now() + JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000),
httpOnly: true,
secure: false,
};

if (process.env.NODE_ENV === 'production') {
cookieOptions.secure = true;
}

cookies().set('jwt', token, cookieOptions);

revalidatePath('/');

redirect('/');
}
9 changes: 9 additions & 0 deletions actions/auth/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use server';

import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

export async function logout() {
cookies().delete('jwt');
redirect('/auth/login');
}
57 changes: 57 additions & 0 deletions actions/auth/reset-password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use server';

import crypto from 'crypto';

import { UserModel } from '@/models/userModel';
import { signToken } from '@/lib/utils';
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

const JWT_COOKIE_EXPIRES_IN: number = Number(
process.env.JWT_COOKIE_EXPIRES_IN!
);

interface Props {
token: string;
newPassword: string;
}

export async function resetPassword({ token, newPassword }: Props) {
// 1) Get user based on the token
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');

const user = await UserModel.findOne({
passwordResetToken: hashedToken,
passwordResetExpires: { $gt: Date.now() },
});

// 2) If token has not expired, and there is user, set the new password
if (!user) {
return { error: 'Token is invalid or has expired' };
}

// 3) Update passwordChangedAt property for the user
user.password = newPassword;
user.passwordResetToken = undefined;
user.passwordResetExpires = undefined;
await user.save();

// 4) Log the user in, send JWT
const newToken = signToken(user._id);

let cookieOptions: ResponseCookie = {
name: 'jwt',
value: newToken,
expires: new Date(Date.now() + JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000),
httpOnly: true,
secure: false,
};

if (process.env.NODE_ENV === 'production') {
cookieOptions.secure = true;
}

cookies().set('jwt', newToken, cookieOptions);
redirect('/');
}
65 changes: 65 additions & 0 deletions actions/auth/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use server';

import { z } from 'zod';
import { cookies } from 'next/headers';
import { revalidatePath } from 'next/cache';
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';
import { redirect } from 'next/navigation';

import { connectToDb } from '@/lib/db';
import { SignUpSchema } from '@/schemas';
import { UserModel } from '@/models/userModel';
import { signToken } from '@/lib/utils';

const JWT_COOKIE_EXPIRES_IN: number = Number(
process.env.JWT_COOKIE_EXPIRES_IN!
);

export async function signup({
email,
password,
username,
}: z.infer<typeof SignUpSchema>) {
connectToDb();

// 1) Validate input
if (!email || !password || !username)
return { error: 'Please provide email and password' };

// 2) Check if the email already exists in the database
const existingEmail = await UserModel.findOne({ email });
if (existingEmail) return { error: 'Email already exists' };

if (username) {
const existingUsername = await UserModel.findOne({ username });
if (existingUsername) return { error: 'Username already exists' };
}

// 3) Creating a new user in the database
const newUser = await UserModel.create({
email,
password,
username,
});

// 4) Send token to client
const token = signToken(newUser._id);

let cookieOptions: ResponseCookie = {
name: 'jwt',
value: token,
expires: new Date(Date.now() + JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000),
httpOnly: true,
secure: false,
};

if (process.env.NODE_ENV === 'production') {
cookieOptions.secure = true;
}

cookies().set('jwt', token, cookieOptions);

revalidatePath('/');

redirect('/');
}
Loading

0 comments on commit 7cc5b4d

Please sign in to comment.