Started with Mongo
This commit is contained in:
7
server/.gitignore
vendored
Normal file
7
server/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.env*
|
||||
dist
|
||||
package-lock.json
|
||||
node_modules
|
||||
.idea
|
||||
.vscode
|
||||
*.log
|
||||
3
server/README.md
Normal file
3
server/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# hackernews-graphql-js
|
||||
|
||||
This repository contains the final project for the [**GraphQL.js tutorial**](https://www.howtographql.com/graphql-js/0-introduction/) on [How to GraphQL](https://www.howtographql.com/). Note that it also serves as foundation for all frontend tutorials on the site.
|
||||
30
server/package.json
Normal file
30
server/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"apollo-server": "^2.19.0",
|
||||
"apollo-server-express": "^2.19.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "^1.19.0",
|
||||
"chai": "^4.2.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express-graphql": "^0.12.0",
|
||||
"graphql": "^14.7.0",
|
||||
"graphql-tools": "^7.0.2",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"migrate": "^1.7.0",
|
||||
"mocha": "^8.2.1",
|
||||
"mongodb": "^3.6.3",
|
||||
"mongoose": "^5.11.9",
|
||||
"node-migrate": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.6"
|
||||
}
|
||||
}
|
||||
99
server/src/index.js
Normal file
99
server/src/index.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const { ApolloServer, PubSub } = require('apollo-server');
|
||||
var MongoClient = require('mongodb', { useUnifiedTopology: true }).MongoClient;
|
||||
// import { MongoClient } from 'mongodb'
|
||||
const Query = require('./resolvers/Query');
|
||||
const Mutation = require('./resolvers/Mutation');
|
||||
const Subscription = require('./resolvers/Subscription');
|
||||
const User = require('./resolvers/User');
|
||||
const Appointment = require('./resolvers/Appointment');
|
||||
const Follow = require('./resolvers/Follow');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getUserId } = require('./utils');
|
||||
|
||||
const pubsub = new PubSub();
|
||||
|
||||
// const mongo = new MongoClient({
|
||||
// errorFormat: 'minimal'
|
||||
// });
|
||||
|
||||
const mongo = MongoClient.connect("mongodb+srv://admin:hEbAjhvkrFDHAP3@cluster0.0hjtt.mongodb.net/Calendar?retryWrites=true&w=majority", function (err, db) {
|
||||
|
||||
if (err) throw err;
|
||||
console.log("ALL good");
|
||||
//Write databse Insert/Update/Query code here..
|
||||
|
||||
});
|
||||
|
||||
const resolvers = {
|
||||
Query,
|
||||
Mutation,
|
||||
Subscription,
|
||||
User,
|
||||
Appointment,
|
||||
Follow
|
||||
};
|
||||
|
||||
let db;
|
||||
|
||||
const server = new ApolloServer({
|
||||
typeDefs: fs.readFileSync(
|
||||
path.join(__dirname, 'schema.graphql'),
|
||||
'utf8'
|
||||
),
|
||||
resolvers,
|
||||
// context: async () => {
|
||||
// if (!db) {
|
||||
// try {
|
||||
// const dbClient = new MongoClient(
|
||||
// 'mongodb+srv://test:qwerty123@cluster0-yvwjx.mongodb.net/next-graphql?retryWrites=true&w=majority',
|
||||
// {
|
||||
// useNewUrlParser: true,
|
||||
// useUnifiedTopology: true,
|
||||
// }
|
||||
// )
|
||||
|
||||
// if (!dbClient.isConnected()) await dbClient.connect()
|
||||
// db = dbClient.db('next-graphql') // database name
|
||||
// } catch (e) {
|
||||
// console.log('--->error while connecting with graphql context (db)', e)
|
||||
// }
|
||||
// }
|
||||
|
||||
// return { db }
|
||||
// },
|
||||
context: ({ req }) => {
|
||||
return {
|
||||
...req,
|
||||
mongo,
|
||||
pubsub,
|
||||
userId:
|
||||
req && req.headers.authorization
|
||||
? getUserId(req)
|
||||
: null
|
||||
};
|
||||
},
|
||||
subscriptions: {
|
||||
onConnect: (connectionParams) => {
|
||||
if (connectionParams.authToken) {
|
||||
return {
|
||||
mongo,
|
||||
userId: getUserId(
|
||||
null,
|
||||
connectionParams.authToken
|
||||
)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
mongo
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server
|
||||
.listen()
|
||||
.then(({ url }) =>
|
||||
console.log(`Server is running on ${url}`)
|
||||
);
|
||||
16
server/src/resolvers/Appointment.js
Normal file
16
server/src/resolvers/Appointment.js
Normal file
@@ -0,0 +1,16 @@
|
||||
function createdBy(parent, args, context) {
|
||||
return context.mongo.appointment
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.createdBy();
|
||||
}
|
||||
|
||||
function follows(parent, args, context) {
|
||||
return context.mongo.appointment
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.follows();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createdBy,
|
||||
follows
|
||||
};
|
||||
16
server/src/resolvers/Follow.js
Normal file
16
server/src/resolvers/Follow.js
Normal file
@@ -0,0 +1,16 @@
|
||||
function appointment(parent, args, context) {
|
||||
return context.mongo.follow
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.appointment();
|
||||
}
|
||||
|
||||
function user(parent, args, context) {
|
||||
return context.mongo.follow
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.user();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
appointment,
|
||||
user
|
||||
};
|
||||
91
server/src/resolvers/Mutation.js
Normal file
91
server/src/resolvers/Mutation.js
Normal file
@@ -0,0 +1,91 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { APP_SECRET } = require('../utils');
|
||||
|
||||
function createAppointment(parent, args, context, info) {
|
||||
const { userId } = context;
|
||||
|
||||
const newAppointment = context.mongo.appointment.create({
|
||||
data: {
|
||||
title: args.title,
|
||||
description: args.description,
|
||||
createdBy: { connect: { id: userId } }
|
||||
}
|
||||
});
|
||||
|
||||
return newAppointment;
|
||||
}
|
||||
|
||||
async function signup(parent, args, context, info) {
|
||||
console.log(context);
|
||||
const password = await bcrypt.hash(args.password, 10);
|
||||
const user = await context.mongo.user.create({
|
||||
data: { ...args, password }
|
||||
});
|
||||
|
||||
const token = jwt.sign({ userId: user.id }, APP_SECRET);
|
||||
|
||||
return {
|
||||
token,
|
||||
user
|
||||
};
|
||||
}
|
||||
|
||||
async function login(parent, args, context, info) {
|
||||
const user = await context.mongo.user.findUnique({
|
||||
where: { email: args.email }
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error('No such user found');
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(
|
||||
args.password,
|
||||
user.password
|
||||
);
|
||||
if (!valid) {
|
||||
throw new Error('Invalid password');
|
||||
}
|
||||
|
||||
const token = jwt.sign({ userId: user.id }, APP_SECRET);
|
||||
|
||||
return {
|
||||
token,
|
||||
user
|
||||
};
|
||||
}
|
||||
|
||||
async function follow(parent, args, context, info) {
|
||||
const { userId } = context;
|
||||
const follow = await context.mongo.follow.findUnique({
|
||||
where: {
|
||||
linkId_userId: {
|
||||
linkId: Number(args.linkId),
|
||||
userId: userId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Boolean(follow)) {
|
||||
throw new Error(
|
||||
`Already followed the appointment: ${args.linkId}`
|
||||
);
|
||||
}
|
||||
|
||||
const newFollow = context.mongo.follow.create({
|
||||
data: {
|
||||
user: { connect: { id: userId } },
|
||||
link: { connect: { id: Number(args.linkId) } }
|
||||
}
|
||||
});
|
||||
context.pubsub.publish('NEW_FOLLOW', newFollow);
|
||||
|
||||
return newFollow;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createAppointment,
|
||||
signup,
|
||||
login,
|
||||
follow
|
||||
};
|
||||
30
server/src/resolvers/Query.js
Normal file
30
server/src/resolvers/Query.js
Normal file
@@ -0,0 +1,30 @@
|
||||
async function feed(parent, args, context, info) {
|
||||
console.log(context);
|
||||
const where = args.filter
|
||||
? {
|
||||
OR: [
|
||||
{ title: { contains: args.filter } },
|
||||
{ description: { contains: args.filter } }
|
||||
]
|
||||
}
|
||||
: {};
|
||||
|
||||
const appointments = await context.mongo.appointment.findMany({
|
||||
where,
|
||||
skip: args.skip,
|
||||
take: args.take,
|
||||
orderBy: args.orderBy
|
||||
});
|
||||
|
||||
const count = await context.mongo.appointment.count({ where });
|
||||
|
||||
return {
|
||||
id: 'main-feed',
|
||||
appointments,
|
||||
count
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
feed
|
||||
};
|
||||
26
server/src/resolvers/Subscription.js
Normal file
26
server/src/resolvers/Subscription.js
Normal file
@@ -0,0 +1,26 @@
|
||||
function newLinkSubscribe(parent, args, context, info) {
|
||||
return context.pubsub.asyncIterator("NEW_LINK")
|
||||
}
|
||||
|
||||
const newAppointment = {
|
||||
subscribe: newLinkSubscribe,
|
||||
resolve: payload => {
|
||||
return payload
|
||||
},
|
||||
}
|
||||
|
||||
function newFollowSubscribe(parent, args, context, info) {
|
||||
return context.pubsub.asyncIterator("NEW_FOLLOW")
|
||||
}
|
||||
|
||||
const newFollow = {
|
||||
subscribe: newFollowSubscribe,
|
||||
resolve: payload => {
|
||||
return payload
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
newAppointment,
|
||||
newFollow
|
||||
}
|
||||
9
server/src/resolvers/User.js
Normal file
9
server/src/resolvers/User.js
Normal file
@@ -0,0 +1,9 @@
|
||||
function appointments(parent, args, context) {
|
||||
return context.mongo.user
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.appointments();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
appointments
|
||||
};
|
||||
78
server/src/schema.graphql
Normal file
78
server/src/schema.graphql
Normal file
@@ -0,0 +1,78 @@
|
||||
type Query {
|
||||
info: String!
|
||||
feed(
|
||||
filter: String
|
||||
skip: Int
|
||||
take: Int
|
||||
orderBy: AppointmentOrderByInput
|
||||
): Feed!
|
||||
}
|
||||
|
||||
type Feed {
|
||||
id: ID!
|
||||
appointments: [Appointment!]!
|
||||
count: Int!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createAppointment(
|
||||
title: String!,
|
||||
description: String!,
|
||||
start: DateTime!,
|
||||
end: DateTime!,
|
||||
): Appointment!
|
||||
signup(
|
||||
email: String!
|
||||
password: String!
|
||||
name: String!
|
||||
): AuthPayload
|
||||
login(email: String!, password: String!): AuthPayload
|
||||
follow(appointmentId: ID!): Follow
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
newAppointment: Appointment
|
||||
newFollow: Follow
|
||||
}
|
||||
|
||||
type AuthPayload {
|
||||
token: String
|
||||
user: User
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
email: String!
|
||||
appointments: [Appointment!]!
|
||||
}
|
||||
|
||||
type Appointment {
|
||||
id: ID!
|
||||
title: String!
|
||||
description: String!
|
||||
start: DateTime!
|
||||
end: DateTime!
|
||||
createdBy: User
|
||||
follows: [Follow!]!
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type Follow {
|
||||
id: ID!
|
||||
appointment: Appointment!
|
||||
user: User!
|
||||
}
|
||||
|
||||
input AppointmentOrderByInput {
|
||||
description: Sort
|
||||
url: Sort
|
||||
createdAt: Sort
|
||||
}
|
||||
|
||||
enum Sort {
|
||||
asc
|
||||
desc
|
||||
}
|
||||
|
||||
scalar DateTime
|
||||
30
server/src/utils.js
Normal file
30
server/src/utils.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const APP_SECRET = 'GraphQL-is-aw3some';
|
||||
|
||||
function getTokenPayload(token) {
|
||||
return jwt.verify(token, APP_SECRET);
|
||||
}
|
||||
|
||||
function getUserId(req, authToken) {
|
||||
if (req) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader) {
|
||||
const token = authHeader.replace('Bearer ', '');
|
||||
if (!token) {
|
||||
throw new Error('No token found');
|
||||
}
|
||||
const { userId } = getTokenPayload(token);
|
||||
return userId;
|
||||
}
|
||||
} else if (authToken) {
|
||||
const { userId } = getTokenPayload(authToken);
|
||||
return userId;
|
||||
}
|
||||
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
APP_SECRET,
|
||||
getUserId
|
||||
};
|
||||
4472
server/yarn.lock
Normal file
4472
server/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user