From 0ca174b335740ecb17ec6bbb97253f3e6bdabb8f Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 21 Sep 2019 13:34:44 +0200 Subject: [PATCH 1/2] DataAccess changes - changed data access structure - changed graphql schema - changed create-tables sql script (added vote_type to votes table) --- src/app.ts | 6 +- src/lib/dataaccess/DataObject.ts | 14 ++ src/lib/dataaccess/Post.ts | 101 ++++++++++++++ src/lib/dataaccess/User.ts | 136 +++++++++++++++++++ src/lib/{DTO.ts => dataaccess/index.ts} | 4 +- src/public/graphql/schema.graphql | 167 ++++++++++++++---------- src/sql/create-tables.sql | 3 +- 7 files changed, 355 insertions(+), 76 deletions(-) create mode 100644 src/lib/dataaccess/DataObject.ts create mode 100644 src/lib/dataaccess/Post.ts create mode 100644 src/lib/dataaccess/User.ts rename src/lib/{DTO.ts => dataaccess/index.ts} (98%) diff --git a/src/app.ts b/src/app.ts index 698a895..3634256 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,7 @@ import * as express from "express"; import * as http from "http"; import * as path from "path"; import * as socketIo from "socket.io"; -import {DTO} from "./lib/DTO"; +import dataaccess from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; @@ -10,20 +10,18 @@ class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; - public dto: DTO; constructor() { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); - this.dto = new DTO(); } /** * initializes everything that needs to be initialized asynchronous. */ public async init() { - await this.dto.init(); + await dataaccess.init(); await routes.ioListeners(this.io); this.app.set("views", path.join(__dirname, "views")); this.app.set("view engine", "pug"); diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts new file mode 100644 index 0000000..afabc32 --- /dev/null +++ b/src/lib/dataaccess/DataObject.ts @@ -0,0 +1,14 @@ +abstract class DataObject { + protected dataLoaded: boolean = false; + + constructor(public id: number, protected row?: any) { + } + + protected abstract loadData(): Promise; + + protected loadDataIfNotExists() { + if (this.dataLoaded) { + this.loadData(); + } + } +} diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts new file mode 100644 index 0000000..51c6b4c --- /dev/null +++ b/src/lib/dataaccess/Post.ts @@ -0,0 +1,101 @@ +import {queryHelper, VoteType} from "./index"; +import {User} from "./User"; + +export class Post extends DataObject { + public readonly id: number; + private $upvotes: number; + private $downvotes: number; + private $createdAt: string; + private $content: string; + private $author: number; + private $type: string; + + /** + * Returns the upvotes of a post. + */ + public async upvotes(): Promise { + this.loadDataIfNotExists(); + return this.$upvotes; + } + + /** + * Returns the downvotes of the post + */ + public async downvotes(): Promise { + this.loadDataIfNotExists(); + return this.$downvotes; + } + + /** + * The content of the post (markdown) + */ + public async content(): Promise { + this.loadDataIfNotExists(); + return this.$content; + } + + /** + * The date the post was created at. + */ + public async createdAt(): Promise { + this.loadDataIfNotExists(); + return this.$createdAt; + } + + /** + * The autor of the post. + */ + public async author(): Promise { + this.loadDataIfNotExists(); + return new User(this.$author); + } + + /** + * Deletes the post. + */ + public async delete(): Promise { + const query = await queryHelper.first({ + text: "DELETE FROM posts WHERE id = $1", + values: [this.id], + }); + } + + /** + * The type of vote the user performed on the post. + */ + public async userVote(userId: number): Promise { + const result = await queryHelper.first({ + text: "SELECT vote_type FROM votes WHERE user_id = $1 AND item_id = $2", + values: [userId, this.id], + }); + if (result) { + return result.vote_type; + } else { + return null; + } + } + + /** + * Loads the data from the database if needed. + */ + protected async loadData(): Promise { + let result: any; + if (this.row) { + result = this.row; + } else { + result = await queryHelper.first({ + text: "SELECT * FROM posts WHERE posts.id = $1", + values: [this.id], + }); + } + if (result) { + this.$author = result.author; + this.$content = result.content; + this.$downvotes = result.downvotes; + this.$upvotes = result.upvotes; + this.$createdAt = result.created_at; + this.$type = result.type; + this.dataLoaded = true; + } + } +} diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts new file mode 100644 index 0000000..ea7c7c9 --- /dev/null +++ b/src/lib/dataaccess/User.ts @@ -0,0 +1,136 @@ +import {queryHelper} from "./index"; +import {Post} from "./Post"; + +export class User extends DataObject { + private $name: string; + private $handle: string; + private $email: string; + private $greenpoints: number; + private $joinedAt: string; + + /** + * The name of the user + */ + public async name(): Promise { + this.loadDataIfNotExists(); + return this.$name; + } + + /** + * Sets the username of the user + * @param name + */ + public async setName(name: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET name = $1 WHERE id = $2", + values: [name, this.id], + }); + return result.name; + } + + /** + * The unique handle of the user. + */ + public async handle(): Promise { + this.loadDataIfNotExists(); + return this.$handle; + } + + /** + * Updates the handle of the user + */ + public async setHandle(handle: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", + values: [handle, this.id], + }); + return result.handle; + } + + /** + * The email of the user + */ + public async email(): Promise { + this.loadDataIfNotExists(); + return this.$email; + } + + /** + * Sets the email of the user + * @param email + */ + public async setEmail(email: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", + values: [email, this.id], + }); + return result.email; + } + + /** + * The number of greenpoints of the user + */ + public async greenpoints(): Promise { + this.loadDataIfNotExists(); + return this.$greenpoints; + } + + /** + * Sets the greenpoints of a user. + * @param points + */ + public async setGreenpoints(points: number): Promise { + const result = await queryHelper.first({ + text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", + values: [points, this.id], + }); + return result.greenpoints; + } + + /** + * The date the user joined the platform + */ + public async joinedAt(): Promise { + this.loadDataIfNotExists(); + return new Date(this.$joinedAt); + } + + /** + * Returns all posts for a user. + */ + public async posts(): Promise { + const result = await queryHelper.all({ + text: "SELECT * FROM posts WHERE author = $1", + values: [this.id], + }); + const posts = []; + + for (const row of result) { + posts.push(new Post(row.id, row)); + } + return posts; + } + + /** + * Fetches the data for the user. + */ + protected async loadData(): Promise { + let result: any; + if (this.row) { + result = this.row; + } else { + result = await queryHelper.first({ + text: "SELECT * FROM users WHERE user.id = $1", + values: [this.id], + }); + } + if (result) { + this.$name = result.name; + this.$handle = result.handle; + this.$email = result.email; + this.$greenpoints = result.greenpoints; + this.$joinedAt = result.joined_at; + this.dataLoaded = true; + } + } +} diff --git a/src/lib/DTO.ts b/src/lib/dataaccess/index.ts similarity index 98% rename from src/lib/DTO.ts rename to src/lib/dataaccess/index.ts index 2d93484..6325227 100644 --- a/src/lib/DTO.ts +++ b/src/lib/dataaccess/index.ts @@ -1,7 +1,7 @@ import {Runtime} from "inspector"; import {Pool} from "pg"; -import globals from "./globals"; -import {QueryHelper} from "./QueryHelper"; +import globals from "../globals"; +import {QueryHelper} from "../QueryHelper"; const config = globals.config; const tableCreationFile = __dirname + "/../sql/create-tables.sql"; diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index f5feb67..fb04f53 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -1,100 +1,129 @@ type Query { - "returns the user object for a given user id" - getUser(userId: ID): User - "returns the post object for a post id" - getPost(postId: ID): Post - "returns the chat object for a chat id" - getChat(chatId: ID): ChatRoom - "returns the request object for a request id" - getRequest(requestId: ID): Request - "find a post by the posted date or content" - findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] - "find a user by user name or handle" - findUser(first: Int, offset: Int, name: String!, handle: String!): [User] + "returns the user object for a given user id" + getUser(userId: ID): User + + "returns the post object for a post id" + getPost(postId: ID): Post + + "returns the chat object for a chat id" + getChat(chatId: ID): ChatRoom + + "returns the request object for a request id" + getRequest(requestId: ID): Request + + "find a post by the posted date or content" + findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] + + "find a user by user name or handle" + findUser(first: Int, offset: Int, name: String!, handle: String!): [User] } type Mutation { - "Upvote/downvote a Post" - vote(postId: ID!, type: [VoteType!]!): Boolean - "Report the post" - report(postId: ID!): Boolean + "Upvote/downvote a Post" + vote(postId: ID!, type: [VoteType!]!): Boolean + + "Report the post" + report(postId: ID!): Boolean + "lets you accept a request for a given request id" - acceptRequest(requestId: ID!): Boolean + acceptRequest(requestId: ID!): Boolean + "send a message in a Chatroom" - sendMessage(chatId: ID!, content: String!): Boolean + sendMessage(chatId: ID!, content: String!): Boolean + + # TODO: createPost, deletePost, sendRequest, denyRequest } "represents a single user account" type User { - "url for the Profile picture of the User" - profilePicture: String! - "name of the User" - name: String! - "unique identifier name from the User" - handle: String! - "Id of the User" - id: ID! - "the total number of posts the user posted" - numberOfPosts: Int - "returns a given number of posts of a user" - getAllPosts(first: Int=10, offset: Int): [Post] - "creation date of the user account" - joinedDate: String! - "returns chats the user pinned" - pinnedChats: [ChatRoom] - "returns all friends of the user" - friends: [User] - "all request for groupChats/friends/events" - requests: [Request] + "url for the Profile picture of the User" + profilePicture: String! + + "name of the User" + name: String! + + "unique identifier name from the User" + handle: String! + + "Id of the User" + id: ID! + + "the total number of posts the user posted" + numberOfPosts: Int + + "returns a given number of posts of a user" + getAllPosts(first: Int=10, offset: Int): [Post] + + "creation date of the user account" + joinedDate: String! + + "returns chats the user pinned" + pinnedChats: [ChatRoom] + + "returns all friends of the user" + friends: [User] + + "all request for groupChats/friends/events" + requests: [Request] } "represents a single user post" type Post { - "returns the path to the posts picture if it has one" - picture: String - "returns the text of the post" - text: String - "upvotes of the Post" - upvotes: Int! - "downvotes of the Post" - downvotes: Int! - "the user that is the author of the Post" - author: User! - "date the post was created" - creationDate: String! - "returns the type of vote the user performed on the post" - alreadyVoted: VoteType - "returns the tags of the post" - tags: [String] + "returns the path to the posts picture if it has one" + picture: String + + "returns the text of the post" + text: String + + "upvotes of the Post" + upvotes: Int! + + "downvotes of the Post" + downvotes: Int! + + "the user that is the author of the Post" + author: User! + + "date the post was created" + creationDate: String! + + "returns the type of vote the user performed on the post" + userVote: VoteType + + "returns the tags of the post" + tags: [String] } "represents a request of any type" type Request { - "id of the request" - id: ID! - "type of the request" - requestType: RequestType! + "id of the request" + id: ID! + + "type of the request" + requestType: RequestType! } "represents a chatroom" type ChatRoom { - "the members of the chatroom" - members: [User!] - "return a specfic range of messages posted in the chat" - getMessages(first: Int, offset: Int): [String] - "id of the chat" - id: ID! + "the members of the chatroom" + members: [User!] + + "return a specfic range of messages posted in the chat" + getMessages(first: Int, offset: Int): [String] + + "id of the chat" + id: ID! } "represents the type of vote performed on a post" enum VoteType { - UPVOTE - DOWNVOTE + UPVOTE + DOWNVOTE } "represents the type of request that the user has received" enum RequestType { - FRIENDREQUEST - GROUPINVITE - EVENTINVITE + FRIENDREQUEST + GROUPINVITE + EVENTINVITE } diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 0d02b2f..7a6292e 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -20,7 +20,8 @@ CREATE TABLE IF NOT EXISTS posts ( CREATE TABLE IF NOT EXISTS votes ( user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE + item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, + vote_type varchar(8) DEFAULT 1 ); CREATE TABLE IF NOT EXISTS events ( From 67650267421d1e91400771c4cf9c3cac27c93890 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 21 Sep 2019 13:34:58 +0200 Subject: [PATCH 2/2] Changed dataAccess index --- src/lib/dataaccess/index.ts | 219 +++--------------------------------- 1 file changed, 17 insertions(+), 202 deletions(-) diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 6325227..8019329 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,7 +1,7 @@ -import {Runtime} from "inspector"; import {Pool} from "pg"; import globals from "../globals"; import {QueryHelper} from "../QueryHelper"; +import {User} from "./User"; const config = globals.config; const tableCreationFile = __dirname + "/../sql/create-tables.sql"; @@ -12,27 +12,21 @@ const dbClient: Pool = new Pool({ port: config.database.port, user: config.database.user, }); -const queryHelper = new QueryHelper(dbClient, tableCreationFile); - -export class DTO { - private queryHelper: QueryHelper; - - constructor() { - this.queryHelper = queryHelper; - } +export const queryHelper = new QueryHelper(dbClient, tableCreationFile); +namespace dataaccess { /** * Initializes everything that needs to be initialized asynchronous. */ - public async init() { - await this.queryHelper.createTables(); + export async function init() { + await queryHelper.createTables(); } /** * Returns the user by id * @param userId */ - public getUser(userId: number) { + export function getUser(userId: number) { return new User(userId); } @@ -40,209 +34,30 @@ export class DTO { * Returns the user by handle. * @param userHandle */ - public async getUserByHandle(userHandle: string) { + export async function getUserByHandle(userHandle: string) { const result = await this.queryHelper.first({ text: "SELECT * FROM users WHERE users.handle = $1", values: [userHandle], }); return new User(result.id, result); } -} - -export class User { - public readonly id: number; - private $name: string; - private $handle: string; - private $email: string; - private $greenpoints: number; - private $joinedAt: string; - private dataLoaded: boolean; - - /** - * Constructor of the user - * @param id - * @param row - */ - constructor(id: number, private row?: any) { - this.id = id; - } - - /** - * The name of the user - */ - public async name(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$name; - } - - /** - * Sets the username of the user - * @param name - */ - public async setName(name: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET name = $1 WHERE id = $2", - values: [name, this.id], - }); - return result.name; - } - - /** - * The unique handle of the user. - */ - public async handle(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$handle; - } - - /** - * Updates the handle of the user - */ - public async setHandle(handle: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", - values: [handle, this.id], - }); - return result.handle; - } - - /** - * The email of the user - */ - public async email(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$email; - } - - /** - * Sets the email of the user - * @param email - */ - public async setEmail(email: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", - values: [email, this.id], - }); - return result.email; - } - - /** - * The number of greenpoints of the user - */ - public async greenpoints(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$greenpoints; - } - - /** - * Sets the greenpoints of a user. - * @param points - */ - public async setGreenpoints(points: number): Promise { - const result = await queryHelper.first({ - text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", - values: [points, this.id], - }); - return result.greenpoints; - } /** - * The date the user joined the platform + * Enum representing the types of votes that can be performed on a post. */ - public async joinedAt(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return new Date(this.$joinedAt); + export enum VoteType { + UPVOTE = "UPVOTE", + DOWNVOTE = "DOWNVOTE", } /** - * Fetches the data for the user. + * Enum representing the types of request that can be created. */ - private async loadData(): Promise { - let result: any; - if (this.row) { - result = this.row; - } else { - result = await queryHelper.first({ - text: "SELECT * FROM users WHERE user.id = $1", - values: [this.id], - }); - } - if (result) { - this.$name = result.name; - this.$handle = result.handle; - this.$email = result.email; - this.$greenpoints = result.greenpoints; - this.$joinedAt = result.joined_at; - this.dataLoaded = true; - } + export enum RequestType { + FRIENDREQUEST = "FRIENDREQUEST", + GROUPINVITE = "GROUPINVITE", + EVENTINVITE = "EVENTINVITE", } } -export class Post { - public readonly id: number; - private $upvotes: number; - private $downvotes: number; - private $createdAt: string; - private $content: string; - private $author: number; - private $type: string; - private dataLoaded: boolean = false; - - constructor(id: number, private row?: any) { - this.id = id; - } - - /** - * Returns the upvotes of a post. - */ - public async upvotes() { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$upvotes; - } - - /** - * Returns the downvotes of the post - */ - public async downvotes() { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$downvotes; - } - - /** - * Loads the data from the database if needed. - */ - private async loadData(): Promise { - let result: any; - if (this.row) { - result = this.row; - } else { - result = await queryHelper.first({ - text: "SELECT * FROM posts WHERE posts.id = $1", - values: [this.id], - }); - } - if (result) { - this.$author = result.author; - this.$content = result.content; - this.$downvotes = result.downvotes; - this.$upvotes = result.upvotes; - this.$createdAt = result.created_at; - this.$type = result.type; - this.dataLoaded = true; - } - } -} +export default dataaccess;