Initial commit
This commit is contained in:
68
server/src/index.js
Normal file
68
server/src/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const { ApolloServer, PubSub } = require('apollo-server');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const Query = require('./resolvers/Query');
|
||||
const Mutation = require('./resolvers/Mutation');
|
||||
const Subscription = require('./resolvers/Subscription');
|
||||
const User = require('./resolvers/User');
|
||||
const Link = require('./resolvers/Link');
|
||||
const Vote = require('./resolvers/Vote');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getUserId } = require('./utils');
|
||||
|
||||
const pubsub = new PubSub();
|
||||
|
||||
const prisma = new PrismaClient({
|
||||
errorFormat: 'minimal'
|
||||
});
|
||||
|
||||
const resolvers = {
|
||||
Query,
|
||||
Mutation,
|
||||
Subscription,
|
||||
User,
|
||||
Link,
|
||||
Vote
|
||||
};
|
||||
|
||||
const server = new ApolloServer({
|
||||
typeDefs: fs.readFileSync(
|
||||
path.join(__dirname, 'schema.graphql'),
|
||||
'utf8'
|
||||
),
|
||||
resolvers,
|
||||
context: ({ req }) => {
|
||||
return {
|
||||
...req,
|
||||
prisma,
|
||||
pubsub,
|
||||
userId:
|
||||
req && req.headers.authorization
|
||||
? getUserId(req)
|
||||
: null
|
||||
};
|
||||
},
|
||||
subscriptions: {
|
||||
onConnect: (connectionParams) => {
|
||||
if (connectionParams.authToken) {
|
||||
return {
|
||||
prisma,
|
||||
userId: getUserId(
|
||||
null,
|
||||
connectionParams.authToken
|
||||
)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
prisma
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server
|
||||
.listen()
|
||||
.then(({ url }) =>
|
||||
console.log(`Server is running on ${url}`)
|
||||
);
|
||||
16
server/src/resolvers/Link.js
Normal file
16
server/src/resolvers/Link.js
Normal file
@@ -0,0 +1,16 @@
|
||||
function postedBy(parent, args, context) {
|
||||
return context.prisma.link
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.postedBy();
|
||||
}
|
||||
|
||||
function votes(parent, args, context) {
|
||||
return context.prisma.link
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.votes();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
postedBy,
|
||||
votes
|
||||
};
|
||||
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');
|
||||
|
||||
async function post(parent, args, context, info) {
|
||||
const { userId } = context;
|
||||
|
||||
const newLink = await context.prisma.link.create({
|
||||
data: {
|
||||
url: args.url,
|
||||
description: args.description,
|
||||
postedBy: { connect: { id: userId } }
|
||||
}
|
||||
});
|
||||
context.pubsub.publish('NEW_LINK', newLink);
|
||||
|
||||
return newLink;
|
||||
}
|
||||
|
||||
async function signup(parent, args, context, info) {
|
||||
const password = await bcrypt.hash(args.password, 10);
|
||||
const user = await context.prisma.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.prisma.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 vote(parent, args, context, info) {
|
||||
const { userId } = context;
|
||||
const vote = await context.prisma.vote.findUnique({
|
||||
where: {
|
||||
linkId_userId: {
|
||||
linkId: Number(args.linkId),
|
||||
userId: userId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Boolean(vote)) {
|
||||
throw new Error(
|
||||
`Already voted for link: ${args.linkId}`
|
||||
);
|
||||
}
|
||||
|
||||
const newVote = context.prisma.vote.create({
|
||||
data: {
|
||||
user: { connect: { id: userId } },
|
||||
link: { connect: { id: Number(args.linkId) } }
|
||||
}
|
||||
});
|
||||
context.pubsub.publish('NEW_VOTE', newVote);
|
||||
|
||||
return newVote;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
post,
|
||||
signup,
|
||||
login,
|
||||
vote
|
||||
};
|
||||
29
server/src/resolvers/Query.js
Normal file
29
server/src/resolvers/Query.js
Normal file
@@ -0,0 +1,29 @@
|
||||
async function feed(parent, args, context, info) {
|
||||
const where = args.filter
|
||||
? {
|
||||
OR: [
|
||||
{ description: { contains: args.filter } },
|
||||
{ url: { contains: args.filter } }
|
||||
]
|
||||
}
|
||||
: {};
|
||||
|
||||
const links = await context.prisma.link.findMany({
|
||||
where,
|
||||
skip: args.skip,
|
||||
take: args.take,
|
||||
orderBy: args.orderBy
|
||||
});
|
||||
|
||||
const count = await context.prisma.link.count({ where });
|
||||
|
||||
return {
|
||||
id: 'main-feed',
|
||||
links,
|
||||
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 newLink = {
|
||||
subscribe: newLinkSubscribe,
|
||||
resolve: payload => {
|
||||
return payload
|
||||
},
|
||||
}
|
||||
|
||||
function newVoteSubscribe(parent, args, context, info) {
|
||||
return context.pubsub.asyncIterator("NEW_VOTE")
|
||||
}
|
||||
|
||||
const newVote = {
|
||||
subscribe: newVoteSubscribe,
|
||||
resolve: payload => {
|
||||
return payload
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
newLink,
|
||||
newVote
|
||||
}
|
||||
9
server/src/resolvers/User.js
Normal file
9
server/src/resolvers/User.js
Normal file
@@ -0,0 +1,9 @@
|
||||
function links(parent, args, context) {
|
||||
return context.prisma.user
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.links();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
links
|
||||
};
|
||||
16
server/src/resolvers/Vote.js
Normal file
16
server/src/resolvers/Vote.js
Normal file
@@ -0,0 +1,16 @@
|
||||
function link(parent, args, context) {
|
||||
return context.prisma.vote
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.link();
|
||||
}
|
||||
|
||||
function user(parent, args, context) {
|
||||
return context.prisma.vote
|
||||
.findUnique({ where: { id: parent.id } })
|
||||
.user();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
link,
|
||||
user
|
||||
};
|
||||
71
server/src/schema.graphql
Normal file
71
server/src/schema.graphql
Normal file
@@ -0,0 +1,71 @@
|
||||
type Query {
|
||||
info: String!
|
||||
feed(
|
||||
filter: String
|
||||
skip: Int
|
||||
take: Int
|
||||
orderBy: LinkOrderByInput
|
||||
): Feed!
|
||||
}
|
||||
|
||||
type Feed {
|
||||
id: ID!
|
||||
links: [Link!]!
|
||||
count: Int!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
post(url: String!, description: String!): Link!
|
||||
signup(
|
||||
email: String!
|
||||
password: String!
|
||||
name: String!
|
||||
): AuthPayload
|
||||
login(email: String!, password: String!): AuthPayload
|
||||
vote(linkId: ID!): Vote
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
newLink: Link
|
||||
newVote: Vote
|
||||
}
|
||||
|
||||
type AuthPayload {
|
||||
token: String
|
||||
user: User
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
email: String!
|
||||
links: [Link!]!
|
||||
}
|
||||
|
||||
type Link {
|
||||
id: ID!
|
||||
description: String!
|
||||
url: String!
|
||||
postedBy: User
|
||||
votes: [Vote!]!
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type Vote {
|
||||
id: ID!
|
||||
link: Link!
|
||||
user: User!
|
||||
}
|
||||
|
||||
input LinkOrderByInput {
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user