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
|
||||
|
||||
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
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",
|
||||
"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
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