Skip to content
25 changes: 25 additions & 0 deletions client/src/components/AnswerResultModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";

interface AnswerResultModalProps {
isOpen: boolean;
onClose: () => void;
isCorrect: boolean;
drDanQuote: string;
}

const AnswerResultModal = ({ isOpen, onClose, isCorrect, drDanQuote }: AnswerResultModalProps) => {
return (
<Dialog open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }}>
<DialogContent>
<DialogHeader>
<DialogTitle className={isCorrect ? 'text-green-500' : 'text-red-500'}>
{isCorrect ? "Correct!" : "Wrong!"}
</DialogTitle>
</DialogHeader>
<p>{drDanQuote}</p>
</DialogContent>
</Dialog>
);
};

export default AnswerResultModal;
23 changes: 23 additions & 0 deletions client/src/components/MinionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";

interface MinionModalProps {
isOpen: boolean;
onClose: () => void;
minionName: string;
minionQuote: string;
}

const MinionModal = ({ isOpen, onClose, minionName, minionQuote }: MinionModalProps) => {
return (
<Dialog open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }}>
<DialogContent>
<DialogHeader>
<DialogTitle>{minionName} Appears!</DialogTitle>
</DialogHeader>
<p>{minionQuote}</p>
</DialogContent>
</Dialog>
);
};

export default MinionModal;
12 changes: 6 additions & 6 deletions client/src/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { gql } from '@apollo/client';

export const GET_QUESTIONS = gql`
query GetQuestions {
export const GENERATE_QUESTIONS = gql`
query GenerateQuestion($track: String!, level: $level, minion: $minion) {
questions {
_id
questionText
options
correctAnswer
question
choices
answer
}
}
`;


2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "node server/server.js",
"start": "cd server && npm run start",
"develop": "concurrently \"cd server && npm run watch\" \"cd client && npm run dev\"",
"install": "cd server && npm i && cd ../client && npm i",
"build": "concurrently \"cd server && npm run build\" \"cd client && npm run build\"",
Expand Down
3 changes: 2 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"start": "node dist/server.js",
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon index.ts"
"dev": "nodemon index.ts",
"watch": "nodemon dist/server.js"
},
"keywords": [],
"author": "",
Expand Down
38 changes: 38 additions & 0 deletions server/src/schemas/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import User from '../models/User';
import Character from '../models/Characters';
import { signToken } from '../utils/auth';
import { AuthenticationError } from 'apollo-server-express';
import { OpenAI } from 'openai';
import { PromptBuilder } from '../utils/PromptBuilder';
import dotenv from 'dotenv';

dotenv.config();

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

interface AddUserArgs {
input: {
Expand All @@ -24,6 +31,35 @@ const resolvers = {
}
throw new AuthenticationError('You need to be logged in!');
},
<<<<<<< HEAD
generateQuestion: async (_parent: any, args: { track: string; level: string; minion: string }) => {
const { track, level, minion } = args;
const prompt = PromptBuilder.getPrompt(track, level);

try {
const completion = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
max_tokens: 250,
});

const raw = completion.choices[0].message.content ?? '';
const [question, ...choicesAndAnswer] = raw.split('\n').filter(Boolean);
const choices = choicesAndAnswer.slice(0, -1);
const answer = choicesAndAnswer[choicesAndAnswer.length - 1];

return { question, choices, answer };
} catch (error) {
console.error('OpenAI failed, falling back to hardcoded question:', error);
const fallback = PromptBuilder.getFallbackQuestion(minion);

return {
question: fallback.question,
choices: fallback.choices,
answer: fallback.choices[fallback.correctIndex],
};
}
=======
users: async () => {
return await User.find();
},
Expand All @@ -35,8 +71,10 @@ const resolvers = {
},
character: async (_: any, { id }: { id: string }) => {
return await Character.findById(id);
>>>>>>> d7567a5c20d729e2d5c004a2d70be9176db8ea33
},
},

Mutation: {
addUser: async (_parent: any, { input }: AddUserArgs) => {
const user = await User.create(input);
Expand Down
13 changes: 13 additions & 0 deletions server/src/schemas/typeDefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,38 @@ const typeDefs = gql`
password: String!
}

<<<<<<< HEAD
=======
type Character {
_id: ID!
name: String!
picture: String!
voice: String!
}

>>>>>>> d7567a5c20d729e2d5c004a2d70be9176db8ea33
type Auth {
token: ID!
user: User
}

type Question {
question: String!
choices: [String!]!
answer: String!
}

type Query {
users: [User]
user(username: String!): User
me: User
<<<<<<< HEAD
generateQuestion(track: String!, level: String!, minion: String!): Question
=======
updateStats(isCorrect: Boolean!): User
characters: [Character]
character(id: ID!): Character
>>>>>>> d7567a5c20d729e2d5c004a2d70be9176db8ea33
}

type Mutation {
Expand Down
23 changes: 13 additions & 10 deletions server/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import express, { Request, Response } from 'express';
import cors from 'cors';
import express, { type Request, type Response } from 'express';

import dotenv from 'dotenv';
import { OpenAI } from 'openai';
// import fs from 'fs';
// import path from 'path';
import { ApolloServer } from 'apollo-server-express';

import path from 'path';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import typeDefs from './schemas/typeDefs';
import resolvers from './schemas/resolvers';
Expand Down Expand Up @@ -33,22 +33,25 @@ dotenv.config();
resolvers,
});

await server.start();
const startApolloServer = async () => {
await server.start();
await db;

app.use(express.static('public')); // serve generated mp3 file
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(express.static('public')); // serve generated mp3 file

app.use('/graphql', expressMiddleware(server as any,
{
context: authenticateToken as any
}
));

// if we're in production, serve client/build as static assets
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(dirname, '../client/build')));
app.use(express.static(path.join(__dirname, '../client/build')));

app.get('*', (_req: Request, res: Response) => {
res.sendFile(path.join(dirname, '../client/dist/index.html'));
res.sendFile(path.join(__dirname, '../client/dist/index.html'));
});
}

Expand Down
8 changes: 8 additions & 0 deletions server/src/types/express/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare namespace Express {
interface Request {
user: {
_id: unknown;
username: string;
};
}
}
4 changes: 4 additions & 0 deletions server/src/utils/PromptBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<<<<<<< HEAD
import { FallbackQuestion, fallbackQuestion } from "../utils/fallbackQuestions";
=======

import { FallbackQuestion, fallbackQuestion } from "../utils/fallbackQuestions";

>>>>>>> d7567a5c20d729e2d5c004a2d70be9176db8ea33

export class PromptBuilder {
/**
Expand Down
29 changes: 17 additions & 12 deletions server/src/utils/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface JwtPayload {
}

export const authMiddleware = ({ req }: { req: any }) => {
const authHeader = req.headers.authorization;
const authHeader = req.headers.get('authorization');

if (authHeader) {
const token = authHeader.split(' ')[1];
Expand Down Expand Up @@ -42,20 +42,25 @@ export const authMiddleware = ({ req }: { req: any }) => {
return { user: null };
};

export const authenticateToken = (token: string) => {
const secretKey = process.env.JWT_SECRET_KEY || '';
export const authenticateToken = async ({ req }: { req: Request }) => {
const authHeader = req.headers.authorization;
let user = null;
console.log('AUTH HEADER', authHeader);

if (!token) {
throw new Error('No token provided');
}
if (authHeader) {
const token = authHeader.split(' ')[1];
console.log('TOKEN', token);
const secretKey = process.env.JWT_SECRET_KEY || '';

try {
const user = jwt.verify(token, secretKey) as JwtPayload;
return user;
} catch (err) {
console.error('Invalid token:', err);
throw new Error('Invalid or expired token');
try {
user = jwt.verify(token, secretKey) as JwtPayload;
console.log('USER', user);
} catch (err) {
console.error(err);
}
}

return { user };
};

export const signToken = (username: string, email: string, _id: unknown) => {
Expand Down
2 changes: 1 addition & 1 deletion server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"@/*": ["src/*"] // Map the '@' alias to the 'src' directory
}
},
"include": ["src/**/*"],
"include": ["src/**/*", "src/server.ts"],
"exclude": ["node_modules"]
}
Loading