Errors and Structure

- moved graphql stuff to a seperate directory
- added error for already existing email in database
pull/1/head
Trivernis 5 years ago
parent 2d0a6e3433
commit bc8455f84b

@ -10,6 +10,7 @@ import {importSchema} from "graphql-import";
import * as http from "http";
import * as path from "path";
import * as socketIo from "socket.io";
import {resolver} from "./graphql/resolvers";
import dataaccess, {queryHelper} from "./lib/dataaccess";
import globals from "./lib/globals";
import routes from "./routes";
@ -72,8 +73,8 @@ class App {
// @ts-ignore all
context: {session: request.session},
graphiql: true,
rootValue: routes.resolvers(request, response),
schema: buildSchema(importSchema(path.join(__dirname, "./public/graphql/schema.graphql"))),
rootValue: resolver(request, response),
schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))),
};
}));
}

@ -0,0 +1,227 @@
import {GraphQLError} from "graphql";
import * as status from "http-status";
import dataaccess from "../lib/dataaccess";
import {Chatroom} from "../lib/dataaccess/Chatroom";
import {Post} from "../lib/dataaccess/Post";
import {Profile} from "../lib/dataaccess/Profile";
import {User} from "../lib/dataaccess/User";
import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors";
import globals from "../lib/globals";
import {InternalEvents} from "../lib/InternalEvents";
import {is} from "../lib/regex";
/**
* Returns the resolvers for the graphql api.
* @param req - the request object
* @param res - the response object
*/
export function resolver(req: any, res: any): any {
return {
getSelf() {
if (req.session.userId) {
return new Profile(req.session.userId);
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async getUser({userId, handle}: { userId: number, handle: string }) {
if (handle) {
return await dataaccess.getUserByHandle(handle);
} else if (userId) {
return new User(userId);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No userId or handle provided.");
}
},
async getPost({postId}: { postId: number }) {
if (postId) {
return await dataaccess.getPost(postId);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No postId given.");
}
},
async getChat({chatId}: { chatId: number }) {
if (chatId) {
return new Chatroom(chatId);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No chatId given.");
}
},
acceptCookies() {
req.session.cookiesAccepted = true;
return true;
},
async login({email, passwordHash}: { email: string, passwordHash: string }) {
if (email && passwordHash) {
try {
const user = await dataaccess.getUserByLogin(email, passwordHash);
req.session.userId = user.id;
return user;
} catch (err) {
globals.logger.warn(err.message);
globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST);
return err.graphqlError || err.message;
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No email or password given.");
}
},
logout() {
if (req.session.user) {
delete req.session.user;
return true;
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) {
if (username && email && passwordHash) {
if (!is.email(email)) {
res.status(status.BAD_REQUEST);
return new GraphQLError(`'${email}' is not a valid email address!`);
}
try {
const user = await dataaccess.registerUser(username, email, passwordHash);
req.session.userId = user.id;
return user;
} catch (err) {
globals.logger.warn(err.message);
globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST);
return err.graphqlError || err.message;
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No username, email or password given.");
}
},
async vote({postId, type}: { postId: number, type: dataaccess.VoteType }) {
if (postId && type) {
if (req.session.userId) {
return await (new Post(postId)).vote(req.session.userId, type);
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No postId or type given.");
}
},
async createPost({content}: { content: string }) {
if (content) {
if (req.session.userId) {
const post = await dataaccess.createPost(content, req.session.userId);
globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post);
return post;
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("Can't create empty post.");
}
},
async deletePost({postId}: { postId: number }) {
if (postId) {
const post = new Post(postId);
if ((await post.author()).id === req.session.userId) {
return await dataaccess.deletePost(post.id);
} else {
res.status(status.FORBIDDEN);
return new GraphQLError("User is not author of the post.");
}
} else {
return new GraphQLError("No postId given.");
}
},
async createChat({members}: { members: number[] }) {
if (req.session.userId) {
const chatMembers = [req.session.userId];
if (members) {
chatMembers.push(...members);
}
return await dataaccess.createChat(...chatMembers);
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async sendMessage({chatId, content}: { chatId: number, content: string }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (chatId && content) {
try {
const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content);
globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message);
return message;
} catch (err) {
globals.logger.warn(err.message);
globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST);
return err.graphqlError || err.message;
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No chatId or content given.");
}
},
async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (receiver && type) {
return await dataaccess.createRequest(req.session.userId, receiver, type);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No receiver or type given.");
}
},
async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (sender && type) {
const profile = new Profile(req.session.userId);
await profile.denyRequest(sender, type);
return true;
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No sender or type given.");
}
},
async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (sender && type) {
try {
const profile = new Profile(req.session.userId);
await profile.acceptRequest(sender, type);
return true;
} catch (err) {
globals.logger.warn(err.message);
globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST);
return err.graphqlError || err.message;
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No sender or type given.");
}
},
};
}

@ -21,7 +21,6 @@ abstract class Route {
public abstract async init(...params: any): Promise<any>;
public abstract async destroy(...params: any): Promise<any>;
public abstract async resolver(request: any, response: any): Promise<object>;
}
export default Route;

@ -1,5 +1,6 @@
import {Pool} from "pg";
import {ChatNotFoundError} from "../errors/ChatNotFoundError";
import {EmailAlreadyRegisteredError} from "../errors/EmailAlreadyRegisteredError";
import {UserNotFoundError} from "../errors/UserNotFoundError";
import globals from "../globals";
import {InternalEvents} from "../InternalEvents";
@ -91,11 +92,19 @@ namespace dataaccess {
* @param password
*/
export async function registerUser(username: string, email: string, password: string) {
const result = await queryHelper.first({
text: "INSERT INTO users (name, handle, password, email) VALUES ($1, $2, $3, $4) RETURNING *",
values: [username, generateHandle(username), password, email],
const existResult = await queryHelper.first({
text: "SELECT email FROM users WHERE email = $1;",
values: [email],
});
return new Profile(result.id, result);
if (!existResult || !existResult.email) {
const result = await queryHelper.first({
text: "INSERT INTO users (name, handle, password, email) VALUES ($1, $2, $3, $4) RETURNING *",
values: [username, generateHandle(username), password, email],
});
return new Profile(result.id, result);
} else {
throw new EmailAlreadyRegisteredError(email);
}
}
/**

@ -0,0 +1,8 @@
import {BaseError} from "./BaseError";
export class EmailAlreadyRegisteredError extends BaseError {
constructor(email: string) {
super(`A user for '${email}' does already exist.`);
}
}

@ -1,11 +1,11 @@
export namespace is {
const emailRegex = /\S+?@\S+?(\.\S+?)?\.\w{2,3}(.\w{2-3})?/g
const emailRegex = /\S+?@\S+?(\.\S+?)?\.\w{2,3}(.\w{2-3})?/g;
/**
* Tests if a string is a valid email.
* @param testString
*/
export function email(testString: string) {
return emailRegex.test(testString)
return emailRegex.test(testString);
}
}

@ -1,20 +1,17 @@
import {Router} from "express";
import {GraphQLError} from "graphql";
import * as status from "http-status";
import {Namespace, Server} from "socket.io";
import dataaccess from "../lib/dataaccess";
import {ChatMessage} from "../lib/dataaccess/ChatMessage";
import {Chatroom} from "../lib/dataaccess/Chatroom";
import {Post} from "../lib/dataaccess/Post";
import {Profile} from "../lib/dataaccess/Profile";
import {Request} from "../lib/dataaccess/Request";
import {User} from "../lib/dataaccess/User";
import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors";
import globals from "../lib/globals";
import {InternalEvents} from "../lib/InternalEvents";
import {is} from "../lib/regex";
import Route from "../lib/Route";
/**
* list of chatroom socket namespaces.
*/
const chatRooms: Namespace[] = [];
/**
@ -69,216 +66,6 @@ class HomeRoute extends Route {
*/
public async destroy(): Promise<void> {
this.router = null;
this.resolver = null;
}
/**
* Returns the resolvers for the graphql api.
* @param req - the request object
* @param res - the response object
*/
public resolver(req: any, res: any): any {
return {
getSelf() {
if (req.session.userId) {
return new Profile(req.session.userId);
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async getUser({userId, handle}: { userId: number, handle: string }) {
if (handle) {
return await dataaccess.getUserByHandle(handle);
} else if (userId) {
return new User(userId);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No userId or handle provided.");
}
},
async getPost({postId}: { postId: number }) {
if (postId) {
return await dataaccess.getPost(postId);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No postId given.");
}
},
async getChat({chatId}: { chatId: number }) {
if (chatId) {
return new Chatroom(chatId);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No chatId given.");
}
},
acceptCookies() {
req.session.cookiesAccepted = true;
return true;
},
async login({email, passwordHash}: { email: string, passwordHash: string }) {
if (email && passwordHash) {
try {
const user = await dataaccess.getUserByLogin(email, passwordHash);
req.session.userId = user.id;
return user;
} catch (err) {
globals.logger.verbose(`Failed to login user '${email}'`);
res.status(status.BAD_REQUEST);
return err.graphqlError;
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No email or password given.");
}
},
logout() {
if (req.session.user) {
delete req.session.user;
return true;
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) {
if (username && email && passwordHash) {
if (!is.email(email)) {
res.status(status.BAD_REQUEST);
return new GraphQLError(`'${email}' is not a valid email address!`);
}
const user = await dataaccess.registerUser(username, email, passwordHash);
if (user) {
req.session.userId = user.id;
return user;
} else {
res.status(status.INTERNAL_SERVER_ERROR);
return new GraphQLError("Failed to create account.");
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No username, email or password given.");
}
},
async vote({postId, type}: { postId: number, type: dataaccess.VoteType }) {
if (postId && type) {
if (req.session.userId) {
return await (new Post(postId)).vote(req.session.userId, type);
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No postId or type given.");
}
},
async createPost({content}: { content: string }) {
if (content) {
if (req.session.userId) {
const post = await dataaccess.createPost(content, req.session.userId);
globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post);
return post;
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("Can't create empty post.");
}
},
async deletePost({postId}: { postId: number }) {
if (postId) {
const post = new Post(postId);
if ((await post.author()).id === req.session.userId) {
return await dataaccess.deletePost(post.id);
} else {
res.status(status.FORBIDDEN);
return new GraphQLError("User is not author of the post.");
}
} else {
return new GraphQLError("No postId given.");
}
},
async createChat({members}: { members: number[] }) {
if (req.session.userId) {
const chatMembers = [req.session.userId];
if (members) {
chatMembers.push(...members);
}
return await dataaccess.createChat(...chatMembers);
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async sendMessage({chatId, content}: { chatId: number, content: string }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (chatId && content) {
try {
const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content);
globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message);
return message;
} catch (err) {
res.status(status.BAD_REQUEST);
return err.graphqlError;
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No chatId or content given.");
}
},
async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (receiver && type) {
return await dataaccess.createRequest(req.session.userId, receiver, type);
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No receiver or type given.");
}
},
async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (sender && type) {
const profile = new Profile(req.session.userId);
await profile.denyRequest(sender, type);
return true;
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No sender or type given.");
}
},
async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) {
if (!req.session.userId) {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
if (sender && type) {
try {
const profile = new Profile(req.session.userId);
await profile.acceptRequest(sender, type);
return true;
} catch (err) {
res.status(status.BAD_REQUEST);
return err.graphqlError;
}
} else {
res.status(status.BAD_REQUEST);
return new GraphQLError("No sender or type given.");
}
},
};
}
/**

@ -22,16 +22,6 @@ namespace routes {
router.use("/", homeRoute.router);
/**
* Asnyc function to create a graphql resolver that takes the request and response
* of express.js as arguments.
* @param request
* @param response
*/
export function resolvers(request: any, response: any): Promise<object> {
return homeRoute.resolver(request, response);
}
/**
* Assigns the io listeners or namespaces to the routes
* @param io

Loading…
Cancel
Save