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
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
# Keep environment variables out of version control
.env

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
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
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
}
}

896
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

1312
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
{
"name": "jobapp",
"version": "1.0.0",
"main": "index.js",
"main": "index.ts",
"scripts": {
"start": "nodemon --exec ts-node src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
@@ -11,10 +12,20 @@
"description": "A job platform to make hiring faster and easier",
"type": "module",
"dependencies": {
"@prisma/client": "^6.5.0",
"apollo-server": "^2.26.2",
"graphql": "^14.7.0"
"bcryptjs": "^3.0.2",
"graphql": "^14.7.0",
"jsonwebtoken": "^9.0.2"
},
"devDependencies": {
"nodemon": "^2.0.20",
"prisma": "^6.5.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
},
"repository": {
"type": "git",
"url": "https://git.jackbeeby.au/jackbeeby/Job_App.git"
}
}

BIN
prisma/dev.db Normal file

Binary file not shown.

View File

@@ -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");

View File

@@ -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;

View File

@@ -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");

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
prisma/schema.prisma Normal file
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
src copy/index copy.ts Normal file
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
src copy/index.ts Normal file
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}`);
});

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;
},
};

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
src copy/schema.graphql Normal file
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
src/index copy.ts Normal file
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}`);
});

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
src/index.ts Normal file
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}`);
});

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;
},
*/
};

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
src/schema.graphql Normal file
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
}