From 92146cc2a4e79959a41712721703d969f1c899d8 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 29 Aug 2019 11:02:27 +0200 Subject: [PATCH 1/6] added sql table creation and config --- src/config.yaml | 6 +++++ src/index.ts | 2 -- src/sql/create-tables.sql | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/config.yaml create mode 100644 src/sql/create-tables.sql diff --git a/src/config.yaml b/src/config.yaml new file mode 100644 index 0000000..536f82f --- /dev/null +++ b/src/config.yaml @@ -0,0 +1,6 @@ +database: + url: localhost + port: 54332 + user: greenvironment + password: greendev + database: greenvironment diff --git a/src/index.ts b/src/index.ts index bd59c0e..b4265bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,3 @@ import App from "./app"; const app = new App(); - -// TODO: init and start diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql new file mode 100644 index 0000000..046541f --- /dev/null +++ b/src/sql/create-tables.sql @@ -0,0 +1,49 @@ +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name varchar(128) NOT NULL, + password varchar(1024) NOT NULL, + email varchar(128) UNIQUE NOT NULL, + greenpoints INTEGER DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS feed_items ( + id BIGSERIAL PRIMARY KEY, + upvotes INTEGER DEFAULT 0, + downvotes INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT now(), + content text, + author SERIAL REFERENCES users (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS votes ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + item_id BIGSERIAL REFERENCES feed_items (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS events ( + id BIGSERIAL PRIMARY KEY, + time TIMESTAMP, + owner SERIAL REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS event_members ( + event BIGSERIAL REFERENCES events (id), + member SERIAL REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS chats ( + id BIGSERIAL PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS chat_messages ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + author SERIAL REFERENCES users (id) ON DELETE SET NULL, + content VARCHAR(1024) NOT NULL, + created_at TIMESTAMP DEFAULT now(), + PRIMARY KEY (chat, author, created_at) +); + +CREATE TABLE IF NOT EXISTS chat_members ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + member SERIAL REFERENCES users (id) ON DELETE CASCADE +); From 6fb002e79be492d1e889565393a3fe52c4760d78 Mon Sep 17 00:00:00 2001 From: RandomUser27 Date: Thu, 29 Aug 2019 12:30:17 +0200 Subject: [PATCH 2/6] GraphQl --- src/public/graphql/schema.graphql | 121 ++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index e69de29..35862f3 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -0,0 +1,121 @@ +type Query { + getUser(userId: Id): User + getPost(postId: Id): Post + getChat(chatId: Id): Chat + getRequest(requestId: Id): Request +} + +type Mutation { + #_____Post_____ + #"Upvote/downvote a Post" + vote(postId: id!, type: [VoteType!]!): Bool + #"Report the post" + report(postId: id!): Bool + #_____Request_____ + #"if u accept a request" + acceptRequest(requestId: id!): bool + #_____ChatRoom_____ + #"send a assage in a Chatroom" + sendMassage(chatId: Id!, massage: String!): bool +} + +type User { + #"ProfilPicture of the User" + profilPicture: String! + #"name from the User" + name: String! + #"unique name from the User" + handle: String! + #"Id from the User" + userId: ID! + #"Counted number of al Posts he posted" + numberOfPosts: int + #"show the posts of the User" + getAllPosts(first: int, offset: int): [Post] + #"Date when he/she Created the account" + JoinedDate: Date! + #"all chats for the mainsite(left)" + pinnedChats: [Chats] + #"all friends of the user" + friends: [User] + #"all request for groupChats/friends/events" + friends: [Request] +} + +type Post { + #"PicturePath" + picture: String + #"posted Text" + text: String + #"upvotes from Post" + upvotes: Int! + #"downvotes from Post" + downvotes: Int! + #"The AuthorID of the Post" + author: User! + #"Creationdate of the Post" + creationDate: Date! + #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" + alreadyVoted: [VoteType] + #"with tags u can find the post in a research" + tags: [String] +} + +type Search { + #"u can find posts with a name(text) or posted time" + findPost(first: int, offset: int, text: String!, postedDate: Date): [Post] + #"u can find a User with his Name or handle" + findUser(first: int, offset: int, name: String!, handle: string!): [User] +} + +type Request { + #"RequestId" + requestId: Id! + #"RequestType" + requestType: [RequestType!]! +} + +type ChatRoom { + #"Every User in the room" + members: [Users!] + #"Get the Chat" + getMessages(first: int, offset: int): [String] + #"chatId" + chatId: Id! +} + +enum VoteType { + UPDATE + DOWNVOTE +} + +enum RequestType { + FRIENDREQUEST + GROUPINVITE + EVENTINVITE +} + +#type Event { +#} + +#type Comment { +# #"CommentId" +# commentId: Id! +# #"text from the Comment" +# text: String +# #"PostId" +# postId: Id +# #"Upvotes of the comment" +# upvotes: int! +# #"Downvotes of the comment" +# downvotes: int! +# #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" +# alreadyVoted: [VoteType] +# #"Creationdate of the Post" +# creationDate: Date! +#Comment +# #"Upvote/downvote a Comment" +# vote(CommentId: id!, type: [VoteType!]!): Bool +# #"Report the Comment" +# report(CommentId: id!): Bool +#} From b7d10454ab23e286827ed63d2081c8972fc461f3 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 29 Aug 2019 13:21:11 +0200 Subject: [PATCH 3/6] Changed tables and graphql schema --- src/public/graphql/schema.graphql | 149 +++++++++++++----------------- src/sql/create-tables.sql | 16 +++- 2 files changed, 76 insertions(+), 89 deletions(-) diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index 35862f3..f5feb67 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -1,121 +1,100 @@ type Query { - getUser(userId: Id): User - getPost(postId: Id): Post - getChat(chatId: Id): Chat - getRequest(requestId: Id): Request + "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 { - #_____Post_____ - #"Upvote/downvote a Post" - vote(postId: id!, type: [VoteType!]!): Bool - #"Report the post" - report(postId: id!): Bool - #_____Request_____ - #"if u accept a request" - acceptRequest(requestId: id!): bool - #_____ChatRoom_____ - #"send a assage in a Chatroom" - sendMassage(chatId: Id!, massage: String!): bool + "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 + "send a message in a Chatroom" + sendMessage(chatId: ID!, content: String!): Boolean } +"represents a single user account" type User { - #"ProfilPicture of the User" - profilPicture: String! - #"name from the User" + "url for the Profile picture of the User" + profilePicture: String! + "name of the User" name: String! - #"unique name from the User" + "unique identifier name from the User" handle: String! - #"Id from the User" - userId: ID! - #"Counted number of al Posts he posted" - numberOfPosts: int - #"show the posts of the User" - getAllPosts(first: int, offset: int): [Post] - #"Date when he/she Created the account" - JoinedDate: Date! - #"all chats for the mainsite(left)" - pinnedChats: [Chats] - #"all friends of the user" + "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" - friends: [Request] + "all request for groupChats/friends/events" + requests: [Request] } +"represents a single user post" type Post { - #"PicturePath" + "returns the path to the posts picture if it has one" picture: String - #"posted Text" + "returns the text of the post" text: String - #"upvotes from Post" + "upvotes of the Post" upvotes: Int! - #"downvotes from Post" + "downvotes of the Post" downvotes: Int! - #"The AuthorID of the Post" + "the user that is the author of the Post" author: User! - #"Creationdate of the Post" - creationDate: Date! - #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" - alreadyVoted: [VoteType] - #"with tags u can find the post in a research" + "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] } -type Search { - #"u can find posts with a name(text) or posted time" - findPost(first: int, offset: int, text: String!, postedDate: Date): [Post] - #"u can find a User with his Name or handle" - findUser(first: int, offset: int, name: String!, handle: string!): [User] -} - +"represents a request of any type" type Request { - #"RequestId" - requestId: Id! - #"RequestType" - requestType: [RequestType!]! + "id of the request" + id: ID! + "type of the request" + requestType: RequestType! } +"represents a chatroom" type ChatRoom { - #"Every User in the room" - members: [Users!] - #"Get the Chat" - getMessages(first: int, offset: int): [String] - #"chatId" - chatId: 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 { - UPDATE + UPVOTE DOWNVOTE } +"represents the type of request that the user has received" enum RequestType { FRIENDREQUEST GROUPINVITE EVENTINVITE } - -#type Event { -#} - -#type Comment { -# #"CommentId" -# commentId: Id! -# #"text from the Comment" -# text: String -# #"PostId" -# postId: Id -# #"Upvotes of the comment" -# upvotes: int! -# #"Downvotes of the comment" -# downvotes: int! -# #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" -# alreadyVoted: [VoteType] -# #"Creationdate of the Post" -# creationDate: Date! -#Comment -# #"Upvote/downvote a Comment" -# vote(CommentId: id!, type: [VoteType!]!): Bool -# #"Report the Comment" -# report(CommentId: id!): Bool -#} diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 046541f..0d02b2f 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -1,23 +1,26 @@ CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name varchar(128) NOT NULL, + handle varchar(128) UNIQUE NOT NULL, password varchar(1024) NOT NULL, email varchar(128) UNIQUE NOT NULL, - greenpoints INTEGER DEFAULT 0 + greenpoints INTEGER DEFAULT 0, + joined_at TIMESTAMP DEFAULT now() ); -CREATE TABLE IF NOT EXISTS feed_items ( +CREATE TABLE IF NOT EXISTS posts ( id BIGSERIAL PRIMARY KEY, upvotes INTEGER DEFAULT 0, downvotes INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT now(), content text, - author SERIAL REFERENCES users (id) ON DELETE CASCADE + author SERIAL REFERENCES users (id) ON DELETE CASCADE, + type varchar(16) NOT NULL ); CREATE TABLE IF NOT EXISTS votes ( user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES feed_items (id) ON DELETE CASCADE + item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS events ( @@ -47,3 +50,8 @@ CREATE TABLE IF NOT EXISTS chat_members ( chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, member SERIAL REFERENCES users (id) ON DELETE CASCADE ); + +CREATE TABLE IF NOT EXISTS user_friends ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE +); From 0dcd590b809053cfe7b972fd870520b5e0ab95e5 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 2 Sep 2019 09:41:59 +0200 Subject: [PATCH 4/6] Database Connection - added QueryHelper - added DAO - added `default-config.yaml` - added auto-copying of the `default-config.yaml` to a `config.yaml` on startup if no config exists --- .gitignore | 3 +- CHANGELOG.md | 4 ++ gulpfile.js | 9 +++- package-lock.json | 13 ++--- package.json | 2 + src/config.yaml | 6 --- src/default-config.yaml | 11 ++++ src/index.ts | 15 +++++- src/lib/DAO.ts | 19 +++++++ src/lib/QueryHelper.ts | 116 ++++++++++++++++++++++++++++++++++++++++ src/lib/globals.ts | 25 +++++++++ tslint.json | 7 ++- 12 files changed, 212 insertions(+), 18 deletions(-) delete mode 100644 src/config.yaml create mode 100644 src/default-config.yaml create mode 100644 src/lib/DAO.ts create mode 100644 src/lib/QueryHelper.ts create mode 100644 src/lib/globals.ts diff --git a/.gitignore b/.gitignore index 331e819..f043384 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules/ npm-debug.log test/*.log dist -.idea \ No newline at end of file +.idea +config.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e86b8..ca6704a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added + +- Connection to Postgres Database +- Graphql Schema +- default-config file and generation of config file on startup diff --git a/gulpfile.js b/gulpfile.js index e474b8c..c1abd43 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -34,9 +34,14 @@ function compileSass() { .pipe(dest('dist/public/stylesheets')); } -task('default', series(clearDist, compileTypescript, minifyJs, compileSass)); +function moveRemaining() { + return src(['src/**/*', '!src/**/*.ts', '!src/**/*.sass', '!src/**/*.js']) + .pipe(dest('dist')); +} + +task('default', series(clearDist, compileTypescript, minifyJs, compileSass, moveRemaining)); task('watch', () => { watch('src/public/stylesheets/sass/**/*.sass', compileSass); watch('**/*.js', minifyJs); watch('**/*.ts', compileTypescript); -}); \ No newline at end of file +}); diff --git a/package-lock.json b/package-lock.json index ae10b13..212f9d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -137,6 +137,11 @@ "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==", "dev": true }, + "@types/js-yaml": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==" + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -370,7 +375,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -1776,8 +1780,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esutils": { "version": "2.0.3", @@ -3815,7 +3818,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -5913,8 +5915,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", diff --git a/package.json b/package.json index 2041d5a..ae3c9f0 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "typescript": "^3.5.3" }, "dependencies": { + "@types/js-yaml": "^3.12.1", "@types/winston": "^2.4.4", "connect-pg-simple": "^6.0.0", "cookie-parser": "^1.4.4", @@ -50,6 +51,7 @@ "fs-extra": "^8.1.0", "graphql": "^14.4.2", "graphql-import": "^0.7.1", + "js-yaml": "^3.13.1", "pg": "^7.12.1", "socket.io": "^2.2.0", "winston": "^3.2.1" diff --git a/src/config.yaml b/src/config.yaml deleted file mode 100644 index 536f82f..0000000 --- a/src/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -database: - url: localhost - port: 54332 - user: greenvironment - password: greendev - database: greenvironment diff --git a/src/default-config.yaml b/src/default-config.yaml new file mode 100644 index 0000000..6b7f17d --- /dev/null +++ b/src/default-config.yaml @@ -0,0 +1,11 @@ +# database connection info +database: + host: + port: + user: + password: + database: + +# http server configuration +server: + port: diff --git a/src/index.ts b/src/index.ts index b4265bb..7e34c95 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,16 @@ +import * as fsx from "fs-extra"; import App from "./app"; -const app = new App(); +const configPath = "config.yaml"; +const defaultConfig = __dirname + "/default-config.yaml"; + +/** + * async main function wrapper. + */ +(async () => { + if (!(await fsx.pathExists(configPath))) { + await fsx.copy(defaultConfig, configPath); + } + const app = new App(); +})(); + diff --git a/src/lib/DAO.ts b/src/lib/DAO.ts new file mode 100644 index 0000000..7d60b87 --- /dev/null +++ b/src/lib/DAO.ts @@ -0,0 +1,19 @@ +import {Pool} from "pg"; +import globals from "./globals"; +import {QueryHelper} from "./QueryHelper"; + +const config = globals.config; + +export class DAO { + private queryHelper: QueryHelper; + constructor() { + 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, + }); + this.queryHelper = new QueryHelper(dbClient); + } +} diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts new file mode 100644 index 0000000..7f22fb5 --- /dev/null +++ b/src/lib/QueryHelper.ts @@ -0,0 +1,116 @@ +import {Pool, PoolClient, QueryConfig, QueryResult} from "pg"; +import globals from "./globals"; + +const logger = globals.logger; + +export class SqlTransaction { + constructor(private client: PoolClient) { + } + + /** + * Begins the transaction. + */ + public async begin() { + return await this.client.query("BEGIN"); + } + + /** + * Commits the transaction + */ + public async commit() { + return await this.client.query("COMMIT"); + } + + /** + * Rolls back the transaction + */ + public async rollback() { + return await this.client.query("ROLLBACK"); + } + + /** + * Executes a query inside the transaction. + * @param query + */ + public async query(query: QueryConfig) { + return await this.client.query(query); + } + + /** + * Releases the client back to the pool. + */ + public async release() { + this.client.release(); + } +} + +export class QueryHelper { + private pool: Pool; + + constructor(pgPool: Pool) { + this.pool = pgPool; + } + + /** + * executes the sql query with values and returns all results. + * @param query + */ + public async all(query: QueryConfig): Promise { + const result = await this.query(query); + return result.rows; + } + + /** + * executes the sql query with values and returns the first result. + * @param query + */ + public async first(query: QueryConfig): Promise { + const result = await this.query(query); + if (result.rows && result.rows.length > 0) { + return result.rows[0]; + } + } + + /** + * Creates a new Transaction to be uses with error handling. + */ + public async createTransaction() { + const client: PoolClient = await this.pool.connect(); + return new SqlTransaction(client); + } + + /** + * Queries the database with error handling. + * @param query - the sql and values to execute + */ + private async query(query: QueryConfig): Promise { + try { + return await this.pool.query(query); + } catch (err) { + logger.debug(`Error on query "${query}".`); + logger.error(`Sql query failed: ${err}`); + logger.verbose(err.stack); + return { + rows: null, + }; + } + } +} + +/** + * Returns the parameterized value sql for inserting + * @param columnCount + * @param rowCount + * @param [offset] + */ +export function buildSqlParameters(columnCount: number, rowCount: number, offset?: number): string { + let sql = ""; + for (let i = 0; i < rowCount; i++) { + sql += "("; + for (let j = 0; j < columnCount; j++) { + sql += `$${(i * columnCount) + j + 1 + offset},`; + } + sql = sql.replace(/,$/, "") + "),"; + } + return sql.replace(/,$/, ""); +} diff --git a/src/lib/globals.ts b/src/lib/globals.ts new file mode 100644 index 0000000..ddd044a --- /dev/null +++ b/src/lib/globals.ts @@ -0,0 +1,25 @@ +import * as fsx from "fs-extra"; +import * as yaml from "js-yaml"; +import * as winston from "winston"; + +/** + * Defines global variables to be used. + */ +namespace globals { + export const config = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8")); + export const logger = winston.createLogger({ + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.timestamp(), + winston.format.colorize(), + winston.format.printf(({ level, message, label, timestamp }) => { + return `${timestamp} [${label}] ${level}: ${message}`; + }), + ), + }), + ], + }); +} + +export default globals; diff --git a/tslint.json b/tslint.json index 21b8db5..78800e0 100644 --- a/tslint.json +++ b/tslint.json @@ -18,11 +18,14 @@ "no-console": { "severity": "warning", "options": ["debug", "info", "log", "time", "timeEnd", "trace"] - } + }, + "no-namespace": false, + "no-internal-module": false, + "max-classes-per-file": false }, "jsRules": { "max-line-length": { "options": [120] } } -} \ No newline at end of file +} From 20d719801030bc554469460a184764f700430511 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 7 Sep 2019 23:31:41 +0200 Subject: [PATCH 5/6] Database and App init - added database table creation - added init method to DAO - added init method to App - added start method to App to start listening on the configured port --- src/app.ts | 29 ++++++++++++++++++++++++++++- src/default-config.yaml | 2 +- src/index.ts | 2 ++ src/lib/DAO.ts | 10 +++++++++- src/lib/QueryHelper.ts | 14 +++++++++++++- src/lib/globals.ts | 2 +- 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/app.ts b/src/app.ts index bdb3fa3..3eee246 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,16 +1,43 @@ import * as express from "express"; import * as http from "http"; import * as socketIo from "socket.io"; - +import {DAO} from "./lib/DAO"; +import globals from "./lib/globals"; class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; + public dao: DAO; constructor() { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); + this.dao = new DAO(); + } + + /** + * initializes everything that needs to be initialized asynchronous. + */ + public async init() { + await this.dao.init(); + this.app.all("/", (req, res) => { + res.send("WIP!"); + }); + } + + /** + * Starts the web server. + */ + public start() { + if (globals.config.server.port) { + globals.logger.info(`Starting server...`); + this.app.listen(globals.config.server.port); + globals.logger.info(`Server running on port ${globals.config.server.port}`); + } else { + globals.logger.error("No port specified in the config." + + "Please configure a port in the config.yaml."); + } } } diff --git a/src/default-config.yaml b/src/default-config.yaml index 6b7f17d..417ccda 100644 --- a/src/default-config.yaml +++ b/src/default-config.yaml @@ -8,4 +8,4 @@ database: # http server configuration server: - port: + port: 8080 diff --git a/src/index.ts b/src/index.ts index 7e34c95..4e814b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,5 +12,7 @@ const defaultConfig = __dirname + "/default-config.yaml"; await fsx.copy(defaultConfig, configPath); } const app = new App(); + await app.init(); + app.start(); })(); diff --git a/src/lib/DAO.ts b/src/lib/DAO.ts index 7d60b87..6349fa9 100644 --- a/src/lib/DAO.ts +++ b/src/lib/DAO.ts @@ -3,6 +3,7 @@ import globals from "./globals"; import {QueryHelper} from "./QueryHelper"; const config = globals.config; +const tableCreationFile = __dirname + "/../sql/create-tables.sql"; export class DAO { private queryHelper: QueryHelper; @@ -14,6 +15,13 @@ export class DAO { port: config.database.port, user: config.database.user, }); - this.queryHelper = new QueryHelper(dbClient); + this.queryHelper = new QueryHelper(dbClient, tableCreationFile); + } + + /** + * Initializes everything that needs to be initialized asynchronous. + */ + public async init() { + await this.queryHelper.createTables(); } } diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 7f22fb5..3431994 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -1,3 +1,4 @@ +import * as fsx from "fs-extra"; import {Pool, PoolClient, QueryConfig, QueryResult} from "pg"; import globals from "./globals"; @@ -47,10 +48,21 @@ export class SqlTransaction { export class QueryHelper { private pool: Pool; - constructor(pgPool: Pool) { + constructor(pgPool: Pool, private tableCreationFile?: string) { this.pool = pgPool; } + /** + * creates all tables needed if a filepath was given with the constructor + */ + public async createTables() { + if (this.tableCreationFile) { + logger.info("Creating nonexistent tables..."); + const tableSql = await fsx.readFile(this.tableCreationFile, "utf-8"); + await this.query({text: tableSql}); + } + } + /** * executes the sql query with values and returns all results. * @param query diff --git a/src/lib/globals.ts b/src/lib/globals.ts index ddd044a..a64aaf0 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -14,7 +14,7 @@ namespace globals { winston.format.timestamp(), winston.format.colorize(), winston.format.printf(({ level, message, label, timestamp }) => { - return `${timestamp} [${label}] ${level}: ${message}`; + return `${timestamp} ${level}: ${message}`; }), ), }), From 7dd351ddf317879b1bd5d28dada98635ec99e3d5 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 9 Sep 2019 10:05:35 +0200 Subject: [PATCH 6/6] Updated Readme --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fe1d0c9..79f8a5f 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -greenvironment-server +# greenvironment-server + +Server of the greenvironment social network. + +## Install + +You need to install a nodejs runtime to run the greenvironment server. +Then you need to install all requirements. To do so, open a terminal in the +greenvironment project folder and execute "npm i". You can build the project by +executing "gulp" in the terminal. To run the server you need +to execute "node ./dist".