Merge branch 'develop' of Software_Engineering_I/greenvironment-server into master

pull/5/head
Trivernis 5 years ago committed by Gitea
commit 086430f0ba

@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- event and eventCount to UserData gql interface
- joined field to Event gql type
- joined field to Group gql type
- rate limits with defaults of 10/min for `/upload` and 30/min for `/graphql`
- complexity limits for graphql queries that can be configured with the `api.maxQueryComplexity` option
### Removed
@ -39,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- default findUser param limit to 20
- only group admins can create group events
- config behaviour to use all files that reside in the ./config directory with the .toml format
- default response timeout from 2 minutes to 30 seconds
### Fixed

@ -3,17 +3,22 @@
# A connection uri string to the database
connectionUri = "sqlite://greenvironment.db"
# Configuration for the redis connection
[redis]
# A connection uri string to the redis server
connectionUri = "redis://localhost:6379"
# Configuration of the http server
[server]
# The port the server is running on
port = 8080
# Allow cross origin requests
cors = false
# The timeout for a server response
timeout = 30000
# Configuration for the sessions
[session]
@ -22,19 +27,50 @@ secret = "REPLACE WITH SAFE RANDOM GENERATED SECRET"
# The maximum age of a cookie. The age is reset with every request of the client
cookieMaxAge = 6048e+5 # 7 days
# Configuration for markdown rendering
[markdown]
# The plugins used in the markdown parser
plugins = ["markdown-it-emoji"]
# Configuration for logging
[logging]
# The loglevel
level = "info"
# Configuration for the frontend files
[frontend]
# The path to the angular index file. Its relative to the public path.
angularIndex = "index.html"
# The path to the public files
publicPath = "./public"
# Configuration for the api
[api]
# if graphiql should be enabled
graphiql = true
# The maximum complexity of queries
maxQueryComplexity = 5000
# Configuration for the api rate limit
[api.rateLimit]
# rate limit of /upload
[api.rateLimit.upload]
# The time in milliseconds before the rate limit is reset
expire = 60000
# The total number of calls allowed before rate limiting
total = 10
# rate limit of /graphql
[api.rateLimit.graphql]
# The time in milliseconds before the rate limit is reset
expire = 60000
# The total number of calls allowed before rate limiting
total = 30

@ -23,6 +23,7 @@
"@types/bluebird": "^3.5.27",
"@types/chai": "^4.2.7",
"@types/compression": "^1.0.1",
"@types/config": "^0.0.36",
"@types/connect-pg-simple": "^4.2.0",
"@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.6",
@ -32,12 +33,14 @@
"@types/express-session": "^1.15.14",
"@types/express-socket.io-session": "^1.3.2",
"@types/fs-extra": "^8.0.0",
"@types/graphql-query-complexity": "^0.2.1",
"@types/http-status": "^0.2.30",
"@types/js-yaml": "^3.12.1",
"@types/markdown-it": "0.0.9",
"@types/mocha": "^5.2.7",
"@types/node": "^12.7.12",
"@types/pg": "^7.11.0",
"@types/redis": "^2.8.14",
"@types/sequelize": "^4.28.5",
"@types/sharp": "^0.23.1",
"@types/socket.io": "^2.1.2",
@ -56,7 +59,6 @@
"typescript": "^3.7.2"
},
"dependencies": {
"@types/config": "^0.0.36",
"compression": "^1.7.4",
"config": "^3.2.4",
"connect-session-sequelize": "^6.0.0",
@ -65,11 +67,13 @@
"express": "^4.17.1",
"express-fileupload": "^1.1.6",
"express-graphql": "^0.9.0",
"express-limiter": "^1.6.1",
"express-session": "^1.16.2",
"express-socket.io-session": "^1.3.5",
"fs-extra": "^8.1.0",
"graphql": "^14.4.2",
"graphql-import": "^0.7.1",
"graphql-query-complexity": "^0.4.1",
"http-status": "^1.3.2",
"js-yaml": "^3.13.1",
"markdown-it": "^10.0.0",

@ -10,12 +10,17 @@ import sharedsession = require("express-socket.io-session");
import * as fsx from "fs-extra";
import {buildSchema} from "graphql";
import {importSchema} from "graphql-import";
import queryComplexity, {directiveEstimator, simpleEstimator} from "graphql-query-complexity";
import {IncomingMessage, ServerResponse} from "http";
import * as http from "http";
import * as httpStatus from "http-status";
import * as path from "path";
import {RedisClient} from "redis";
import * as redis from "redis";
import {Sequelize} from "sequelize-typescript";
import * as socketIo from "socket.io";
import * as socketIoRedis from "socket.io-redis";
import {query} from "winston";
import {resolver} from "./graphql/resolvers";
import dataaccess from "./lib/dataAccess";
import globals from "./lib/globals";
@ -23,6 +28,7 @@ import HomeRoute from "./routes/HomeRoute";
import {UploadRoute} from "./routes/UploadRoute";
const SequelizeStore = require("connect-session-sequelize")(session.Store);
const createLimiter: (...args: any) => any = require("express-limiter");
const logger = globals.logger;
/**
@ -40,6 +46,16 @@ class App {
*/
public io: socketIo.Server;
/**
* The instance of the redis client
*/
public redisClient: RedisClient;
/**
* The limiter for api requests
*/
public limiter: any;
/**
* An instance of the http server where the site is served
*/
@ -63,6 +79,8 @@ class App {
constructor(id?: number) {
this.id = id;
this.app = express();
this.redisClient = redis.createClient(null, null, {url: config.get("redis.connectionUri")});
this.limiter = createLimiter(this.app, this.redisClient);
this.server = new http.Server(this.app);
this.io = socketIo(this.server);
this.sequelize = new Sequelize(config.get("database.connectionUri"));
@ -102,6 +120,7 @@ class App {
this.io.use(sharedsession(appSession, {autoSave: true}));
logger.info("Configuring express app.");
this.server.setTimeout(config.get("server.timeout"));
this.app.set("views", path.join(__dirname, "views"));
this.app.set("view engine", "pug");
this.app.set("trust proxy", 1);
@ -146,17 +165,54 @@ class App {
await homeRoute.init(this.io);
this.app.use("/home", homeRoute.router);
this.limiter({
expire: config.get("api.rateLimit.upload.expire"),
lookup: ["connection.remoteAddress"],
method: "all",
onRateLimited: (req: IncomingMessage, res: any) => {
res.status(httpStatus.TOO_MANY_REQUESTS);
res.json({error: "Rate Limit Exceeded"});
},
path: "/upload",
total: config.get("api.rateLimit.upload.total"),
});
this.app.use("/upload", uploadRoute.router);
// listen for graphql requests
this.app.use("/graphql", graphqlHTTP((request, response) => {
this.limiter({
expire: config.get("api.rateLimit.graphql.expire"),
lookup: ["connection.remoteAddress"],
method: "all",
onRateLimited: (req: IncomingMessage, res: any) => {
res.status(httpStatus.TOO_MANY_REQUESTS);
res.json({errors: [{message: "Rate Limit Exceeded"}]});
},
path: "/graphql",
total: config.get("api.rateLimit.graphql.total"),
});
// @ts-ignore
this.app.use("/graphql", graphqlHTTP(async (request, response, {variables}) => {
return {
// @ts-ignore all
context: {session: request.session},
graphiql: true,
graphiql: config.get("api.graphiql"),
rootValue: resolver(request, response),
schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))),
validationRules: [
queryComplexity({
estimators: [
directiveEstimator(),
simpleEstimator(),
],
maximumComplexity: config.get("api.maxQueryComplexity"),
onComplete: (complexity: number) => {
logger.debug(`QueryComplexity: ${complexity}`);
},
variables,
}),
],
};
}));
// allow access to cluster information

@ -1,3 +1,12 @@
"a directive to assign a complexity to a query field"
directive @complexity(
"The complexity value for the field"
value: Int!,
"Optional multipliers"
multipliers: [String!]
) on FIELD_DEFINITION
type Query {
"returns the user object for a given user id or a handle (only one required)"
getUser(userId: ID, handle: String): User
@ -18,10 +27,10 @@ type Query {
getRequest(requestId: ID!): Request
"searches for users, groups, events, posts and returns a search result"
search(query: String!, first: Int = 20, offset: Int = 0): SearchResult!
search(query: String!, first: Int = 20, offset: Int = 0): SearchResult! @complexity(value: 1, multipliers: ["first"])
"returns the post filtered by the sort type with pagination."
getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post!]!
getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post!]! @complexity(value: 1, multipliers: ["first"])
"returns all activities"
getActivities: [Activity]
@ -121,25 +130,25 @@ interface UserData {
postCount: Int!
"returns a given number of posts of a user"
posts(first: Int=10, offset: Int=0): [Post!]!
posts(first: Int=10, offset: Int=0): [Post!]! @complexity(value: 1, multipliers: ["first"])
"creation date of the user account"
joinedAt: String!
"all friends of the user"
friends(first: Int=10, offset: Int=0): [User!]!
friends(first: Int=10, offset: Int=0): [User!]! @complexity(value: 1, multipliers: ["first"])
"The number of friends the user has"
friendCount: Int!
"The groups the user has joined"
groups(first: Int=10, offset: Int=0): [Group!]!
groups(first: Int=10, offset: Int=0): [Group!]! @complexity(value: 1, multipliers: ["first"])
"The number of groups the user has joined"
groupCount: Int!
"The events the user is participating in"
events(first: Int=10, offset: Int=0): [Event!]!
events(first: Int=10, offset: Int=0): [Event!]! @complexity(value: 1, multipliers: ["first"])
"The number of events the user is participating in"
eventCount: Int!
@ -169,7 +178,7 @@ type User implements UserData{
numberOfPosts: Int!
"returns a given number of posts of a user"
posts(first: Int=10, offset: Int): [Post!]!
posts(first: Int=10, offset: Int): [Post!]! @complexity(value: 1, multipliers: ["first"])
"the number of posts the user has created"
postCount: Int!
@ -178,7 +187,7 @@ type User implements UserData{
joinedAt: String!
"all friends of the user"
friends(first: Int=10, offset: Int=0): [User!]!
friends(first: Int=10, offset: Int=0): [User!]! @complexity(value: 1, multipliers: ["first"])
"The number of friends the user has"
friendCount: Int!
@ -187,13 +196,13 @@ type User implements UserData{
points: Int!
"the groups the user has joined"
groups(first: Int=10, offset: Int=0): [Group!]!
groups(first: Int=10, offset: Int=0): [Group!]! @complexity(value: 1, multipliers: ["first"])
"The numbef of groups the user has joined"
groupCount: Int!
"The events the user is participating in"
events(first: Int=10, offset: Int=0): [Event!]!
events(first: Int=10, offset: Int=0): [Event!]! @complexity(value: 1, multipliers: ["first"])
"The number of events the user is participating in"
eventCount: Int!
@ -213,7 +222,7 @@ type Profile implements UserData {
email: String!
"returns the chatrooms the user joined."
chats(first: Int=10, offset: Int): [ChatRoom]
chats(first: Int=10, offset: Int): [ChatRoom] @complexity(value: 1, multipliers: ["first"])
"the count of the users chats"
chatCount: Int!
@ -231,13 +240,13 @@ type Profile implements UserData {
postCount: Int!
"returns a given number of posts of a user"
posts(first: Int=10, offset: Int): [Post!]!
posts(first: Int=10, offset: Int): [Post!]! @complexity(value: 1, multipliers: ["first"])
"creation date of the user account"
joinedAt: String!
"all friends of the user"
friends(first: Int=10, offset: Int=0): [User!]!
friends(first: Int=10, offset: Int=0): [User!]! @complexity(value: 1, multipliers: ["first"])
"The number of friends the user has"
friendCount: Int!
@ -255,13 +264,13 @@ type Profile implements UserData {
createdGroups: [Group!]!
"all groups the user has joined"
groups(first: Int=10, offset: Int=0): [Group!]!
groups(first: Int=10, offset: Int=0): [Group!]! @complexity(value: 1, multipliers: ["first"])
"The numbef of groups the user has joined"
groupCount: Int!
"The events the user is participating in"
events(first: Int=10, offset: Int=0): [Event!]!
events(first: Int=10, offset: Int=0): [Event!]! @complexity(value: 1, multipliers: ["first"])
"The number of events the user is participating in"
eventCount: Int!
@ -335,10 +344,10 @@ type ChatRoom {
namespace: String
"the members of the chatroom"
members(first: Int=10, offset: Int=0): [User!]!
members(first: Int=10, offset: Int=0): [User!]! @complexity(value: 1, multipliers: ["first"])
"return a specfic range of messages posted in the chat"
messages(first: Int = 10, offset: Int): [ChatMessage!]!
messages(first: Int = 10, offset: Int): [ChatMessage!]! @complexity(value: 1, multipliers: ["first"])
"id of the chat"
id: ID!
@ -375,16 +384,16 @@ type Group {
creator: User!
"all admins of the group"
admins(first: Int=10, offset: Int=0): [User!]!
admins(first: Int=10, offset: Int=0): [User!]! @complexity(value: 1, multipliers: ["first"])
"the members of the group with pagination"
members(first: Int = 10, offset: Int = 0): [User!]!
members(first: Int = 10, offset: Int = 0): [User!]! @complexity(value: 1, multipliers: ["first"])
"the groups chat"
chat: ChatRoom!
"the events of the group"
events(first: Int=10, offset: Int=0): [Event!]!
events(first: Int=10, offset: Int=0): [Event!]! @complexity(value: 1, multipliers: ["first"])
"If the user with the specified id has joined the group"
joined(userId: Int): Boolean!
@ -404,7 +413,7 @@ type Event {
group: Group!
"The participants of the event."
participants(first: Int=10, offset: Int=0): [User!]!
participants(first: Int=10, offset: Int=0): [User!]! @complexity(value: 1, multipliers: ["first"])
"Returns if the user with the specified id has joined the event"
joined(userId: Int): Boolean

File diff suppressed because it is too large Load Diff

@ -152,6 +152,13 @@
dependencies:
"@types/node" "*"
"@types/graphql-query-complexity@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@types/graphql-query-complexity/-/graphql-query-complexity-0.2.1.tgz#5166c7f32b6cd0a24f1aad5e00ca513b82b7f0e0"
integrity sha512-PxYhF92UFagAl9UIep8seEUd9j18JardL9ZM9tOfP02fWot9ZlkBYYGFwSZ7fRE6HTva/Yr4BQem7b4P/TgDPA==
dependencies:
graphql-query-complexity "*"
"@types/http-status@^0.2.30":
version "0.2.30"
resolved "https://registry.yarnpkg.com/@types/http-status/-/http-status-0.2.30.tgz#b43a1e1673b6ed9b5a28e8647862b51b6473634d"
@ -212,6 +219,13 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/redis@^2.8.14":
version "2.8.14"
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.14.tgz#2ed46d0f923f7ccd63fbe73a46a1241e606cf716"
integrity sha512-255dzsOLJdXFHBio9/aMHGozNkoiBUgc+g2nlNjbTSp5qcAlmpm4Z6Xs3pKOBLNIKdZbA2BkUxWvYSIwKra0Yw==
dependencies:
"@types/node" "*"
"@types/sequelize@^4.28.5":
version "4.28.6"
resolved "https://registry.yarnpkg.com/@types/sequelize/-/sequelize-4.28.6.tgz#01d2f1d3781cc34448cd63c2fd97bdb0612b15de"
@ -1745,6 +1759,11 @@ express-graphql@^0.9.0:
http-errors "^1.7.3"
raw-body "^2.4.1"
express-limiter@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/express-limiter/-/express-limiter-1.6.1.tgz#70ede144f9e875e3c7e120644018a6f7784bda71"
integrity sha512-w/Xz/FIHuAOIVIUeHSe6g2rSYTqCSKA9WFLO2CxX15BzEAK+avp7HoYd7pu/M2tEp5E/to253+4x8vQ6WcTJkQ==
express-session@^1.16.2:
version "1.17.0"
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.0.tgz#9b50dbb5e8a03c3537368138f072736150b7f9b3"
@ -2261,6 +2280,13 @@ graphql-import@^0.7.1:
lodash "^4.17.4"
resolve-from "^4.0.0"
graphql-query-complexity@*, graphql-query-complexity@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/graphql-query-complexity/-/graphql-query-complexity-0.4.1.tgz#06ad49de617da0d74c8196fb4a641349f104552d"
integrity sha512-Uo87hNlnJ5jwoWBkVYITbJpTrlCVwgfG5Wrfel0K1/42G+3xvud31CpsprAwiSpFIP+gCqttAx7OVmw4eTqLQQ==
dependencies:
lodash.get "^4.4.2"
graphql@^14.4.2, graphql@^14.5.3:
version "14.5.8"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.8.tgz#504f3d3114cb9a0a3f359bbbcf38d9e5bf6a6b3c"
@ -3028,6 +3054,11 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"

Loading…
Cancel
Save