Initial commit

This commit is contained in:
Riccardo
2021-01-01 10:14:50 +01:00
parent 67a1e45386
commit bccea7574a
40 changed files with 15172 additions and 0 deletions

68
server/src/index.js Normal file
View 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}`)
);

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

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

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

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

View File

@@ -0,0 +1,9 @@
function links(parent, args, context) {
return context.prisma.user
.findUnique({ where: { id: parent.id } })
.links();
}
module.exports = {
links
};

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