Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
anujd64 committed Feb 24, 2024
0 parents commit 3733d36
Show file tree
Hide file tree
Showing 69 changed files with 10,116 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"
}
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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
# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
bun.lockb
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

# GatePYQ 📚💻

[Live Link](https://gate-pyq-web.vercel.app)

GatePYQ is a dynamic web application designed to help in preparing for competitive exam Graduate Aptitude Test in Engineering (GATE). It provides a centralized platform for accessing and practicing previous year questions.

## Features 🚀

- 📝 Access to extensive collections of previous year questions for GATE exams.
- 📚 Detailed explanations provided for each question and its correct answer, powered by Google's Gemini Pro Generative AI.
- 🔒 Secure authentication using NextAuth with Google and Credentials Providers.
- 🌐 Built with Next.js 14, MongoDB, Prisma, and React components for a seamless user experience.
- 💪 Written in Typescript.
- 〽️ MongoDB data api as an alternative data source.

## Screenshots 📸

<img src="./Screenshots/Homepage.png" style="width: 75%;margin:16px;" />&nbsp;&nbsp;
<img src="./Screenshots/Questions - Signed In - Gemini Response.png" style="width: 75%;margin:16px;" />&nbsp;&nbsp;
<img src="./Screenshots/Sign In Page.png" style="width: 75%;margin:16px;" />&nbsp;&nbsp;
<img src="./Screenshots/Profile Page.png" style="width: 75%;margin:16px;" />&nbsp;&nbsp;
<img src="./Screenshots/404.png" style="width: 75%;margin:16px;" />&nbsp;&nbsp;

## Installation 🛠️

To run GatePYQ locally, follow these steps:

1. Clone the repository:
```
git clone https://github.com/anujd64/GatePYQ.git
```

2. Navigate to the project directory:
```
cd GatePYQ
```

3. Install dependencies:
```
npm install
```

4. Start the development server:
```
npm run dev
```

5. Access GatePYQ in your browser at `http://localhost:3000`.
Binary file added Screenshots/404.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshots/Homepage Signed in.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshots/Homepage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshots/Profile Page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshots/Sign In Page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 90 additions & 0 deletions app/_lib/authOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import GoogleProvider from 'next-auth/providers/google';
import CredentialsProvider from 'next-auth/providers/credentials'
import { AuthOptions } from "next-auth";
export const authOptions: AuthOptions = {
// adapter: PrismaAdapter(PrismaClient),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
}),

CredentialsProvider({
type: "credentials",
credentials: {
email: {
label: "Email",
type: "email",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials) {

const credentialDetails = {
email: credentials?.email,
password: credentials?.password,
};

const resp = await fetch(process.env.NEXTAUTH_URL+"/api/auth/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(credentialDetails),
});
const user = await resp.json();
console.log("user request ",credentials?.email,credentials?.password,"user response", user)
if (resp.ok) {
console.log("nextauth user: " + user.user);

return user.user;
}
if(!resp.ok){
console.log("check your credentials");
throw new Error(user.message)
}
return null;
},
}),
],


callbacks: {
jwt: async ({ token, user }) => {

if (user) {
token.email = user.email ;
token.username = user.name;
token.name = user.name;
token.image = user.image;
}


// if (user) {
// const { user: userData } = user;

// console.log("jwt callback",user, userData)
// token.email = userData?.email ?? user.email;
// token.username = userData?.name ?? user.name;
// token.name = userData?.name ?? user.name;
// token.image = userData?.image ?? user.image;
// // token.accessToken = userData?.token; // Uncomment if needed
// }
return token;
},
session: ({ session, token }) => {
if (token) {
session.user!.email = token.email;
session.user!.name = token.name;
session.user!.image = token.image as string;
// session.user.accessToken = token.accessToken;
}
return session;
},
},

pages: {
signIn: '/profile/login',
},
};
3 changes: 3 additions & 0 deletions app/_lib/fonts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Outfit } from 'next/font/google';

export const outfit = Outfit({ subsets: ['latin'] });
40 changes: 40 additions & 0 deletions app/_lib/mongodb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { MongoClient } from 'mongodb';

// Replace with your MongoDB connection string
const uri = process.env.MONGODB_URI as string;
const options = {};

declare global {
var _mongoClientPromise: Promise<MongoClient>;
}

class Singleton {
private static _instance: Singleton;
private client: MongoClient;
private clientPromise: Promise<MongoClient>;

private constructor() {
this.client = new MongoClient(uri, options);
this.clientPromise = this.client.connect();

if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable to preserve the value
// across module reloads caused by HMR (Hot Module Replacement).
global._mongoClientPromise = this.clientPromise;
}
}

public static get instance() {
if (!this._instance) {
this._instance = new Singleton();
}
return this._instance.clientPromise;
}
}

const clientPromise = Singleton.instance;

// Export a module-scoped MongoClient promise.
// By doing this in a separate module,
// the client can be shared across functions.
export default clientPromise;
94 changes: 94 additions & 0 deletions app/_lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import { findUser, createUser } from "../_server/actions/UserAction";
import { IUser } from "../_server/UserService/IUserService";
import { json } from "stream/consumers";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

//https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
/* Randomize array in-place using Durstenfeld shuffle algorithm */

export function shuffleArray(array: string[]) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}


// export async function getTags() {
// const res = await fetch('http://localhost:2020/tags')
// // The return value is *not* serialized
// // You can return Date, Map, Set, etc.

// if (!res.ok) {
// throw new Error('Failed to fetch data')
// }

// return res.json()
// }


export function validateRegisterForm(values: {email:string,password:string,passwordC:string, username:string}) {
const errors: any = {};

if (!values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Invalid email address";
}

if (values.password !== values.passwordC) {
const errMsg = "Passwords don't match";
errors.password = errMsg;
errors.passwordC = errMsg;
}

return errors;
}

export async function onRegisterSubmit(values:{email:string,password:string, passwordC:string,username:string}) {

const userObj = (values: { email: string; password: string; username?: string; })=> {
return {
email:values.email,
password:values.password,
username:values.username
}
}
const data = findUser({email: values.email})
let user = await data.then(json => json.data);
if(user === null){
user = await createUser(userObj(values)).then(json => json.data)
}
return user;
}


export function validateLoginForm(values: {email:string,password:string}) {
const errors: any = {};

if (!values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Invalid email address";
}

if (values.password == "") {
const errMsg = "Password is too short";
errors.password = errMsg;
}

return errors;
}
8 changes: 8 additions & 0 deletions app/_server/IRepositoryService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface IRepository<T> {
find(
filter: Partial<T>,
page: number,
limit: number,
projection?: Partial<Record<keyof T, 1 | 0>>,
): Promise<{ data: T[], totalCount: number }>;
}
16 changes: 16 additions & 0 deletions app/_server/QuestionService/IQuestionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type IQuestion = {
_id:string,
question: string,
options: string[],
correct_options:string[],
image_links:string[],
explanation_link:string,
tags: string[] | any,
};


export interface IQuestionService {
getQuestions(
filter: Partial<IQuestion>
): Promise<{ data: IQuestion[]; totalCount: number }>;
}
20 changes: 20 additions & 0 deletions app/_server/QuestionService/QuestionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Repository } from "@/app/_server/RepositoryService";
import { IQuestion, IQuestionService } from "./IQuestionService";

export class QuestionService implements IQuestionService {
private repository: Repository<IQuestion>;

constructor() {
this.repository = new Repository<IQuestion>("questions");
}

async getQuestions(
filter: Partial<IQuestion>,
page: number = 1,
limit: number = 10
): Promise<{ data: IQuestion[], totalCount: number }> {


return this.repository.find(filter, page, limit);
}
}
Loading

0 comments on commit 3733d36

Please sign in to comment.