Merge branch 'develop' of Software_Engineering_I/greenvironment-server into max-dev
commit
9629e2de71
@ -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".
|
||||
|
@ -0,0 +1,11 @@
|
||||
# database connection info
|
||||
database:
|
||||
host:
|
||||
port:
|
||||
user:
|
||||
password:
|
||||
database:
|
||||
|
||||
# http server configuration
|
||||
server:
|
||||
port: 8080
|
@ -0,0 +1,27 @@
|
||||
import {Pool} from "pg";
|
||||
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;
|
||||
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, tableCreationFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes everything that needs to be initialized asynchronous.
|
||||
*/
|
||||
public async init() {
|
||||
await this.queryHelper.createTables();
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
import * as fsx from "fs-extra";
|
||||
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, 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
|
||||
*/
|
||||
public async all(query: QueryConfig): Promise<any[]> {
|
||||
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<any> {
|
||||
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<QueryResult|{rows: any}> {
|
||||
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(/,$/, "");
|
||||
}
|
@ -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} ${level}: ${message}`;
|
||||
}),
|
||||
),
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export default globals;
|
@ -0,0 +1,100 @@
|
||||
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]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
"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 {
|
||||
"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]
|
||||
}
|
||||
|
||||
"represents a request of any type"
|
||||
type Request {
|
||||
"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!
|
||||
}
|
||||
|
||||
"represents the type of vote performed on a post"
|
||||
enum VoteType {
|
||||
UPVOTE
|
||||
DOWNVOTE
|
||||
}
|
||||
|
||||
"represents the type of request that the user has received"
|
||||
enum RequestType {
|
||||
FRIENDREQUEST
|
||||
GROUPINVITE
|
||||
EVENTINVITE
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
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,
|
||||
joined_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
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,
|
||||
type varchar(16) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS votes (
|
||||
user_id SERIAL REFERENCES users (id) ON DELETE CASCADE,
|
||||
item_id BIGSERIAL REFERENCES posts (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
|
||||
);
|
||||
|
||||
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
|
||||
);
|
Loading…
Reference in New Issue