This commit is contained in:
jackbeeby
2025-03-31 16:13:56 +11:00
parent d8773925e8
commit 0b9d543d36
22 changed files with 3203 additions and 32 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules
# Keep environment variables out of version control
.env
+35
View File
@@ -5,4 +5,39 @@ Installed apollo server and graphql npm install apollo-server@^2 graphql@^14.6.0
Can Run the project using node src/index.js Can Run the project using node src/index.js
Installed typescript to the project npm install typescript --save-dev Installed typescript to the project npm install typescript --save-dev
Installed typescripts error runtime npm install ts-node typescript --save-dev
Running typescript compiler npx tsc Running typescript compiler npx tsc
Restart server on save npm install -g nodemon
Run nodemon src/index.js instead of node src/index.js
INSTALL PRISMA
npm install prisma --save-dev
npm install @prisma/client
INSTALL WEBTOKENS JTK
npm install jsonwebtoken bcryptjs
Initialise prisma npx prisma init
Run the prisma migrate to update the database connections
npx prisma migrate dev
Generate prisma client
npx prisma generate
MIGRATE
npx prisma migrate dev
query {
link {
id
url
description
}
}
+895 -1
View File
File diff suppressed because it is too large Load Diff
+1310 -2
View File
File diff suppressed because it is too large Load Diff
+13 -2
View File
@@ -1,8 +1,9 @@
{ {
"name": "jobapp", "name": "jobapp",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.ts",
"scripts": { "scripts": {
"start": "nodemon --exec ts-node src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"keywords": [], "keywords": [],
@@ -11,10 +12,20 @@
"description": "A job platform to make hiring faster and easier", "description": "A job platform to make hiring faster and easier",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@prisma/client": "^6.5.0",
"apollo-server": "^2.26.2", "apollo-server": "^2.26.2",
"graphql": "^14.7.0" "bcryptjs": "^3.0.2",
"graphql": "^14.7.0",
"jsonwebtoken": "^9.0.2"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.20",
"prisma": "^6.5.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.2" "typescript": "^5.8.2"
},
"repository": {
"type": "git",
"url": "https://git.jackbeeby.au/jackbeeby/Job_App.git"
} }
} }
BIN
View File
Binary file not shown.
@@ -0,0 +1,44 @@
-- CreateTable
CREATE TABLE "Account" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"type" BIGINT,
"firstName" BIGINT,
"lastName" BIGINT,
"email" BIGINT,
"phone" BIGINT,
"company" BIGINT,
"password" BIGINT
);
-- CreateTable
CREATE TABLE "JobPost" (
"id" BIGINT NOT NULL PRIMARY KEY,
"businessId" INTEGER,
"heading" BIGINT,
"description" BIGINT,
"locationText" BIGINT,
"locationLongLat" BIGINT,
"field" BIGINT,
"contractType" BIGINT,
"payRange" BIGINT,
"hours" BIGINT,
"createdAt" BIGINT,
"endingAtTime" BIGINT
);
-- CreateTable
CREATE TABLE "AccountInformation" (
"id" BIGINT NOT NULL PRIMARY KEY,
"sex" BIGINT,
"age" BIGINT,
"suburb" BIGINT,
"postcode" BIGINT,
"state" BIGINT,
"searchPostcode" BIGINT,
"searchRadius" BIGINT,
"accountId" INTEGER NOT NULL,
CONSTRAINT "AccountInformation_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "AccountInformation_accountId_key" ON "AccountInformation"("accountId");
@@ -0,0 +1,43 @@
/*
Warnings:
- You are about to alter the column `phone` on the `Account` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`.
- The primary key for the `AccountInformation` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to alter the column `age` on the `AccountInformation` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`.
- You are about to alter the column `id` on the `AccountInformation` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Account" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"type" TEXT,
"firstName" TEXT,
"lastName" TEXT,
"email" TEXT,
"phone" INTEGER,
"company" TEXT,
"password" TEXT
);
INSERT INTO "new_Account" ("company", "email", "firstName", "id", "lastName", "password", "phone", "type") SELECT "company", "email", "firstName", "id", "lastName", "password", "phone", "type" FROM "Account";
DROP TABLE "Account";
ALTER TABLE "new_Account" RENAME TO "Account";
CREATE TABLE "new_AccountInformation" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"sex" TEXT,
"age" INTEGER,
"suburb" BIGINT,
"postcode" BIGINT,
"state" BIGINT,
"searchPostcode" BIGINT,
"searchRadius" BIGINT,
"accountId" INTEGER NOT NULL,
CONSTRAINT "AccountInformation_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_AccountInformation" ("accountId", "age", "id", "postcode", "searchPostcode", "searchRadius", "sex", "state", "suburb") SELECT "accountId", "age", "id", "postcode", "searchPostcode", "searchRadius", "sex", "state", "suburb" FROM "AccountInformation";
DROP TABLE "AccountInformation";
ALTER TABLE "new_AccountInformation" RENAME TO "AccountInformation";
CREATE UNIQUE INDEX "AccountInformation_accountId_key" ON "AccountInformation"("accountId");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
@@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[email]` on the table `Account` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "Account_email_key" ON "Account"("email");
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"
+55
View File
@@ -0,0 +1,55 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Account {
id Int @id @default(autoincrement())
type String?
firstName String?
lastName String?
email String? @unique
phone Int?
company String?
password String?
accountInformation AccountInformation?
}
model JobPost {
id BigInt @id @default(autoincrement())
businessId Int?
heading BigInt?
description BigInt?
locationText BigInt?
locationLongLat BigInt?
field BigInt?
contractType BigInt?
payRange BigInt?
hours BigInt?
createdAt BigInt?
endingAtTime BigInt?
}
model AccountInformation {
id Int @id @default(autoincrement())
sex String?
age Int?
suburb BigInt?
postcode BigInt?
state BigInt?
searchPostcode BigInt?
searchRadius BigInt?
accountId Int @unique // Add the unique constraint here
account Account @relation(fields: [accountId], references: [id])
}
+179
View File
@@ -0,0 +1,179 @@
import { ApolloServer } from 'apollo-server';
import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
import * as path from 'path';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
// Get __dirname equivalent in ES Modules
const __dirname = path.dirname(new URL(import.meta.url).pathname);
// Load schema.graphql
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.graphql'), 'utf8');
// Initialize Prisma
const prisma = new PrismaClient();
const JWT_SECRET = 'your-jwt-secret'; // You should store this in environment variables
// Generate JWT token
function generateToken(user) {
return jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '1h' });
}
// GraphQL Resolvers
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
feed: async (parent, args, context) => {
// Check if the user is authenticated
if (!context.user) {
throw new Error('Not authenticated');
}
// If authenticated, return the feed
return context.prisma.link.findMany();
},
},
Mutation: {
signup: async (parent, args, context) => {
const { email, password, name } = args;
// Check if the user already exists
const existingUser = await context.prisma.User.findUnique({
where: { email },
});
if (existingUser) {
throw new Error('User already exists');
}
// Hash the password before storing it
const hashedPassword = await bcrypt.hash(password, 10);
// Create a new user
const user = await context.prisma.User.create({
data: {
email,
password: hashedPassword,
name,
},
});
const token = generateToken(user);
return {
token,
user,
};
},
login: async (parent, args, context) => {
const { email, password } = args;
// Find the user by email
const user = await context.prisma.user.findUnique({
where: { email },
});
if (!user) {
throw new Error('User not found');
}
// Check if the password is correct
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('Invalid password');
}
const token = generateToken(user);
return {
token,
user,
};
},
post: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const newLink = context.prisma.link.create({
data: {
url: args.url,
description: args.description,
postedById: context.user.id,
},
});
return newLink;
},
updatePost: async (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const { id, description, url } = args;
const updatedLink = await context.prisma.link.update({
where: { id },
data: {
description,
url,
},
});
return updatedLink;
},
deletePost: async (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const { id } = args;
const link = await context.prisma.link.findUnique({
where: { id },
});
if (!link) {
throw new Error(`Link with ID ${id} not found`);
}
const deletedLink = await context.prisma.link.delete({
where: { id },
});
return deletedLink;
},
},
};
// Middleware to authenticate users
const getUserFromToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
return null;
}
};
// Initialize Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const token = req.headers.authorization || '';
const user = token ? getUserFromToken(token.replace('Bearer ', '')) : null;
return {
prisma,
user,
};
},
});
// Start the server
server.listen().then(({ url }) => {
console.log(`🚀 Server is running at ${url}`);
});
+57
View File
@@ -0,0 +1,57 @@
import { ApolloServer } from 'apollo-server';
import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
import * as path from 'path';
import jwt from 'jsonwebtoken';
import { queryResolvers } from './resolvers/queryResolvers.ts'; // Import query resolvers
import { mutationResolvers } from './resolvers/mutationResolvers.ts'; // Import mutation resolvers
// Get __dirname equivalent in ES Modules
const __dirname = path.dirname(new URL(import.meta.url).pathname);
// Load schema.graphql
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.graphql'), 'utf8');
// Initialize Prisma
const prisma = new PrismaClient();
const JWT_SECRET = 'your-jwt-secret'; // You should store this in environment variables
// Generate JWT token
export function generateToken(user) {
return jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '1h' });
}
// Middleware to authenticate users
const getUserFromToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
return null;
}
};
// GraphQL Resolvers
const resolvers = {
Query: queryResolvers,
Mutation: mutationResolvers,
};
// Initialize Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const token = req.headers.authorization || '';
const user = token ? getUserFromToken(token.replace('Bearer ', '')) : null;
return {
prisma,
user,
};
},
});
// Start the server
server.listen().then(({ url }) => {
console.log(`🚀 Server is running at ${url}`);
});
+111
View File
@@ -0,0 +1,111 @@
import bcrypt from 'bcryptjs';
import { generateToken } from '../index.ts'; // We will import this from index.ts
export const mutationResolvers = {
signup: async (parent, args, context) => {
const { email, password, name } = args;
const existingUser = await context.prisma.User.findUnique({
where: { email },
});
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await bcrypt.hash(password, 10);
const user = await context.prisma.User.create({
data: {
email,
password: hashedPassword,
name,
},
});
const token = generateToken(user);
return {
token,
user,
};
},
login: async (parent, args, context) => {
const { email, password } = args;
const user = await context.prisma.user.findUnique({
where: { email },
});
if (!user) {
throw new Error('User not found');
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('Invalid password');
}
const token = generateToken(user);
return {
token,
user,
};
},
post: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const newLink = context.prisma.link.create({
data: {
url: args.url,
description: args.description,
postedById: context.user.id,
},
});
return newLink;
},
updatePost: async (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const { id, description, url } = args;
const updatedLink = await context.prisma.link.update({
where: { id },
data: {
description,
url,
},
});
return updatedLink;
},
deletePost: async (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const { id } = args;
const link = await context.prisma.link.findUnique({
where: { id },
});
if (!link) {
throw new Error(`Link with ID ${id} not found`);
}
const deletedLink = await context.prisma.link.delete({
where: { id },
});
return deletedLink;
},
};
+11
View File
@@ -0,0 +1,11 @@
import { generateToken } from '../index.ts';
export const queryResolvers = {
info: () => `This is the API of a Hackernews Clone`,
feed: async (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return context.prisma.link.findMany();
},
};
+32
View File
@@ -0,0 +1,32 @@
type Query {
info: String
feed(id: Int): [Link] #straight brackets when needing to link the data to another table
}
type Link {
id: Int!
description: String!
url: String!
postedBy: User
}
type AuthPayload {
token: String
user: User
}
type User {
id: ID!
name: String!
email: String!
links: [Link!]!
}
type Mutation {
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
post(url: String!, description: String!): Link!
updatePost(id: Int!, description: String!, url: String!): Link
deletePost(id: Int!): Link
}
+179
View File
@@ -0,0 +1,179 @@
import { ApolloServer } from 'apollo-server';
import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
import * as path from 'path';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
// Get __dirname equivalent in ES Modules
const __dirname = path.dirname(new URL(import.meta.url).pathname);
// Load schema.graphql
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.graphql'), 'utf8');
// Initialize Prisma
const prisma = new PrismaClient();
const JWT_SECRET = 'your-jwt-secret'; // You should store this in environment variables
// Generate JWT token
function generateToken(user) {
return jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '1h' });
}
// GraphQL Resolvers
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
feed: async (parent, args, context) => {
// Check if the user is authenticated
if (!context.user) {
throw new Error('Not authenticated');
}
// If authenticated, return the feed
return context.prisma.link.findMany();
},
},
Mutation: {
signup: async (parent, args, context) => {
const { email, password, name } = args;
// Check if the user already exists
const existingUser = await context.prisma.User.findUnique({
where: { email },
});
if (existingUser) {
throw new Error('User already exists');
}
// Hash the password before storing it
const hashedPassword = await bcrypt.hash(password, 10);
// Create a new user
const user = await context.prisma.User.create({
data: {
email,
password: hashedPassword,
name,
},
});
const token = generateToken(user);
return {
token,
user,
};
},
login: async (parent, args, context) => {
const { email, password } = args;
// Find the user by email
const user = await context.prisma.user.findUnique({
where: { email },
});
if (!user) {
throw new Error('User not found');
}
// Check if the password is correct
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('Invalid password');
}
const token = generateToken(user);
return {
token,
user,
};
},
post: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const newLink = context.prisma.link.create({
data: {
url: args.url,
description: args.description,
postedById: context.user.id,
},
});
return newLink;
},
updatePost: async (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const { id, description, url } = args;
const updatedLink = await context.prisma.link.update({
where: { id },
data: {
description,
url,
},
});
return updatedLink;
},
deletePost: async (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const { id } = args;
const link = await context.prisma.link.findUnique({
where: { id },
});
if (!link) {
throw new Error(`Link with ID ${id} not found`);
}
const deletedLink = await context.prisma.link.delete({
where: { id },
});
return deletedLink;
},
},
};
// Middleware to authenticate users
const getUserFromToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
return null;
}
};
// Initialize Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const token = req.headers.authorization || '';
const user = token ? getUserFromToken(token.replace('Bearer ', '')) : null;
return {
prisma,
user,
};
},
});
// Start the server
server.listen().then(({ url }) => {
console.log(`🚀 Server is running at ${url}`);
});
-27
View File
@@ -1,27 +0,0 @@
import { ApolloServer } from 'apollo-server';
// 1 the dypeDefs defines the graphQL schema
const typeDefs = `
type Query {
info: String!
}
`
// 2 The implementation of the graphQL schema. Identical to th4e typeDef schema in [1]
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`
}
}
// 3 This passes the resolvers [2] and schema [1] to the apollo server
const server = new ApolloServer({
typeDefs,
resolvers,
})
server
.listen()
.then(({ url }) =>
console.log(`Server is running on ${url}`)
);
+57
View File
@@ -0,0 +1,57 @@
import { ApolloServer } from 'apollo-server';
import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
import * as path from 'path';
import jwt from 'jsonwebtoken';
import { queryResolvers } from './resolvers/queryResolvers.ts'; // Import query resolvers
import { mutationResolvers } from './resolvers/mutationResolvers.ts'; // Import mutation resolvers
// Get __dirname equivalent in ES Modules
const __dirname = path.dirname(new URL(import.meta.url).pathname);
// Load schema.graphql
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.graphql'), 'utf8');
// Initialize Prisma
const prisma = new PrismaClient();
const JWT_SECRET = 'your-jwt-secret'; // You should store this in environment variables
// Generate JWT token
export function generateToken(authenticate) {
return jwt.sign({ userId: authenticate.id }, JWT_SECRET, { expiresIn: '1h' });
}
// Middleware to authenticate users
const getUserFromToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
return null;
}
};
// GraphQL Resolvers
const resolvers = {
Query: queryResolvers,
Mutation: mutationResolvers,
};
// Initialize Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const token = req.headers.authorization || '';
const authenticate = token ? getUserFromToken(token.replace('Bearer ', '')) : null;
return {
prisma,
authenticate,
};
},
});
// Start the server
server.listen().then(({ url }) => {
console.log(`🚀 Server is running at ${url}`);
});
+115
View File
@@ -0,0 +1,115 @@
import bcrypt from 'bcryptjs';
import { generateToken } from '../index.ts'; // We will import this from index.ts
export const mutationResolvers = {
signup: async (parent, args, context) => {
const { email, password, firstName } = args;
const existingAccount = await context.prisma.Account.findUnique({
where: { email },
});
if (existingAccount) {
throw new Error('Account already exists');
}
const hashedPassword = await bcrypt.hash(password, 10);
const account = await context.prisma.Account.create({
data: {
email,
password: hashedPassword,
firstName,
},
});
const token = generateToken(account);
console.log('Account Data:', account);
return {
token,
account,
};
},
login: async (parent, args, context) => {
const { email, password } = args;
const account = await context.prisma.Account.findUnique({
where: { email },
});
if (!account) {
throw new Error('User not found');
}
const isValid = await bcrypt.compare(password, account.password);
if (!isValid) {
throw new Error('Invalid password');
}
const token = generateToken(account);
return {
token,
account,
};
},
/*
post: (parent, args, context) => {
if (!context.authenticate) {
throw new Error('Not authenticated');
}
const newLink = context.prisma.link.create({
data: {
url: args.url,
description: args.description,
postedById: context.user.id,
},
});
return newLink;
},
updatePost: async (parent, args, context) => {
if (!context.authenticate) {
throw new Error('Not authenticated');
}
const { id, description, url } = args;
const updatedLink = await context.prisma.link.update({
where: { id },
data: {
description,
url,
},
});
return updatedLink;
},
deletePost: async (parent, args, context) => {
if (!context.authenticate) {
throw new Error('Not authenticated');
}
const { id } = args;
const link = await context.prisma.link.findUnique({
where: { id },
});
if (!link) {
throw new Error(`Link with ID ${id} not found`);
}
const deletedLink = await context.prisma.link.delete({
where: { id },
});
return deletedLink;
},
*/
};
+11
View File
@@ -0,0 +1,11 @@
import { generateToken } from '../index.ts';
export const queryResolvers = {
info: () => `This is the API of a Hackernews Clone`,
feed: async (parent, args, context) => {
if (!context.authenticate) {
throw new Error('Not authenticated');
}
return context.prisma.account.findMany();
},
};
+42
View File
@@ -0,0 +1,42 @@
type Query {
info: String
feed(id: Int): [Account] #straight brackets when needing to link the data to another table
account: [Account]
}
type Link {
id: Int!
description: String!
url: String!
postedBy: Account
}
type AuthPayload {
token: String
account: Account #no brackets when its a single result
}
type Account {
id: Int!
firstName: String
lastName: String!
email: String!
accountInformation: [AccountInformation]
}
type AccountInformation {
id: Int!
sex: String!
age: Int!
}
type Mutation {
signup(email: String!, password: String!, firstName: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
post(url: String!, description: String!): Link!
updatePost(id: Int!, description: String!, url: String!): Link
deletePost(id: Int!): Link
}