update
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
# Keep environment variables out of version control
|
||||||
|
.env
|
||||||
35
README.md
35
README.md
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
896
node_modules/.package-lock.json
generated
vendored
896
node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
1312
package-lock.json
generated
1312
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -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
prisma/dev.db
Normal file
BIN
prisma/dev.db
Normal file
Binary file not shown.
44
prisma/migrations/20250329030839_init/migration.sql
Normal file
44
prisma/migrations/20250329030839_init/migration.sql
Normal 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");
|
||||||
43
prisma/migrations/20250329033214_init/migration.sql
Normal file
43
prisma/migrations/20250329033214_init/migration.sql
Normal 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;
|
||||||
8
prisma/migrations/20250329033745_init/migration.sql
Normal file
8
prisma/migrations/20250329033745_init/migration.sql
Normal 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");
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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
55
prisma/schema.prisma
Normal 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
179
src copy/index copy.ts
Normal 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
57
src copy/index.ts
Normal 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
src copy/resolvers/mutationResolvers.ts
Normal file
111
src copy/resolvers/mutationResolvers.ts
Normal 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
src copy/resolvers/queryResolvers.ts
Normal file
11
src copy/resolvers/queryResolvers.ts
Normal 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
32
src copy/schema.graphql
Normal 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
179
src/index copy.ts
Normal 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
src/index.js
27
src/index.js
@@ -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
57
src/index.ts
Normal 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
src/resolvers/mutationResolvers.ts
Normal file
115
src/resolvers/mutationResolvers.ts
Normal 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
src/resolvers/queryResolvers.ts
Normal file
11
src/resolvers/queryResolvers.ts
Normal 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
42
src/schema.graphql
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user