diff --git a/client/public/codezilla_bkgd.png b/client/background/codezilla_bkgd.png similarity index 100% rename from client/public/codezilla_bkgd.png rename to client/background/codezilla_bkgd.png diff --git a/client/src/components/AnswerResultModal.tsx b/client/src/components/AnswerResultModal.tsx index e69de29..4bb7a2c 100644 --- a/client/src/components/AnswerResultModal.tsx +++ b/client/src/components/AnswerResultModal.tsx @@ -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 ( + { if (!open) onClose(); }}> + + + + {isCorrect ? "Correct!" : "Wrong!"} + + +

{drDanQuote}

+
+
+ ); +}; + +export default AnswerResultModal; diff --git a/client/src/components/MinionModal.tsx b/client/src/components/MinionModal.tsx index e69de29..b575d2c 100644 --- a/client/src/components/MinionModal.tsx +++ b/client/src/components/MinionModal.tsx @@ -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 ( + { if (!open) onClose(); }}> + + + {minionName} Appears! + +

{minionQuote}

+
+
+ ); +}; + +export default MinionModal; diff --git a/client/src/graphql/queries.ts b/client/src/graphql/queries.ts index 4b76e20..fec6426 100644 --- a/client/src/graphql/queries.ts +++ b/client/src/graphql/queries.ts @@ -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 } } `; + diff --git a/package.json b/package.json index 7507d76..3c7e013 100644 --- a/package.json +++ b/package.json @@ -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\"", diff --git a/server/package.json b/server/package.json index ac9f345..b555474 100644 --- a/server/package.json +++ b/server/package.json @@ -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": "", diff --git a/server/src/schemas/resolvers.ts b/server/src/schemas/resolvers.ts index 54998fa..70a165d 100644 --- a/server/src/schemas/resolvers.ts +++ b/server/src/schemas/resolvers.ts @@ -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: { @@ -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(); }, @@ -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); diff --git a/server/src/schemas/typeDefs.ts b/server/src/schemas/typeDefs.ts index a9b876e..be51e79 100644 --- a/server/src/schemas/typeDefs.ts +++ b/server/src/schemas/typeDefs.ts @@ -16,6 +16,8 @@ const typeDefs = gql` password: String! } +<<<<<<< HEAD +======= type Character { _id: ID! name: String! @@ -23,18 +25,29 @@ const typeDefs = gql` 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 { diff --git a/server/src/server.ts b/server/src/server.ts index e527958..4504434 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -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'; @@ -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')); }); } diff --git a/server/src/types/express/index.d.ts b/server/src/types/express/index.d.ts new file mode 100644 index 0000000..e649f26 --- /dev/null +++ b/server/src/types/express/index.d.ts @@ -0,0 +1,8 @@ +declare namespace Express { + interface Request { + user: { + _id: unknown; + username: string; + }; + } +} diff --git a/server/src/utils/PromptBuilder.ts b/server/src/utils/PromptBuilder.ts index deaa247..14d919b 100644 --- a/server/src/utils/PromptBuilder.ts +++ b/server/src/utils/PromptBuilder.ts @@ -1,6 +1,10 @@ +<<<<<<< HEAD +import { FallbackQuestion, fallbackQuestion } from "../utils/fallbackQuestions"; +======= import { FallbackQuestion, fallbackQuestion } from "../utils/fallbackQuestions"; +>>>>>>> d7567a5c20d729e2d5c004a2d70be9176db8ea33 export class PromptBuilder { /** diff --git a/server/src/utils/auth.ts b/server/src/utils/auth.ts index 01ef3ac..e0efdd3 100644 --- a/server/src/utils/auth.ts +++ b/server/src/utils/auth.ts @@ -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]; @@ -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) => { diff --git a/server/tsconfig.json b/server/tsconfig.json index a082141..e3f0e0c 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -13,6 +13,6 @@ "@/*": ["src/*"] // Map the '@' alias to the 'src' directory } }, - "include": ["src/**/*"], + "include": ["src/**/*", "src/server.ts"], "exclude": ["node_modules"] } \ No newline at end of file