Merge branch 'julius-dev' of Software_Engineering_I/greenvironment-server into max-dev

pull/1/head
Max_ES 5 years ago committed by Gitea
commit 3fab56f340

@ -12,3 +12,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Graphql Schema - Graphql Schema
- default-config file and generation of config file on startup - default-config file and generation of config file on startup
- DTOs - DTOs
- Home Route

44
package-lock.json generated

@ -1039,9 +1039,9 @@
} }
}, },
"chokidar": { "chokidar": {
"version": "2.1.6", "version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
"integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
"dev": true, "dev": true,
"requires": { "requires": {
"anymatch": "^2.0.0", "anymatch": "^2.0.0",
@ -1858,9 +1858,9 @@
} }
}, },
"es5-ext": { "es5-ext": {
"version": "0.10.50", "version": "0.10.51",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz",
"integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"es6-iterator": "~2.0.3", "es6-iterator": "~2.0.3",
@ -1880,13 +1880,13 @@
} }
}, },
"es6-symbol": { "es6-symbol": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz",
"integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"d": "1", "d": "^1.0.1",
"es5-ext": "~0.10.14" "es5-ext": "^0.10.51"
} }
}, },
"es6-weak-map": { "es6-weak-map": {
@ -2691,14 +2691,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -2717,7 +2715,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -2897,8 +2894,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -3004,8 +3000,7 @@
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },
@ -3026,6 +3021,11 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
}, },
"g": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/g/-/g-2.0.1.tgz",
"integrity": "sha1-C1lj69DKcOO8jGdmk0oCGCHIuFc="
},
"gauge": { "gauge": {
"version": "2.7.4", "version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
@ -7013,9 +7013,9 @@
} }
}, },
"upath": { "upath": {
"version": "1.1.2", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
"integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
"dev": true "dev": true
}, },
"uri-js": { "uri-js": {

@ -49,6 +49,7 @@
"express-session": "^1.16.2", "express-session": "^1.16.2",
"express-socket.io-session": "^1.3.5", "express-socket.io-session": "^1.3.5",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"g": "^2.0.1",
"graphql": "^14.4.2", "graphql": "^14.4.2",
"graphql-import": "^0.7.1", "graphql-import": "^0.7.1",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",

@ -2,7 +2,7 @@ import * as express from "express";
import * as http from "http"; import * as http from "http";
import * as path from "path"; import * as path from "path";
import * as socketIo from "socket.io"; import * as socketIo from "socket.io";
import {DTO} from "./lib/DTO"; import dataaccess from "./lib/dataaccess";
import globals from "./lib/globals"; import globals from "./lib/globals";
import routes from "./routes"; import routes from "./routes";
@ -10,22 +10,22 @@ class App {
public app: express.Application; public app: express.Application;
public io: socketIo.Server; public io: socketIo.Server;
public server: http.Server; public server: http.Server;
public dto: DTO;
constructor() { constructor() {
this.app = express(); this.app = express();
this.server = new http.Server(this.app); this.server = new http.Server(this.app);
this.io = socketIo(this.server); this.io = socketIo(this.server);
this.dto = new DTO();
} }
/** /**
* initializes everything that needs to be initialized asynchronous. * initializes everything that needs to be initialized asynchronous.
*/ */
public async init() { 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("views", path.join(__dirname, "views"));
this.app.set("view engine", "pug"); this.app.set("view engine", "pug");
this.app.use(express.static(path.join(__dirname, "public")));
this.app.use(routes.router); this.app.use(routes.router);
} }

@ -1,16 +1,9 @@
import * as fsx from "fs-extra";
import App from "./app"; import App from "./app";
const configPath = "config.yaml";
const defaultConfig = __dirname + "/default-config.yaml";
/** /**
* async main function wrapper. * async main function wrapper.
*/ */
(async () => { (async () => {
if (!(await fsx.pathExists(configPath))) {
await fsx.copy(defaultConfig, configPath);
}
const app = new App(); const app = new App();
await app.init(); await app.init();
app.start(); app.start();

@ -1,248 +0,0 @@
import {Runtime} from "inspector";
import {Pool} from "pg";
import globals from "./globals";
import {QueryHelper} from "./QueryHelper";
const config = globals.config;
const tableCreationFile = __dirname + "/../sql/create-tables.sql";
const dbClient: Pool = new Pool({
database: config.database.database,
host: config.database.host,
password: config.database.password,
port: config.database.port,
user: config.database.user,
});
const queryHelper = new QueryHelper(dbClient, tableCreationFile);
export class DTO {
private queryHelper: QueryHelper;
constructor() {
this.queryHelper = queryHelper;
}
/**
* Initializes everything that needs to be initialized asynchronous.
*/
public async init() {
await this.queryHelper.createTables();
}
/**
* Returns the user by id
* @param userId
*/
public getUser(userId: number) {
return new User(userId);
}
/**
* Returns the user by handle.
* @param userHandle
*/
public async 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<string> {
if (!this.dataLoaded) {
await this.loadData();
}
return this.$name;
}
/**
* Sets the username of the user
* @param name
*/
public async setName(name: string): Promise<string> {
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<string> {
if (!this.dataLoaded) {
await this.loadData();
}
return this.$handle;
}
/**
* Updates the handle of the user
*/
public async setHandle(handle: string): Promise<string> {
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<string> {
if (!this.dataLoaded) {
await this.loadData();
}
return this.$email;
}
/**
* Sets the email of the user
* @param email
*/
public async setEmail(email: string): Promise<string> {
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<number> {
if (!this.dataLoaded) {
await this.loadData();
}
return this.$greenpoints;
}
/**
* Sets the greenpoints of a user.
* @param points
*/
public async setGreenpoints(points: number): Promise<number> {
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<Date> {
if (!this.dataLoaded) {
await this.loadData();
}
return new Date(this.$joinedAt);
}
/**
* Fetches the data for the user.
*/
private async loadData(): Promise<void> {
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 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<void> {
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;
}
}
}

@ -1,10 +1,24 @@
/**
* @author Trivernis
* @remarks
*
* Taken from {@link https://github.com/Trivernis/whooshy}
*/
import * as fsx from "fs-extra"; import * as fsx from "fs-extra";
import {Pool, PoolClient, QueryConfig, QueryResult} from "pg"; import {Pool, PoolClient, QueryConfig, QueryResult} from "pg";
import globals from "./globals"; import globals from "./globals";
const logger = globals.logger; const logger = globals.logger;
/**
* Transaction class to wrap SQL transactions.
*/
export class SqlTransaction { export class SqlTransaction {
/**
* Constructor.
* @param client
*/
constructor(private client: PoolClient) { constructor(private client: PoolClient) {
} }
@ -45,10 +59,19 @@ export class SqlTransaction {
} }
} }
/**
* Query helper for easyer fetching of a specific row count.
*/
export class QueryHelper { export class QueryHelper {
private pool: Pool; private pool: Pool;
constructor(pgPool: Pool, private tableCreationFile?: string) { /**
* Constructor.
* @param pgPool
* @param [tableCreationFile]
* @param [tableUpdateFile]
*/
constructor(pgPool: Pool, private tableCreationFile?: string, private tableUpdateFile?: string) {
this.pool = pgPool; this.pool = pgPool;
} }
@ -63,6 +86,17 @@ export class QueryHelper {
} }
} }
/**
* Updates the definition of the tables if the table update file was passed in the constructor
*/
public async updateTableDefinitions() {
if (this.tableUpdateFile) {
logger.info("Updating table definitions...");
const tableSql = await fsx.readFile(this.tableUpdateFile, "utf-8");
await this.query({text: tableSql});
}
}
/** /**
* executes the sql query with values and returns all results. * executes the sql query with values and returns all results.
* @param query * @param query

@ -0,0 +1,27 @@
/**
* @author Trivernis
* @remarks
*
* Taken from {@link https://github.com/Trivernis/whooshy}
*/
import {Router} from "express";
import {Namespace, Server} from "socket.io";
/**
* Abstract Route class to be implemented by each route.
* This class contains the socket-io Server, router and resolver
* for each route.
*/
abstract class Route {
public router?: Router;
protected io?: Server;
protected ions?: Namespace;
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;

@ -0,0 +1,20 @@
/**
* abstact DataObject class
*/
export abstract class DataObject {
protected dataLoaded: boolean = false;
constructor(public id: number, protected row?: any) {
}
protected abstract loadData(): Promise<void>;
/**
* Loads data from the database if data has not been loaded
*/
protected loadDataIfNotExists() {
if (this.dataLoaded) {
this.loadData();
}
}
}

@ -0,0 +1,103 @@
import {DataObject} from "./DataObject";
import {queryHelper} from "./index";
import dataaccess 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<number> {
this.loadDataIfNotExists();
return this.$upvotes;
}
/**
* Returns the downvotes of the post
*/
public async downvotes(): Promise<number> {
this.loadDataIfNotExists();
return this.$downvotes;
}
/**
* The content of the post (markdown)
*/
public async content(): Promise<string> {
this.loadDataIfNotExists();
return this.$content;
}
/**
* The date the post was created at.
*/
public async createdAt(): Promise<string> {
this.loadDataIfNotExists();
return this.$createdAt;
}
/**
* The autor of the post.
*/
public async author(): Promise<User> {
this.loadDataIfNotExists();
return new User(this.$author);
}
/**
* Deletes the post.
*/
public async delete(): Promise<void> {
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<dataaccess.VoteType> {
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<void> {
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;
}
}
}

@ -0,0 +1,137 @@
import {DataObject} from "./DataObject";
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<string> {
this.loadDataIfNotExists();
return this.$name;
}
/**
* Sets the username of the user
* @param name
*/
public async setName(name: string): Promise<string> {
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<string> {
this.loadDataIfNotExists();
return this.$handle;
}
/**
* Updates the handle of the user
*/
public async setHandle(handle: string): Promise<string> {
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<string> {
this.loadDataIfNotExists();
return this.$email;
}
/**
* Sets the email of the user
* @param email
*/
public async setEmail(email: string): Promise<string> {
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<number> {
this.loadDataIfNotExists();
return this.$greenpoints;
}
/**
* Sets the greenpoints of a user.
* @param points
*/
public async setGreenpoints(points: number): Promise<number> {
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<Date> {
this.loadDataIfNotExists();
return new Date(this.$joinedAt);
}
/**
* Returns all posts for a user.
*/
public async posts(): Promise<Post[]> {
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<void> {
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;
}
}
}

@ -0,0 +1,66 @@
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";
const tableUpdateFile = __dirname + "/../../sql/update-tables.sql";
const dbClient: Pool = new Pool({
database: config.database.database,
host: config.database.host,
password: config.database.password,
port: config.database.port,
user: config.database.user,
});
export const queryHelper = new QueryHelper(dbClient, tableCreationFile, tableUpdateFile);
namespace dataaccess {
/**
* Initializes everything that needs to be initialized asynchronous.
*/
export async function init() {
await queryHelper.updateTableDefinitions();
await queryHelper.createTables();
}
/**
* Returns the user by id
* @param userId
*/
export function getUser(userId: number) {
return new User(userId);
}
/**
* Returns the user by handle.
* @param userHandle
*/
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);
}
/**
* Enum representing the types of votes that can be performed on a post.
*/
export enum VoteType {
UPVOTE = "UPVOTE",
DOWNVOTE = "DOWNVOTE",
}
/**
* Enum representing the types of request that can be created.
*/
export enum RequestType {
FRIENDREQUEST = "FRIENDREQUEST",
GROUPINVITE = "GROUPINVITE",
EVENTINVITE = "EVENTINVITE",
}
}
export default dataaccess;

@ -1,7 +1,22 @@
/**
* @author Trivernis
* @remarks
*
* Partly taken from {@link https://github.com/Trivernis/whooshy}
*/
import * as fsx from "fs-extra"; import * as fsx from "fs-extra";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import * as winston from "winston"; import * as winston from "winston";
const configPath = "config.yaml";
const defaultConfig = __dirname + "/../default-config.yaml";
// ensure that the config exists by copying the default config.
if (!(fsx.pathExistsSync(configPath))) {
fsx.copySync(defaultConfig, configPath);
}
/** /**
* Defines global variables to be used. * Defines global variables to be used.
*/ */

@ -1,100 +1,145 @@
type Query { type Query {
"returns the user object for a given user id" "returns the user object for a given user id"
getUser(userId: ID): User getUser(userId: ID): User
"returns the post object for a post id"
getPost(postId: ID): Post "returns the post object for a post id"
"returns the chat object for a chat id" getPost(postId: ID): Post
getChat(chatId: ID): ChatRoom
"returns the request object for a request id" "returns the chat object for a chat id"
getRequest(requestId: ID): Request getChat(chatId: ID): ChatRoom
"find a post by the posted date or content"
findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] "returns the request object for a request id"
"find a user by user name or handle" getRequest(requestId: ID): Request
findUser(first: Int, offset: Int, name: String!, handle: String!): [User]
"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 { type Mutation {
"Upvote/downvote a Post" "Upvote/downvote a Post"
vote(postId: ID!, type: [VoteType!]!): Boolean vote(postId: ID!, type: [VoteType!]!): Boolean
"Report the post"
report(postId: ID!): Boolean "Report the post"
report(postId: ID!): Boolean
"send a request"
sendRequest(reciever: ID!, type: RequestType): Boolean
"lets you accept a request for a given request id" "lets you accept a request for a given request id"
acceptRequest(requestId: ID!): Boolean acceptRequest(requestId: ID!): Boolean
"lets you deny a request for a given request id"
denyRequest(requestId: ID!): Boolean
"send a message in a Chatroom" "send a message in a Chatroom"
sendMessage(chatId: ID!, content: String!): Boolean sendMessage(chatId: ID!, content: String!): Boolean
"create the post"
createPost(text: String, picture: String, tags: [String]): Boolean
"delete the post for a given post id"
deletePost(postId: ID!): Boolean
} }
"represents a single user account" "represents a single user account"
type User { type User {
"url for the Profile picture of the User" "url for the Profile picture of the User"
profilePicture: String! profilePicture: String!
"name of the User"
name: String! "name of the User"
"unique identifier name from the User" name: String!
handle: String!
"Id of the User" "unique identifier name from the User"
id: ID! handle: String!
"the total number of posts the user posted"
numberOfPosts: Int "Id of the User"
"returns a given number of posts of a user" id: ID!
getAllPosts(first: Int=10, offset: Int): [Post]
"creation date of the user account" "the total number of posts the user posted"
joinedDate: String! numberOfPosts: Int
"returns chats the user pinned"
pinnedChats: [ChatRoom] "returns a given number of posts of a user"
"returns all friends of the user" getAllPosts(first: Int=10, offset: Int): [Post]
friends: [User]
"all request for groupChats/friends/events" "creation date of the user account"
requests: [Request] 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" "represents a single user post"
type Post { type Post {
"returns the path to the posts picture if it has one" "returns the path to the posts picture if it has one"
picture: String picture: String
"returns the text of the post"
text: String "returns the text of the post"
"upvotes of the Post" text: String
upvotes: Int!
"downvotes of the Post" "upvotes of the Post"
downvotes: Int! upvotes: Int!
"the user that is the author of the Post"
author: User! "downvotes of the Post"
"date the post was created" downvotes: Int!
creationDate: String!
"returns the type of vote the user performed on the post" "the user that is the author of the Post"
alreadyVoted: VoteType author: User!
"returns the tags of the post"
tags: [String] "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" "represents a request of any type"
type Request { type Request {
"id of the request" "id of the request"
id: ID! id: ID!
"type of the request"
requestType: RequestType! "Id of the user who sended the request"
sender: User!
"Id of the user who received the request"
receiver: User!
"type of the request"
requestType: RequestType!
} }
"represents a chatroom" "represents a chatroom"
type ChatRoom { type ChatRoom {
"the members of the chatroom" "the members of the chatroom"
members: [User!] members: [User!]
"return a specfic range of messages posted in the chat"
getMessages(first: Int, offset: Int): [String] "return a specfic range of messages posted in the chat"
"id of the chat" getMessages(first: Int, offset: Int): [String]
id: ID!
"id of the chat"
id: ID!
} }
"represents the type of vote performed on a post" "represents the type of vote performed on a post"
enum VoteType { enum VoteType {
UPVOTE UPVOTE
DOWNVOTE DOWNVOTE
} }
"represents the type of request that the user has received" "represents the type of request that the user has received"
enum RequestType { enum RequestType {
FRIENDREQUEST FRIENDREQUEST
GROUPINVITE GROUPINVITE
EVENTINVITE EVENTINVITE
} }

@ -0,0 +1,5 @@
@mixin gridPosition($rowStart, $rowEnd, $columnStart, $columnEnd)
grid-row-start: $rowStart
grid-row-end: $rowEnd
grid-column-start: $columnStart
grid-column-end: $columnEnd

@ -0,0 +1,82 @@
@import "vars"
@import "mixins"
body
font-family: Arial, serif
button
border: 2px solid $cPrimary
margin-top: 0.125em
padding: 0.125em
background-color: $cPrimary
color: $cPrimarySurface
font-weight: bold
transition-duration: 0.25s
button:hover
background-color: lighten($cPrimary, 10%)
cursor: pointer
button:active
background-color: darken($cPrimary, 5%)
box-shadow: inset 0.25em 0.25em 0.1em rgba(0, 0, 0, 0.25)
.stylebar
@include gridPosition(1, 2, 1, 4)
display: grid
grid-template: 100% /25% 50% 25%
background-color: $cPrimary
color: $cPrimarySurface
h1
@include gridPosition(1, 2, 1, 2)
text-align: center
margin: auto
#content
grid-template: 7.5% 92.5% / 25% 50% 25%
display: grid
width: 100%
height: 100%
#friendscontainer
@include gridPosition(2, 3, 1, 2)
background-color: $cPrimaryBackground
#feedcontainer
@include gridPosition(2, 3, 2, 3)
background-color: $cSecondaryBackground
.postinput
margin: 0.5em
input
width: 100%
border-radius: 0.25em
border: 1px solid $cPrimary
padding: 0.125em
height: 2em
button.submitbutton
border-radius: 0.25em
height: 2em
.feeditem
background-color: $cPrimaryBackground
min-height: 2em
margin: 0.5em
padding: 0.25em
border-radius: 0.25em
.itemhead
align-items: flex-start
.title, .handle, .date
margin: 0.125em
.title
font-weight: bold
.handle, .date
color: $cInactiveText
.handle a
text-decoration: none
color: $cInactiveText
font-style: normal
.handle a:hover
text-decoration: underline

@ -0,0 +1,5 @@
$cPrimaryBackground: #fff
$cSecondaryBackground: #ddd
$cInactiveText: #555
$cPrimary: #0d6b14
$cPrimarySurface: #fff

@ -1,10 +1,55 @@
import {Router} from "express"; import {Router} from "express";
import {Server} from "socket.io";
import Route from "../lib/Route";
const router = Router(); /**
* Class for the home route.
*/
class HomeRoute extends Route {
/**
* Constructor, creates new router.
*/
constructor() {
super();
this.router = Router();
this.configure();
}
/* GET home page. */ /**
router.get("/", (req, res) => { * Asynchronous init for socket.io.
res.render("home"); * @param io - the io instance
}); */
public async init(io: Server) {
this.io = io;
}
export default router; /**
* Destroys the instance by dereferencing the router and resolver.
*/
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 async resolver(req: any, res: any): Promise<object> {
return {
// TODO: Define grapql resolvers
};
}
/**
* Configures the route.
*/
private configure() {
this.router.get("/", (req, res) => {
res.render("home");
});
}
}
export default HomeRoute;

@ -1,21 +1,43 @@
/**
* @author Trivernis
* @remarks
*
* Taken from {@link https://github.com/Trivernis/whooshy}
*/
import {Router} from "express"; import {Router} from "express";
import {Server} from "socket.io"; import {Server} from "socket.io";
import homeRouter from "./home"; import HomeRoute from "./home";
const homeRoute = new HomeRoute();
/**
* Namespace to manage the routes of the server.
* Allows easier assignments of graphql endpoints, socket.io connections and routers when
* used with {@link Route}.
*/
namespace routes { namespace routes {
export const router = Router(); export const router = Router();
router.use("/", homeRouter); 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 const resolvers = async (request: any, response: any): Promise<object> => { export const resolvers = async (request: any, response: any): Promise<object> => {
return { return homeRoute.resolver(request, response);
};
}; };
// tslint:disable-next-line:no-empty /**
export const ioListeners = (io: Server) => { * Assigns the io listeners or namespaces to the routes
* @param io
*/
export const ioListeners = async (io: Server) => {
await homeRoute.init(io);
}; };
} }

@ -20,7 +20,8 @@ CREATE TABLE IF NOT EXISTS posts (
CREATE TABLE IF NOT EXISTS votes ( CREATE TABLE IF NOT EXISTS votes (
user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, 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 'upvote'
); );
CREATE TABLE IF NOT EXISTS events ( CREATE TABLE IF NOT EXISTS events (

@ -0,0 +1,3 @@
ALTER TABLE IF EXISTS votes
ADD COLUMN IF NOT EXISTS vote_type varchar(8) DEFAULT 'upvote',
ALTER COLUMN vote_type SET DEFAULT 'upvote';

@ -1,5 +0,0 @@
html
head
title Greenvironment Network
body
h1 Greenvironment

@ -0,0 +1,13 @@
div#feedcontainer
div.postinput
input(type=text placeholder='Post something')
button.submitbutton Submit
div.feeditem
div.itemhead
span.title Testuser
span.handle
a(href='#') @testuser
span.date 23.09.19 10:07
p.text
| Example Test text.
| This is a test

@ -0,0 +1 @@
div#friendscontainer

@ -0,0 +1,9 @@
html
head
title Greenvironment Network
include ../includes/head
body
div#content
include stylebar
include feed
include friends

@ -0,0 +1,2 @@
div.stylebar
h1 Greenvironment

@ -0,0 +1 @@
link(rel='stylesheet' href='stylesheets/style.css')
Loading…
Cancel
Save