API changes and error pages

- added pagination for most list types
- added count for pagination list
- added 404 and 500 error page
pull/2/head
Trivernis 5 years ago
parent 5478f70098
commit b17fca2590

1
.gitignore vendored

@ -8,3 +8,4 @@ dist
config.yaml
sqz-force
greenvironment.db
logs

@ -15,3 +15,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- session management
- Sequelize models and integration
- Sequelize-typescript integration
- error pages
- pagination for most list types

65
package-lock.json generated

@ -2387,6 +2387,14 @@
"resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz",
"integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg=="
},
"file-stream-rotator": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.5.5.tgz",
"integrity": "sha512-XzvE1ogpxUbARtZPZLICaDRAeWxoQLFMKS3ZwADoCQmurKEwuDD2jEfDVPm/R1HeKYsRYEl9PzVIezjQ3VTTPQ==",
"requires": {
"moment": "^2.11.2"
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@ -2651,7 +2659,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -2672,12 +2681,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2692,17 +2703,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -2819,7 +2833,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -2831,6 +2846,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -2845,6 +2861,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -2852,12 +2869,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -2876,6 +2895,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2956,7 +2976,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -2968,6 +2989,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -3053,7 +3075,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -3089,6 +3112,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -3108,6 +3132,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -3151,12 +3176,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -5042,6 +5069,11 @@
}
}
},
"object-hash": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
"integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA=="
},
"object-is": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz",
@ -7685,6 +7717,17 @@
"winston-transport": "^4.3.0"
}
},
"winston-daily-rotate-file": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.2.1.tgz",
"integrity": "sha512-ETNkdkMsf05HMg0kgkmTkA9GC6u6fFrat4mUVmx9XLCdgBoQL+iLuzbNUTWQxCVhlJ/w7MzsQfkU7bGf49NDbA==",
"requires": {
"file-stream-rotator": "^0.5.5",
"object-hash": "^1.3.0",
"triple-beam": "^1.3.0",
"winston-transport": "^4.2.0"
}
},
"winston-transport": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz",

@ -73,6 +73,7 @@
"sequelize-typescript": "^1.0.0",
"socket.io": "^2.2.0",
"sqlite3": "^4.1.0",
"winston": "^3.2.1"
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.2.1"
}
}

@ -1,6 +1,7 @@
import * as compression from "compression";
import * as cookieParser from "cookie-parser";
import * as cors from "cors";
import {Request, Response} from "express";
import * as express from "express";
import * as graphqlHTTP from "express-graphql";
import * as session from "express-session";
@ -9,6 +10,7 @@ import * as fsx from "fs-extra";
import {buildSchema} from "graphql";
import {importSchema} from "graphql-import";
import * as http from "http";
import * as httpStatus from "http-status";
import * as path from "path";
import {Sequelize} from "sequelize-typescript";
import * as socketIo from "socket.io";
@ -84,6 +86,14 @@ class App {
schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))),
};
}));
this.app.use((req: any, res: Response) => {
res.status(httpStatus.NOT_FOUND);
res.render("errors/404.pug", {url: req.url});
});
this.app.use((err, req: Request, res: Response) => {
res.status(httpStatus.INTERNAL_SERVER_ERROR);
res.render("errors/500.pug");
});
}
/**

@ -7,13 +7,16 @@ server:
port: 8080
cors: false
# configuration of sessions
session:
secret: REPLACE WITH SAFE RANDOM GENERATED SECRET
cookieMaxAge: 604800000 # 7 days
# configuration of the markdown parser
markdown:
plugins:
- 'markdown-it-emoji'
# configuration for logging
logging:
level: info

@ -21,7 +21,7 @@ type Query {
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]
findUser(first: Int, offset: Int, name: String, handle: String): [User]
"returns the post filtered by the sort type with pagination."
getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post]
@ -62,16 +62,16 @@ type Mutation {
sendMessage(chatId: ID!, content: String!): ChatMessage
"create the post"
createPost(content: String!): Post
createPost(content: String!): Post!
"delete the post for a given post id"
deletePost(postId: ID!): Boolean
deletePost(postId: ID!): Boolean!
"Creates a chat between the user (and optional an other user)"
createChat(members: [ID!]): ChatRoom
createChat(members: [ID!]): ChatRoom!
"Creates a new group with a given name and additional members"
createGroup(name: String!, members: [ID!]): Group
createGroup(name: String!, members: [ID!]): Group!
"Joins a group with the given id"
joinGroup(id: ID!): Group
@ -108,23 +108,35 @@ interface UserData {
"Id of the User"
id: ID!
"the total number of posts the user posted"
numberOfPosts: Int
"DEPRECATED! the total number of posts the user posted"
numberOfPosts: Int!
"the number of posts the user has created"
postCount: Int!
"returns a given number of posts of a user"
posts(first: Int=10, offset: Int): [Post]
posts(first: Int=10, offset: Int=0): [Post]
"creation date of the user account"
joinedAt: String!
"all friends of the user"
friends: [User]
friends(first: Int=10, offset: Int=0): [User]
"The number of friends the user has"
friendCount: Int!
"The groups the user has joined"
groups(first: Int=10, offset: Int=0): [Group]
"The numbef of groups the user has joined"
groupCount: Int!
"the points of the user"
points: Int
points: Int!
"the levels of the user depending on the points"
level: Int
level: Int!
}
"represents a single user account"
@ -142,25 +154,34 @@ type User implements UserData{
id: ID!
"the total number of posts the user posted"
numberOfPosts: Int
numberOfPosts: Int!
"returns a given number of posts of a user"
posts(first: Int=10, offset: Int): [Post]
"the number of posts the user has created"
postCount: Int!
"creation date of the user account"
joinedAt: String!
"all friends of the user"
friends: [User]
friends(first: Int=10, offset: Int=0): [User]
"The number of friends the user has"
friendCount: Int!
"the points of the user"
points: Int
points: Int!
"the groups the user has joined"
groups: [Group]
groups(first: Int=10, offset: Int=0): [Group]
"The numbef of groups the user has joined"
groupCount: Int!
"the levels of the user depending on the points"
level: Int
level: Int!
}
type Profile implements UserData {
@ -176,6 +197,9 @@ type Profile implements UserData {
"returns the chatrooms the user joined."
chats(first: Int=10, offset: Int): [ChatRoom]
"the count of the users chats"
chatCount: Int!
"unique identifier name from the User"
handle: String!
@ -183,37 +207,46 @@ type Profile implements UserData {
id: ID!
"the total number of posts the user posted"
numberOfPosts: Int
numberOfPosts: Int!
"the number of posts the user has created"
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!]!
"creation date of the user account"
joinedAt: String!
"all friends of the user"
friends: [User]
friends(first: Int=10, offset: Int=0): [User!]!
"The number of friends the user has"
friendCount: Int!
"all sent request for groupChats/friends/events"
sentRequests: [Request]
sentRequests: [Request!]!
"all received request for groupChats/friends/events"
receivedRequests: [Request]
receivedRequests: [Request!]!
"all groups the user is an admin of"
administratedGroups: [Group]
administratedGroups: [Group!]!
"all groups the user has created"
createdGroups: [Group]
createdGroups: [Group!]!
"all groups the user has joined"
groups: [Group]
groups(first: Int=10, offset: Int=0): [Group!]!
"The numbef of groups the user has joined"
groupCount: Int!
"the points of the user"
points: Int
points: Int!
"the levels of the user depending on the points"
level: Int
level: Int!
}
"represents a single user post"
@ -266,7 +299,7 @@ type ChatRoom {
namespace: String
"the members of the chatroom"
members: [User!]
members(first: Int=10, offset: Int=0): [User!]
"return a specfic range of messages posted in the chat"
messages(first: Int = 10, offset: Int, containing: String): [ChatMessage]!
@ -306,7 +339,7 @@ type Group {
creator: User
"all admins of the group"
admins: [User]!
admins(first: Int=10, offset: Int=0): [User]!
"the members of the group with pagination"
members(first: Int = 10, offset: Int = 0): [User]!
@ -315,7 +348,7 @@ type Group {
chat: ChatRoom
"the events of the group"
events: [Event!]!
events(first: Int=10, offset: Int=0): [Event!]!
}
type Event {
@ -332,7 +365,7 @@ type Event {
group: Group!
"The participants of the event."
participants: [User!]!
participants(first: Int=10, offset: Int=0): [User!]!
}
"represents the type of vote performed on a post"

@ -9,6 +9,7 @@ import {EventEmitter} from "events";
import * as fsx from "fs-extra";
import * as yaml from "js-yaml";
import * as winston from "winston";
require('winston-daily-rotate-file');
const configPath = "config.yaml";
const defaultConfig = __dirname + "/../default-config.yaml";
@ -27,6 +28,7 @@ if (!(fsx.pathExistsSync(configPath))) {
*/
namespace globals {
export const config = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8"));
// @ts-ignore
export const logger = winston.createLogger({
transports: [
new winston.transports.Console({
@ -39,6 +41,20 @@ namespace globals {
),
level: config.logging.level,
}),
// @ts-ignore
new (winston.transports.DailyRotateFile)({
dirname: "logs",
filename: "gv-%DATE%.log",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(({level, message, timestamp}) => {
return `${timestamp} ${level}: ${message}`;
}),
),
json: false,
maxFiles: "7d",
zippedArchive: true,
}),
],
});
export const internalEmitter = new EventEmitter();

@ -29,6 +29,8 @@ export class Event extends Model<Event> {
}
public async participants({first, offset}: {first: number, offset: number}): Promise<User[]> {
return await this.$get("rParticipants") as User[];
const limit = first || 10;
offset = offset || 0;
return await this.$get("rParticipants", {limit, offset}) as User[];
}
}

@ -40,8 +40,10 @@ export class Group extends Model<Group> {
return await this.$get("rCreator") as User;
}
public async admins(): Promise<User[]> {
return await this.$get("rAdmins") as User[];
public async admins({first, offset}: { first: number, offset: number }): Promise<User[]> {
const limit = first || 10;
offset = offset || 0;
return await this.$get("rAdmins", {limit, offset}) as User[];
}
public async members({first, offset}: { first: number, offset: number }): Promise<User[]> {
@ -54,7 +56,9 @@ export class Group extends Model<Group> {
return await this.$get("rChat") as ChatRoom;
}
public async events(): Promise<Event[]> {
return await this.$get("rEvents") as Event[];
public async events({first, offset}: { first: number, offset: number }): Promise<Event[]> {
const limit = first || 10;
offset = offset || 0;
return await this.$get("rEvents", {limit, offset}) as Event[];
}
}

@ -92,62 +92,156 @@ export class User extends Model<User> {
@UpdatedAt
public readonly updatedAt!: Date;
/**
* The name of the user
*/
public get name(): string {
return this.getDataValue("username");
}
/**
* The date the user joined the network
*/
public get joinedAt(): Date {
return this.getDataValue("createdAt");
}
/**
* The points of the user
*/
public get points(): number {
return this.rankpoints;
}
/**
* The level of the user which is the points divided by 100
*/
public get level(): number {
return Math.ceil(this.rankpoints / 100);
}
public async friends(): Promise<User[]> {
return await this.$get("rFriendOf") as User[];
/**
* All friends of the user
* @param first
* @param offset
*/
public async friends({first, offset}: {first: number, offset: number}): Promise<User[]> {
const limit = first || 10;
offset = offset || 0;
return await this.$get("rFriendOf", {limit, offset}) as User[];
}
public async chats(): Promise<ChatRoom[]> {
return await this.$get("rChats") as ChatRoom[];
/**
* The total number of the users friends.
*/
public async friendCount(): Promise<number> {
return this.$count("rFriends");
}
/**
* The chats the user has joined
* @param first
* @param offset
*/
public async chats({first, offset}: {first: number, offset: number}): Promise<ChatRoom[]> {
const limit = first || 10;
offset = offset || 0;
return await this.$get("rChats", {limit, offset}) as ChatRoom[];
}
/**
* the number of chats the user has
*/
public async chatCount(): Promise<number> {
return this.$count("rChats");
}
/**
* All active requests the user ha ssent
*/
public async sentRequests(): Promise<Request[]> {
return await this.$get("rSentRequests") as Request[];
}
/**
* All requests the user has received
*/
public async receivedRequests(): Promise<Request[]> {
return await this.$get("rReceivedRequests") as Request[];
}
public async posts({first, offset}: {first: number, offset: number}): Promise<Post[]> {
return await this.$get("rPosts", {limit: first, offset}) as Post[];
const limit = first || 10;
offset = offset || 0;
return await this.$get("rPosts", {limit, offset}) as Post[];
}
/**
* @deprecated
* use {@link postCount} instead
*/
public async numberOfPosts(): Promise<number> {
return this.postCount();
}
/**
* number of posts the user created
*/
public async postCount(): Promise<number> {
return this.$count("rPosts");
}
/**
* Groups the user is the admin of
*/
public async administratedGroups(): Promise<Group[]> {
return await this.$get("rAdministratedGroups") as Group[];
}
/**
* Groups the user has created
*/
public async createdGroups(): Promise<Group[]> {
return await this.$get("rCreatedGroups") as Group[];
}
public async groups(): Promise<Group[]> {
return await this.$get("rGroups") as Group[];
/**
* Groups the user is a member of
* @param first
* @param offset
*/
public async groups({first, offset}: {first: number, offset: number}): Promise<Group[]> {
const limit = first || 10;
offset = offset || 0;
return await this.$get("rGroups", {limit, offset}) as Group[];
}
/**
* The number of groups the user has joined
*/
public async groupCount(): Promise<number> {
return this.$count("rGroups");
}
/**
* Events the user has joined
*/
public async events(): Promise<Event[]> {
return await this.$get("rEvents") as Event[];
}
/**
* The number of events the user is participating in.
*/
public async eventCount(): Promise<number> {
return this.$count("rEvents");
}
/**
* Denys a request the user has received
* @param sender
* @param type
*/
public async denyRequest(sender: number, type: RequestType) {
const request = await this.$get("rReceivedRequests",
{where: {senderId: sender, requestType: type}}) as Request[];
@ -156,6 +250,11 @@ export class User extends Model<User> {
}
}
/**
* Accepts a request the user has received
* @param sender
* @param type
*/
public async acceptRequest(sender: number, type: RequestType) {
const requests = await this.$get("rReceivedRequests",
{where: {senderId: sender, requestType: type}}) as Request[];
@ -173,6 +272,10 @@ export class User extends Model<User> {
}
}
/**
* Removes a user from the users friends
* @param friendId
*/
public async removeFriend(friendId: number) {
const friend = await User.findByPk(friendId);
if (friend) {

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

@ -1,108 +1,10 @@
@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)
#server-error
*
margin-left: auto
margin-right: auto
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
#input-login
margin-top: 1em
@include gridPosition(2,3,2,3)
grid-template: 7.5% 7.5% 7.5% 7.5% 72%/ 100%
display: grid
background-color: $cPrimaryBackground
input
margin: 0.25em
.loginButton
margin: 0.25em
#input-register
margin-top: 1em
@include gridPosition(2,3,2,3)
grid-template: 7.5% 7.5% 7.5% 7.5% 7.5% 7.5% 58%/ 100%
display: grid
background-color: $cPrimaryBackground
input
margin: 0.25em
.registerButton
margin: 0.25em
#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
code
font-size: 2em

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

@ -1,9 +1,9 @@
import {Router} from "express";
import {Namespace, Server} from "socket.io";
import dataaccess from "../lib/dataaccess";
import {ChatMessage, ChatRoom, Post, Request, User} from "../lib/models";
import globals from "../lib/globals";
import {InternalEvents} from "../lib/InternalEvents";
import {ChatMessage, ChatRoom, Post, Request, User} from "../lib/models";
import Route from "../lib/Route";
/**

@ -0,0 +1,12 @@
html
head
link(href="/stylesheets/style.css" rel="stylesheet" type="text/css")
body
div#server-error
div
h1 Page not found!
div
code 404
div
h1 The page "#{url}" was not found.

@ -0,0 +1,13 @@
html
head
link(href="/stylesheets/style.css" rel="stylesheet" type="text/css")
body
div#server-error
div
h1 Internal server error!
div
code 500
div
h2 Oops the server couldn't handle that.
div
p You might want to report this.
Loading…
Cancel
Save