Skip to content

Commit

Permalink
Graphql
Browse files Browse the repository at this point in the history
  • Loading branch information
AlbertKhomich committed Dec 13, 2023
1 parent bee41b3 commit 914d264
Show file tree
Hide file tree
Showing 14 changed files with 430 additions and 111 deletions.
49 changes: 38 additions & 11 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ const mongoose = require('mongoose');
const path = require('path');
const multer = require('multer');
const { v4: uuidv4 } = require('uuid');
const { graphqlHTTP } = require('express-graphql');

const feedRoutes = require('./router/feed');
const authRoutes = require('./router/auth');
const graphqlSchema = require('./graphql/schema');
const graphqlResolver = require('./graphql/resolvers');
const auth = require('./middleware/auth');
const { clearImage } = require('./utils/file');

const MONGODB_URI =
'mongodb+srv://alterego:[email protected]/messages?retryWrites=true&w=majority';
Expand Down Expand Up @@ -48,11 +51,41 @@ app.use((req, res, next) => {
'GET, POST, PUT, PATCH, DELETE'
);
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});

app.use('/feed', feedRoutes);
app.use('/auth', authRoutes);
app.use(auth);

app.put('/post-image', (req, res, next) => {
if (!req.isAuth) throw new Error('Not authenticated');
if (!req.file) res.status(200).json({ message: 'No image provided' });
if (req.body.oldPath) clearImage(req.body.oldPath);
return res.status(201).json({
message: 'File stored',
filePath: req.file.path.replace('\\', '/'),
});
});

app.use(
'/graphql',
graphqlHTTP({
schema: graphqlSchema,
rootValue: graphqlResolver,
graphiql: true,
customFormatErrorFn(err) {
if (!err.originalError) {
return err;
}
const data = err.originalError.data;
const message = err.message || 'An error occurred';
const code = err.originalError.code || 500;
return { message: message, status: code, data: data };
},
})
);

app.use((error, req, res, next) => {
console.log(error);
Expand All @@ -65,12 +98,6 @@ app.use((error, req, res, next) => {
mongoose
.connect(MONGODB_URI)
.then(() => {
const server = app.listen(8080);
const io = require('./socket').init(server, {
cors: { origin: 'http://localhost:3000', methods: ['GET', 'POST'] },
});
io.on('connection', (socket) => {
console.log('Client connected.');
});
app.listen(8080);
})
.catch((err) => console.log(err));
239 changes: 239 additions & 0 deletions graphql/resolvers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
const bcrypt = require('bcryptjs');
const validator = require('validator');
const jwt = require('jsonwebtoken');

const User = require('../models/user');
const Post = require('../models/post');
const { clearImage } = require('../utils/file');

module.exports = {
createUser: async function ({ userInput }, req) {
const errors = [];
if (!validator.isEmail(userInput.email)) {
errors.push({ message: 'Email is invalid' });
}
if (!validator.isLength(userInput.password, { min: 5 })) {
errors.push({ message: 'Password is too short' });
}
if (errors.length > 0) {
const error = new Error('Invalid input');
error.data = errors;
error.code = 422;
throw error;
}
const existingUser = await User.findOne({ email: userInput.email });
if (existingUser) {
throw new Error('User exist already');
}
const hashedPw = await bcrypt.hash(userInput.password, 12);

const user = new User({
email: userInput.email,
name: userInput.name,
password: hashedPw,
});
const createdUser = await user.save();
return { ...createdUser._doc, _id: createdUser._id.toString() };
},
login: async function ({ email, password }) {
const user = await User.findOne({ email: email });
if (!user) {
const error = new Error('User not found.');
error.code = 401;
throw error;
}
const isEqual = await bcrypt.compare(password, user.password);
if (!isEqual) {
const error = new Error('Password is incorrect');
error.code = 401;
throw error;
}
const token = jwt.sign(
{
userId: user._id.toString(),
email: user.email,
},
'secret',
{ expiresIn: '1h' }
);
return { token: token, userId: user._id.toString() };
},
createPost: async function ({ postInput }, req) {
if (!req.isAuth) {
const error = new Error('Not authenticated');
error.code = 401;
throw error;
}
const errors = [];
if (!validator.isLength(postInput.title, { min: 5 })) {
errors.push({ message: 'Title is too short' });
}
if (!validator.isLength(postInput.content, { min: 5 })) {
errors.push({ message: 'Content is too short' });
}
if (errors.length > 0) {
const error = new Error('Invalid input');
error.data = errors;
error.code = 422;
throw error;
}
const user = await User.findById(req.userId);
if (!user) {
const error = new Error('No such user');
error.code = 401;
throw error;
}
const post = new Post({
title: postInput.title,
imageUrl: postInput.imageUrl,
content: postInput.content,
creator: user,
});
await post.save();
user.posts.push(post);
await user.save();
return { ...post._doc, createdAt: post.createdAt.toDateString() };
},
showPosts: async function ({ page }, req) {
if (!req.isAuth) {
const error = new Error('Not authenticated');
error.code = 401;
throw error;
}
if (!page) page = 1;
const perPage = 2;
const postsCount = await Post.find().countDocuments();
const posts = await Post.find()
.sort({ createdAt: -1 })
.skip((page - 1) * perPage)
.limit(perPage)
.populate('creator');
return {
posts: posts.map((post) => {
return { ...post._doc, createdAt: post.createdAt.toDateString() };
}),
totalPosts: postsCount,
};
},
post: async function ({ id }, req) {
if (!req.isAuth) {
const error = new Error('Not authenticated');
error.code = 401;
throw error;
}
const post = await Post.findById(id).populate('creator');
if (!post) {
const error = new Error('No current post');
error.code = 404;
throw error;
}
return { ...post._doc, createdAt: post.createdAt.toDateString() };
},
updatePost: async function ({ id, postInput }, req) {
// if (!req.isAuth) {
// const error = new Error('Not authenticated');
// error.code = 401;
// throw error;
// }
const post = await Post.findById(id).populate('creator');
if (!post) {
const error = new Error('No current post');
error.code = 404;
throw error;
}
// if (post.creator._id.toString() !== req.userId.toString()) {
// const error = new Error('Not authorized');
// error.code = 403;
// throw error;
// }
const errors = [];
if (!validator.isLength(postInput.title, { min: 5 })) {
errors.push({ message: 'Title is too short' });
}
if (!validator.isLength(postInput.content, { min: 5 })) {
errors.push({ message: 'Content is too short' });
}
if (errors.length > 0) {
const error = new Error('Invalid input');
error.data = errors;
error.code = 422;
throw error;
}
post.title = postInput.title;
post.content = postInput.content;
if (postInput.imageUrl) {
post.imageUrl = postInput.imageUrl;
} else {
postInput.imageUrl = post.imageUrl;
}
const updatedPost = await post.save();
return {
...updatedPost._doc,
createdAt: updatedPost.createdAt.toDateString(),
};
},
deletePost: async function ({ id }, req) {
if (!req.isAuth) {
const error = new Error('Not authenticated');
error.code = 401;
throw error;
}
const post = await Post.findById(id);
if (!post) {
const error = new Error('Post not found');
error.code = 404;
throw error;
}
if (post.creator.toString() !== req.userId.toString()) {
const error = new Error('Not authorized');
error.code = 403;
throw error;
}
clearImage(post.imageUrl);
await Post.findByIdAndDelete(id);
const user = await User.findById(req.userId);
user.posts.pull(id);
await user.save();
return true;
},
user: async function (args, req) {
if (!req.isAuth) {
const error = new Error('Not authenticated');
error.code = 401;
throw error;
}
const user = await User.findById(req.userId);
if (!user) {
const error = new Error('User not found');
error.code = 404;
throw error;
}
return user;
},
newStatus: async function ({ status }, req) {
if (!req.isAuth) {
const error = new Error('Not authenticated');
error.code = 401;
throw error;
}
const user = await User.findById(req.userId);
if (!user) {
const error = new Error('User not found');
error.code = 404;
throw error;
}
const errors = [];
if (!validator.isLength(status, { min: 1 })) {
errors.push({ message: 'Status can not be empty' });
}
if (errors.length > 0) {
const error = new Error('Invalid input');
error.data = errors;
error.code = 422;
throw error;
}
user.status = status;
await user.save();
return user;
},
};
64 changes: 64 additions & 0 deletions graphql/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const { buildSchema } = require('graphql');

module.exports = buildSchema(`
type Post {
_id: ID!
title: String!
content: String!
imageUrl: String!
creator: User!
createdAt: String!
updatedAt: String!
}
type PostData {
posts: [Post!]!
totalPosts: Int!
}
type User {
_id: ID!
name: String!
email: String!
password: String
status: String!
posts: [Post!]!
}
type AuthData {
token: String!
userId: String!
}
input UserInputData {
email: String!
name: String!
password: String!
}
input PostInputData {
title: String!
content: String!
imageUrl: String
}
type RootQuery {
login(email: String!, password: String!): AuthData!
showPosts(page: Int): PostData
post(id: ID!): Post!
user: User!
}
type RootMutation {
createUser(userInput: UserInputData): User!
createPost(postInput: PostInputData): Post!
updatePost(id: ID!, postInput: PostInputData): Post!
deletePost(id: ID!): Boolean
newStatus(status: String!): User!
}
schema {
query: RootQuery
mutation: RootMutation
}
`);
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading

0 comments on commit 914d264

Please sign in to comment.