Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added client/public/balck.sabbath.mp3
Binary file not shown.
Binary file added client/public/godzilla.roar.mp3
Binary file not shown.
Binary file added client/public/metallica.first30.mp3
Binary file not shown.
3,071 changes: 1,645 additions & 1,426 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 12 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "node server/server.js",
"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\"",
"render-build": "npm install && npm run build",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "concurrently \"npm --prefix server run dev\" \"npm --prefix client run dev\""
},
Expand All @@ -20,18 +25,19 @@
},
"homepage": "https://github.com/OutsideofemiT/Codezilla#readme",
"dependencies": {
"@radix-ui/react-dialog": "^1.1.11",
"apollo-server-express": "^3.13.0",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"lucide-react": "^0.503.0",
"express": "^4.17.1",
"openai": "^4.95.1",
"react": "^19.1.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.4",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.1",
"@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.9",
"@types/mongoose": "^5.11.96",
"@types/node": "^22.14.1",
"concurrently": "^9.1.2",
"nodemon": "^3.1.10",
Expand Down
Empty file removed server/models/User.ts
Empty file.
1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon index.ts"
},
Expand Down
File renamed without changes.
File renamed without changes.
55 changes: 55 additions & 0 deletions server/src/models/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Schema, model, Document } from 'mongoose';
import bcrypt from 'bcrypt';

// Define an interface for the User document
interface IUser extends Document {
username: string;
email: string;
password: string;
isCorrectPassword(password: string): Promise<boolean>;
}

// Define the schema for the User document
const userSchema = new Schema<IUser>(
{
username: {
type: String,
required: true,
unique: true,
trim: true,
},
email: {
type: String,
required: true,
unique: true,
match: [/.+@.+\..+/, 'Must match an email address!'],
},
password: {
type: String,
required: true,
minlength: 5,
},
},
{
timestamps: true,
toJSON: { getters: true },
toObject: { getters: true },
}
);

userSchema.pre<IUser>('save', async function (next) {
if (this.isNew || this.isModified('password')) {
const saltRounds = 10;
this.password = await bcrypt.hash(this.password, saltRounds);
}

next();
});

userSchema.methods.isCorrectPassword = async function (password: string): Promise<boolean> {
return bcrypt.compare(password, this.password);
};

const User = model<IUser>('User', userSchema);

export default User;
4 changes: 4 additions & 0 deletions server/src/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import typeDefs from './typeDefs.js';
import resolvers from './resolvers.js';

export { typeDefs, resolvers };
Empty file removed server/src/schemas/resolvers
Empty file.
52 changes: 52 additions & 0 deletions server/src/schemas/resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import User from '../models/User';
import { signToken } from '../utils/auth';
import { AuthenticationError } from 'apollo-server-express';

interface AddUserArgs {
input: {
username: string;
email: string;
password: string;
};
}

interface LoginUserArgs {
email: string;
password: string;
}

const resolvers = {
Query: {
me: async (_parent: any, _args: any, context: any) => {
if (context.user) {
return User.findOne({ _id: context.user._id });
}
throw new AuthenticationError('You need to be logged in!');
},
},
Mutation: {
addUser: async (_parent: any, { input }: AddUserArgs) => {
const user = await User.create(input) as { username: string; email: string; _id: string };
const token = signToken(user.username, user.email, user._id);
return { token, user };
},
login: async (_parent: any, { email, password }: LoginUserArgs) => {
const user = await User.findOne({ email }) as { username: string; email: string; _id: string; isCorrectPassword: (password: string) => Promise<boolean> };

if (!user) {
throw new AuthenticationError('Invalid credentials');
}

const isPasswordCorrect = await user.isCorrectPassword(password);

if (!isPasswordCorrect) {
throw new AuthenticationError('Invalid credentials');
}

const token = signToken(user.username, user.email, user._id);
return { token, user };
},
},
};

export default resolvers;
Empty file removed server/src/schemas/typeDefs
Empty file.
32 changes: 32 additions & 0 deletions server/src/schemas/typeDefs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const typeDefs = `
type User {
_id: ID
username: String
email: String
password: String
}

input UserInput {
username: String!
email: String!
password: String!
}

type Auth {
token: ID!
user: User
}

type Query {
users: [User]
user(username: String!): User
me: User
}

type Mutation {
addUser(input: UserInput!): Auth
login(email: String!, password: String!): Auth
}
`;

export default typeDefs;
89 changes: 53 additions & 36 deletions server/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,68 @@
import express from 'express';
import { Request, Response } from 'express';

import express, { Request, Response } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { OpenAI } from 'openai';
import fs from 'fs';
import path from 'path';
// import fs from 'fs';
// import path from 'path';
import { ApolloServer } from 'apollo-server-express';
import { expressMiddleware } from '@apollo/server/express4';
import typeDefs from './schemas/typeDefs';
import resolvers from './schemas/resolvers';
import { authenticateToken } from './utils/auth';

dotenv.config();

const app = express();
const PORT = process.env.PORT || 3001;
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });

// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('public')); // serve generated mp3 files
const startServer = async () => {
const app = express();
const PORT = process.env.PORT || 3001;
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });

// TTS Route for Dr. Dan
app.post('/api/tts', async (req: Request, res: Response) => {
const { text } = req.body;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('public')); // serve generated mp3 files
app.use(authenticateToken);

try {
const speech = await openai.audio.speech.create({
model: 'tts-1',
voice: 'onyx',
input: text
// Apollo Server setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }: { req: Request }) => ({ user: req.user }),
});

const buffer = Buffer.from(await speech.arrayBuffer());
res.set({ "Content-Type": "audio/mpeg"})
res.send(buffer);

} catch (err) {
console.error('❌ TTS error:', err);
res.status(500).send('TTS failed');
}
});
await server.start();
server.applyMiddleware({ app, path: '/graphql' });

// TTS Route for Dr. Dan
app.post('/api/tts', async (req: Request, res: Response) => {
const { text } = req.body;

try {
const speech = await openai.audio.speech.create({
model: 'tts-1',
voice: 'onyx',
input: text,
});

const buffer = Buffer.from(await speech.arrayBuffer());
res.set({ 'Content-Type': 'audio/mpeg' });
res.send(buffer);
} catch (err) {
console.error('❌ TTS error:', err);
res.status(500).send('TTS failed');
}
});

app.get('/', (req: Request, res: Response) => {
res.send('🎙️ Codezilla server is up!');
});
// Root route
app.get('/', (req: Request, res: Response) => {
res.send('🎙️ Codezilla server is up!');
});

// Start the server
app.listen(PORT, () => {
console.log(`✅ Server is running on http://localhost:${PORT}`);
console.log(`✅ GraphQL endpoint is available at http://localhost:${PORT}/graphql`);
});
};

app.listen(PORT, () => {
console.log(`✅ Server is running on port ${PORT}`);
});
startServer();
56 changes: 56 additions & 0 deletions server/src/utils/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Request, Response, NextFunction} from 'express';
import jwt from 'jsonwebtoken';

import dotenv from 'dotenv';
dotenv.config();

interface JwtPayload {
_id: unknown;
username: string;
email: string;
}


declare global {
namespace Express {
interface Request {
user?: JwtPayload;
}
}
}

export const authenticateToken = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization || '';

if (!authHeader) {
return res.status(401).json({ error: 'Authorization header is missing' });
}

const token = authHeader.split(' ')[1];
const secretKey = process.env.JWT_SECRET_KEY;

if (!secretKey) {
console.error('JWT_SECRET_KEY is not defined in the environment variables');
return res.status(500).json({ error: 'Internal server error' });
}

jwt.verify(token, secretKey, (err, user) => {
if (err) {
console.error('JWT verification failed:', err.message);
return res.status(403).json({ error: 'Invalid or expired token' });
}

req.user = user as JwtPayload;
next();
});
};
export const signToken = (username: string, email: string, _id: unknown) => {
const payload = { username, email, _id };
const secretKey = process.env.JWT_SECRET_KEY;

if (!secretKey) {
throw new Error('JWT_SECRET_KEY is not defined in the environment variables');
}

return jwt.sign(payload, secretKey, { expiresIn: '1h' });
};
Loading