From 9d26d2b923b2a6105764cfeb8fe7354345b2eebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Wed, 30 Jun 2021 18:18:53 +0200 Subject: [PATCH 01/24] Temporary change authentication When this is enabled, the website can be easily tested with multiple accounts --- src/config/passport.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config/passport.ts b/src/config/passport.ts index e9450378..74a78778 100644 --- a/src/config/passport.ts +++ b/src/config/passport.ts @@ -27,9 +27,10 @@ passport.use( _profile: unknown, done: (err: Error, user: User) => void ) => { - const responseUser = await fetch( - `${AUTH_SCH_URL}/api/profile?access_token=${accessToken}` - ).then(res => res.json()) + const randId = Math.random() * 100 | 0 + const responseUser = { + 'internal_id': randId+'', displayName: 'testuser' + randId, mail: randId+'test@test.net' + } const user = await User.query().findOne({ authSchId: responseUser.internal_id }) From 974060603850fcf67db1b59d6f65441717b2a9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Wed, 25 Aug 2021 15:48:32 +0200 Subject: [PATCH 02/24] Create column groups.kind --- .../20210825151547_add_group_kind_to_groups.ts | 16 ++++++++++++++++ src/components/groups/group.ts | 10 +++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 migrations/20210825151547_add_group_kind_to_groups.ts diff --git a/migrations/20210825151547_add_group_kind_to_groups.ts b/migrations/20210825151547_add_group_kind_to_groups.ts new file mode 100644 index 00000000..c688fc37 --- /dev/null +++ b/migrations/20210825151547_add_group_kind_to_groups.ts @@ -0,0 +1,16 @@ +import * as Knex from 'knex' + +export async function up(knex: Knex): Promise { + return knex.schema.alterTable('groups', table => { + table.enu('kind', ['classic', 'anonymous'], { + useNative: true, + enumName: 'group_kind' + }).defaultTo('classic') + }) +} + +export async function down(knex: Knex): Promise { + return knex.schema.alterTable('groups', table => { + table.dropColumn('kind') + }) +} diff --git a/src/components/groups/group.ts b/src/components/groups/group.ts index 598d2469..864e03a1 100644 --- a/src/components/groups/group.ts +++ b/src/components/groups/group.ts @@ -2,6 +2,11 @@ import { Model } from 'objection' import { User } from '../users/user' +export enum GroupKind { + classic = 'classic', + anonymous = 'anonymous' +} + export class Group extends Model { id!: number name: string @@ -18,6 +23,8 @@ export class Group extends Model { createdAt: Date maxAttendees: number + kind = GroupKind.classic + $beforeInsert(): void { this.createdAt = new Date() } @@ -63,7 +70,8 @@ export class Group extends Model { place: { type: 'string' }, startDate: { type: 'datetime' }, endDate: { type: 'datetime' }, - maxAttendees: { type: 'integer' } + maxAttendees: { type: 'integer' }, + kind: { type: 'string' } } } } From 7c4dc44af40517a6c945ff7f78e76b0bbbc1c1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Wed, 25 Aug 2021 18:06:20 +0200 Subject: [PATCH 03/24] Convert enum values to uppercase --- migrations/20210825151547_add_group_kind_to_groups.ts | 4 ++-- src/components/groups/group.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/migrations/20210825151547_add_group_kind_to_groups.ts b/migrations/20210825151547_add_group_kind_to_groups.ts index c688fc37..31ccc4e2 100644 --- a/migrations/20210825151547_add_group_kind_to_groups.ts +++ b/migrations/20210825151547_add_group_kind_to_groups.ts @@ -2,10 +2,10 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { return knex.schema.alterTable('groups', table => { - table.enu('kind', ['classic', 'anonymous'], { + table.enu('kind', ['CLASSIC', 'ANONYMOUS'], { useNative: true, enumName: 'group_kind' - }).defaultTo('classic') + }).defaultTo('CLASSIC') }) } diff --git a/src/components/groups/group.ts b/src/components/groups/group.ts index 864e03a1..a18e5809 100644 --- a/src/components/groups/group.ts +++ b/src/components/groups/group.ts @@ -3,8 +3,8 @@ import { Model } from 'objection' import { User } from '../users/user' export enum GroupKind { - classic = 'classic', - anonymous = 'anonymous' + classic = 'CLASSIC', + anonymous = 'ANONYMOUS' } export class Group extends Model { From 6c6e902f107c7c74568f851f88bf44a1800523f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Thu, 26 Aug 2021 20:35:35 +0200 Subject: [PATCH 04/24] Apply db changes for anonymous groups --- ...25160429_add_group_role_to_users_groups.ts | 31 +++++++++++++++++++ seeds/02_add_groups.ts | 16 ++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 migrations/20210825160429_add_group_role_to_users_groups.ts diff --git a/migrations/20210825160429_add_group_role_to_users_groups.ts b/migrations/20210825160429_add_group_role_to_users_groups.ts new file mode 100644 index 00000000..3de199df --- /dev/null +++ b/migrations/20210825160429_add_group_role_to_users_groups.ts @@ -0,0 +1,31 @@ +import * as Knex from 'knex' + +const ENUM_VALUES = ['OWNER', 'MEMBER', 'UNAPPROVED'] +const ENUM_CONFIG = { useNative: true, enumName: 'group_roles' } +export async function up(knex: Knex): Promise { + // create role + await knex.schema.alterTable('users_groups', (table) => + table + .enu('group_role', ENUM_VALUES, ENUM_CONFIG) + .defaultTo('MEMBER') + .notNullable() + ) + + // save owners + const ownerJoins = knex('users_groups') + .select('users_groups.id') + .join('groups', 'group_id', '=', 'groups.id') + .whereRaw('user_id = owner_id') + + await knex('users_groups') + .update('group_role', 'OWNER') + .where('id', 'in', ownerJoins) +} + +export async function down(knex: Knex): Promise { + // TODO: drop enum `group_roles` + + await knex.schema.alterTable('users_groups', (table) => { + table.dropColumn('group_role') + }) +} diff --git a/seeds/02_add_groups.ts b/seeds/02_add_groups.ts index 215d2480..64ddb727 100644 --- a/seeds/02_add_groups.ts +++ b/seeds/02_add_groups.ts @@ -23,7 +23,7 @@ export async function seed(knex: Knex): Promise { // Compose group - const startSeed = faker.datatype.number(500) * 1_000_000 * (faker.datatype.boolean()? -1 : 1) + const startSeed = faker.datatype.number(500) * 1_000_000 * (faker.datatype.boolean() ? -1 : 1) const maxTwoHours = faker.datatype.number(6600) * 1000 + 600_000 const ownerId = (i * groupsPerFloor + j) % 16 + 1 const groupId = (i * groupsPerFloor + j) + 1 @@ -32,31 +32,35 @@ export async function seed(knex: Knex): Promise { name: faker.company.catchPhrase(), tags: faker.random.words(faker.datatype.number(5)).split(' ').join(','), description, - startDate: (new Date( Date.now() + startSeed * (j + 1) )), - endDate: (new Date( Date.now() + startSeed * (j + 1) + maxTwoHours )), + startDate: (new Date(Date.now() + startSeed * (j + 1))), + endDate: (new Date(Date.now() + startSeed * (j + 1) + maxTwoHours)), room: i + startingFloor, doNotDisturb: (i * groupsPerFloor + j) % 16 == 1, maxAttendees: 100, createdAt: new Date(), - ownerId + ownerId, + kind: (i + j) % 3 === 0 ? 'CLASSIC' : 'ANONYMOUS' } console.log('\x1b[33m%s\x1b[0m', - `Group: #${groupId} ${group.name}, floor: ${group.room}, owner: ${group.ownerId}`) + `Group: #${groupId} ${group.name}, floor: ${group.room}, owner: ${ownerId}`) groupArray.push(group) // Connect groups and users const connectOwner = { userId: (i * groupsPerFloor + j) % 16 + 1, groupId: (i * groupsPerFloor + j) + 1, + groupRole: 'OWNER' } console.log(`Connect: owner #${connectOwner.userId} to #${connectOwner.groupId}`) connectArray.push(connectOwner) connectCountSum++ for (let k = 1; k < ownerId; ++k) { + const isUnapproved = group.kind == 'ANONYMOUS' && (k % 2 === 0 || k % 3 === 0) const connect = { userId: k, - groupId + groupId, + groupRole: isUnapproved ? 'UNAPPROVED' : 'MEMBER' } console.log(`Connect: user #${connect.userId} to #${connect.groupId}`) connectArray.push(connect) From 1efce8ec46bb7179790cb505ba856a9e3fbe9ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Thu, 26 Aug 2021 22:00:01 +0200 Subject: [PATCH 05/24] Assign groupRole to user when joining --- src/components/groups/group.middlewares.ts | 19 ++++++++++++++++--- src/components/groups/group.ts | 6 ++++-- src/components/groups/groupMember.ts | 9 +++++++++ src/components/groups/grouprole.ts | 8 ++++++++ src/components/users/groupOfUser.ts | 12 ++++++++++++ src/components/users/user.ts | 7 ++++--- 6 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/components/groups/groupMember.ts create mode 100644 src/components/groups/grouprole.ts create mode 100644 src/components/users/groupOfUser.ts diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index 173f3fdf..3b8a545e 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -6,18 +6,23 @@ import winston from 'winston' import { differenceInMinutes } from 'date-fns' import { RoleType, User } from '../users/user' -import { Group } from './group' +import { Group, GroupKind } from './group' import { asyncWrapper } from '../../util/asyncWrapper' import sendMessage from '../../util/sendMessage' import { sendEmail } from '../../util/sendEmail' +import { GroupRole } from './grouprole'; export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { const user = req.user as User const group = req.group + let role: GroupRole | null = null + // Join group if not already in it, and it's not closed or it's the owner who joins. // We only join the group if it is not full already - if (group.doNotDisturb && (user.id !== group.ownerId)){ + if (user.id == group.ownerId) { + role = GroupRole.owner + } else if (group.doNotDisturb) { sendMessage(res, 'Ez egy privát csoport!') } else if (group.users?.find(it => it.id === user.id)) { sendMessage(res, 'Már tagja vagy ennek a csoportnak!') @@ -26,11 +31,19 @@ export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: } else if (group.endDate < new Date()) { sendMessage(res, 'Ez a csoport már véget ért!') } else { + role = group.kind === GroupKind.anonymous ? GroupRole.unapproved : GroupRole.member + } + + if (role !== null) { await Group.relatedQuery('users') .for(group.id) - .relate(user.id) + .relate({ + id: user.id, + group_role: role // eslint-disable-line @typescript-eslint/camelcase + } as unknown) return next() } + res.redirect(`/groups/${req.params.id}`) }) diff --git a/src/components/groups/group.ts b/src/components/groups/group.ts index a18e5809..bbfd50ad 100644 --- a/src/components/groups/group.ts +++ b/src/components/groups/group.ts @@ -1,6 +1,7 @@ import { Model } from 'objection' import { User } from '../users/user' +import { GroupMember } from './groupMember' export enum GroupKind { classic = 'CLASSIC', @@ -19,7 +20,7 @@ export class Group extends Model { place?: string doNotDisturb: boolean ownerId: number - users: User[] + users: GroupMember[] createdAt: Date maxAttendees: number @@ -45,7 +46,8 @@ export class Group extends Model { // ManyToMany relation needs the `through` object to describe the join table. through: { from: 'users_groups.groupId', - to: 'users_groups.userId' + to: 'users_groups.userId', + extra: ['group_role'] }, to: 'users.id' } diff --git a/src/components/groups/groupMember.ts b/src/components/groups/groupMember.ts new file mode 100644 index 00000000..541ffe17 --- /dev/null +++ b/src/components/groups/groupMember.ts @@ -0,0 +1,9 @@ +import { User } from '../users/user' +import { GroupRole } from './grouprole' + +/** + * User with role + */ +export class GroupMember extends User { + groupRole: GroupRole +} diff --git a/src/components/groups/grouprole.ts b/src/components/groups/grouprole.ts new file mode 100644 index 00000000..0a9848ed --- /dev/null +++ b/src/components/groups/grouprole.ts @@ -0,0 +1,8 @@ +/** + * Role of user inside group +*/ +export enum GroupRole { + owner = 'OWNER', + member = 'MEMBER', + unapproved = 'UNAPPROVED' +} diff --git a/src/components/users/groupOfUser.ts b/src/components/users/groupOfUser.ts new file mode 100644 index 00000000..f73a8272 --- /dev/null +++ b/src/components/users/groupOfUser.ts @@ -0,0 +1,12 @@ +import { Group } from '../groups/group' +import { GroupRole } from '../groups/grouprole' + +export { Group, GroupRole } + + +/** + * Group class containing role of user + */ +export class GroupOfUser extends Group { + groupRole: GroupRole; +} diff --git a/src/components/users/user.ts b/src/components/users/user.ts index d0fd1cbc..ad0b2b21 100644 --- a/src/components/users/user.ts +++ b/src/components/users/user.ts @@ -1,6 +1,6 @@ import { Model } from 'objection' -import { Group } from '../groups/group' +import { Group, GroupOfUser } from './groupOfUser' export enum RoleType { ADMIN = 'ADMIN', @@ -17,7 +17,7 @@ export class User extends Model { role: RoleType floor: number wantEmail: boolean - groups: Group[] + groups: GroupOfUser[] static get tableName(): string { return 'users' } @@ -33,7 +33,8 @@ export class User extends Model { from: 'users.id', through: { from: 'users_groups.userId', - to: 'users_groups.groupId' + to: 'users_groups.groupId', + extra: ['group_role'] }, to: 'groups.id' } From 84aaffbfa784d7f6737191b66aebd1538a13a550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 04:36:02 +0200 Subject: [PATCH 06/24] Fix: req.group is not an instance of Group It lost the behaviour on a destruction. Refactored code to don't use destruction. --- src/components/groups/group.service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/groups/group.service.ts b/src/components/groups/group.service.ts index c5167037..7fb0c3b9 100644 --- a/src/components/groups/group.service.ts +++ b/src/components/groups/group.service.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction} from 'express' +import { Request, Response, NextFunction } from 'express' import { Group } from './group' import { User } from '../users/user' @@ -28,11 +28,11 @@ export const getGroup = asyncWrapper(async (req: Request, res: Response, next: N .withGraphFetched('users') if (group) { - // Getting raw description for /copy and /edit pages - if (/\/copy|\/edit/.test(req.path)) - req.group = group - else - req.group = { ...group, description: formatMdToSafeHTML(group.description) } as Group + req.group = group + // Except when getting raw description for /copy and /edit pages + if (!/\/copy|\/edit/.test(req.path)) { + req.group.description = formatMdToSafeHTML(group.description) + } next() } else { res.render('error/not-found') From 69b11307482cd62fc275f7a07a44228e73d3057d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 04:45:54 +0200 Subject: [PATCH 07/24] Add missing whitespaces --- src/components/groups/group.middlewares.ts | 40 +++++++++++----------- src/components/groups/group.routes.ts | 2 +- src/components/users/user.ts | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index 3b8a545e..98e9a821 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -10,7 +10,7 @@ import { Group, GroupKind } from './group' import { asyncWrapper } from '../../util/asyncWrapper' import sendMessage from '../../util/sendMessage' import { sendEmail } from '../../util/sendEmail' -import { GroupRole } from './grouprole'; +import { GroupRole } from './grouprole' export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { const user = req.user as User @@ -48,7 +48,7 @@ export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: }) export const sendEmailToOwner = asyncWrapper( - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const user = req.user as User const group = req.group @@ -71,15 +71,15 @@ export const leaveGroup = asyncWrapper(async (req: Request, res: Response, next: }) export const isMemberInGroup = -asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const kickableUser = await Group.relatedQuery('users').for(req.group.id) - .findOne({ userId: parseInt(req.params.userid) }) - if (kickableUser) { - next() - } else { - res.redirect('/not-found') - } -}) + asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { + const kickableUser = await Group.relatedQuery('users').for(req.group.id) + .findOne({ userId: parseInt(req.params.userid) }) + if (kickableUser) { + next() + } else { + res.redirect('/not-found') + } + }) export const kickMember = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { await Group.relatedQuery('users') @@ -91,7 +91,7 @@ export const kickMember = asyncWrapper(async (req: Request, res: Response, next: }) export const sendEmailToMember = asyncWrapper( - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const emailRecepient = await User.query().findOne({ id: req.params.userid }) sendEmail([emailRecepient], { subject: 'Kirúgtak egy csoportból!', @@ -175,12 +175,12 @@ function isValidHttpsUrl(str) { return false } // not catching bad top lvl domain (1 character) - const pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string - '(\\#[-a-z\\d_]*)?$','i') // fragment locator + const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i') // fragment locator // not allowing '(' and ')' // catching 1 character TLD @@ -224,7 +224,7 @@ export const validateGroup = (): ValidationChain[] => { .custom((value, { req }) => new Date(value).getTime() < new Date(req.body.endDate).getTime()) .withMessage('A kezdés nem lehet korábban, mint a befejezés') .custom((value, { req }) => - differenceInMinutes(new Date(req.body.endDate), new Date(value)) <= 5*60) + differenceInMinutes(new Date(req.body.endDate), new Date(value)) <= 5 * 60) .withMessage('A foglalás időtartama nem lehet hosszabb 5 óránál'), check('endDate', 'A befejezés időpontja kötelező') .exists({ checkFalsy: true, checkNull: true }), @@ -243,7 +243,7 @@ export const checkValidMaxAttendeeLimit = asyncWrapper( if (req.group.users.length > (req.body.maxAttendees || 100)) { res.status(400).json( { - errors: [{msg: 'Nem lehet kisebb a maximum jelenlét, mint a jelenlegi'}] + errors: [{ msg: 'Nem lehet kisebb a maximum jelenlét, mint a jelenlegi' }] } ) } else { diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 757c9021..6fdbd450 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -4,7 +4,7 @@ import { formatDistanceStrict } from 'date-fns' import huLocale from 'date-fns/locale/hu' -import { Request, Response, Router} from 'express' +import { Request, Response, Router } from 'express' import multer from 'multer' import { isAuthenticated } from '../../config/passport' diff --git a/src/components/users/user.ts b/src/components/users/user.ts index ad0b2b21..24121b74 100644 --- a/src/components/users/user.ts +++ b/src/components/users/user.ts @@ -53,7 +53,7 @@ export class User extends Model { name: { type: 'string', minLength: 1, maxLength: 255 }, authSchId: { type: 'string' }, floor: { type: ['integer', 'null'] }, - wantEmail: { type: 'boolean'} + wantEmail: { type: 'boolean' } } } } From fbf087222e8af69fa59996b94544ad0c42f6e9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 04:47:38 +0200 Subject: [PATCH 08/24] Implement api route to approve joining requests --- src/components/groups/group.middlewares.ts | 11 +++++++++++ src/components/groups/group.routes.ts | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index 98e9a821..52d09af5 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -81,6 +81,17 @@ export const isMemberInGroup = } }) +export const approveMember = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + await Group.relatedQuery('users') + .for(req.group.id) + // eslint-disable-next-line @typescript-eslint/camelcase + .patch({ group_role: GroupRole.member } as unknown) + .where('user_id', req.params.userid) + + next() + }) + export const kickMember = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { await Group.relatedQuery('users') .for(req.group.id) diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 6fdbd450..33722b3b 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -16,6 +16,7 @@ import { sendEmailToOwner, leaveGroup, isMemberInGroup, + approveMember, kickMember, sendEmailToMember, createICSEvent, @@ -89,6 +90,17 @@ router.post('/:id/leave', leaveGroup, (req, res) => res.redirect('/groups') ) + +router.post('/:id/approve/:userid', + isAuthenticated, + getGroup, + isGroupOwnerOrAdmin, + isMemberInGroup, + approveMember, + sendEmailToMember, + (req, res) => res.redirect(`/groups/${req.params.id}`) +) + router.post('/:id/kick/:userid', isAuthenticated, getGroup, From 033ff1f322329906bb01712b83c962c8053a4d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 04:59:14 +0200 Subject: [PATCH 09/24] Implement front end for the new role system subfeatures: - Hide the user's name if needed - Show approve button on unapproved users for moderators --- src/components/groups/group.routes.ts | 25 ++++++++++++--- src/components/groups/group.ts | 12 +++++++- src/components/groups/groupMember.ts | 10 +++++- src/components/users/groupOfTheUser.ts | 9 ++++++ src/components/users/groupOfUser.ts | 12 -------- src/components/users/user.ts | 6 ++-- views/group/show.pug | 42 ++++++++++++++++++-------- 7 files changed, 83 insertions(+), 33 deletions(-) create mode 100644 src/components/users/groupOfTheUser.ts delete mode 100644 src/components/users/groupOfUser.ts diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 33722b3b..0390e26e 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -26,6 +26,7 @@ import { checkValidMaxAttendeeLimit } from './group.middlewares' import { createGroup, getGroup, getGroups, updateGroup, removeGroup } from './group.service' +import { GroupRole } from './grouprole' const router = Router() @@ -68,11 +69,27 @@ router.get('/:id', checkIdParam, getGroup, (req, res) => { - const joined = req.group.users.some(u => u.id === (req.user as User).id) - const isOwner = req.group.ownerId === (req.user as User).id - const isAdmin = (req.user as User).role == RoleType.ADMIN + const { group } = req + const user = req.user as User + const userId = user.id + + const joined = group.users.some(u => u.id === userId) + const isOwner = group.ownerId === userId + const isAdmin = user.role == RoleType.ADMIN + const canSeeMembers = isOwner || isAdmin || group.canSeeMembers(userId) + const canModerate = isOwner || isAdmin res.render('group/show', { - group: req.group, joined, isOwner, format, DATE_FORMAT, isAdmin + group, + joined, + isOwner, + format, + DATE_FORMAT, + isAdmin, + canSeeMembers, + canModerate, + GroupRole, + userId, + userRole: group.users.find(x => x.id === userId)?.groupRole }) }) diff --git a/src/components/groups/group.ts b/src/components/groups/group.ts index bbfd50ad..6e09eba2 100644 --- a/src/components/groups/group.ts +++ b/src/components/groups/group.ts @@ -2,6 +2,7 @@ import { Model } from 'objection' import { User } from '../users/user' import { GroupMember } from './groupMember' +import { GroupRole } from './grouprole' export enum GroupKind { classic = 'CLASSIC', @@ -23,9 +24,18 @@ export class Group extends Model { users: GroupMember[] createdAt: Date maxAttendees: number - kind = GroupKind.classic + isApproved(userId: User['id']): boolean { + return this.users.some( + x => x.id === userId && x.groupRole !== GroupRole.unapproved) + } + + + canSeeMembers(userId: User['id']): boolean { + return this.kind !== GroupKind.anonymous || this.isApproved(userId) + } + $beforeInsert(): void { this.createdAt = new Date() } diff --git a/src/components/groups/groupMember.ts b/src/components/groups/groupMember.ts index 541ffe17..84404c2f 100644 --- a/src/components/groups/groupMember.ts +++ b/src/components/groups/groupMember.ts @@ -5,5 +5,13 @@ import { GroupRole } from './grouprole' * User with role */ export class GroupMember extends User { - groupRole: GroupRole + groupRole: GroupRole + + isOwner(): boolean { + return this.groupRole === GroupRole.owner + } + + isApproved(): boolean { + return this.groupRole !== GroupRole.unapproved + } } diff --git a/src/components/users/groupOfTheUser.ts b/src/components/users/groupOfTheUser.ts new file mode 100644 index 00000000..ec926a51 --- /dev/null +++ b/src/components/users/groupOfTheUser.ts @@ -0,0 +1,9 @@ +import { Group } from '../groups/group' +import { GroupRole } from '../groups/grouprole' + +/** + * Group with role + */ +export class GroupOfTheUser extends Group { + groupRole: GroupRole +} diff --git a/src/components/users/groupOfUser.ts b/src/components/users/groupOfUser.ts deleted file mode 100644 index f73a8272..00000000 --- a/src/components/users/groupOfUser.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Group } from '../groups/group' -import { GroupRole } from '../groups/grouprole' - -export { Group, GroupRole } - - -/** - * Group class containing role of user - */ -export class GroupOfUser extends Group { - groupRole: GroupRole; -} diff --git a/src/components/users/user.ts b/src/components/users/user.ts index 24121b74..485bfbf7 100644 --- a/src/components/users/user.ts +++ b/src/components/users/user.ts @@ -1,6 +1,6 @@ import { Model } from 'objection' -import { Group, GroupOfUser } from './groupOfUser' +import { GroupOfTheUser } from './groupOfTheUser' export enum RoleType { ADMIN = 'ADMIN', @@ -17,7 +17,7 @@ export class User extends Model { role: RoleType floor: number wantEmail: boolean - groups: GroupOfUser[] + groups: GroupOfTheUser[] static get tableName(): string { return 'users' } @@ -27,7 +27,7 @@ export class User extends Model { return { groups: { relation: Model.ManyToManyRelation, - modelClass: Group, + modelClass: GroupOfTheUser, join: { from: 'users.id', diff --git a/views/group/show.pug b/views/group/show.pug index 5b8731bf..378b4725 100644 --- a/views/group/show.pug +++ b/views/group/show.pug @@ -105,7 +105,7 @@ block content svg(xmlns='http://www.w3.org/2000/svg' alt="Más hely" aria-label="Más hely" fill='none' viewbox='0 0 24 24' stroke='currentColor' class='w-6 h-6') path(stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z') span(class='text-lg sm:text-xl')= group.place - + if startDate == endDate li(class='flex flex-row items-center space-x-2') //- Heroicon name: calendar @@ -154,17 +154,35 @@ block content div(class='flex flex-col items-start space-y-2 text-xl') each user in group.users div(class='w-full flex flex-row justify-between items-center ml-6 space-x-2') - a(href=`/users/${user.id}` class='lg:whitespace-no-wrap hover:text-blue-500')= user.name - if user.id === group.ownerId - span(class='px-3 py-1 text-sm text-white bg-purple-600 rounded-full') Szervező - else if isOwner || isAdmin - button(type='button' class='px-3 py-1 text-sm text-white btn btn-primary animate-hover cursor-pointer' onClick=`toggleModal( - '/groups/${group.id}/kick/${user.id}', - 'Biztosan ki akarod rúgni ${user.name}-t?', - 'Biztosan ki akarod rúgni ${user.name}-t? Ezt később nem tudod visszavonni!', - 'Kirúgás', - false)`) Kirúgás - + - nameVisible = canModerate || group.canSeeMembers(userId) || user.groupRole === GroupRole.owner || user.id === userId + if nameVisible + a(href=`/users/${user.id}` class='lg:whitespace-no-wrap hover:text-blue-500')= user.name + else + a(href="#" class='lg:whitespace-no-wrap hover:text-blue-500') Anonymous + div(class='flex space-x-1 flex-wrap justify-end') + case user.groupRole + when GroupRole.owner + span(class='px-3 py-1 text-sm text-white bg-purple-600 rounded-full') Szervező + when GroupRole.unapproved + if !canModerate || user.id === userId + span(class='px-3 py-1 text-sm text-white bg-gray-600 rounded-full') Jelentkező + else if canModerate && !groupEnded + button(type='button' class='px-3 py-1 text-sm text-white btn btn-primary bg-green-600 hover:bg-green-700 animate-hover cursor-pointer' onClick=`toggleModal( + '/groups/${group.id}/approve/${user.id}', + 'Biztosan fel akarod venni ${user.name}-t?', + 'Biztosan fel akarod venni ${user.name}-t? Ezt később nem tudod visszavonni!', + 'Felvétel', + false)`) Felvétel + when GroupRole.member + if !nameVisible + span(class='px-3 py-1 text-sm text-white bg-green-600 rounded-full') Csoporttag + if canModerate && user.groupRole !== GroupRole.owner + button(type='button' class='px-3 py-1 text-sm text-white btn btn-primary animate-hover cursor-pointer' onClick=`toggleModal( + '/groups/${group.id}/kick/${user.id}', + 'Biztosan ki akarod rúgni ${user.name}-t?', + 'Biztosan ki akarod rúgni ${user.name}-t? Ezt később nem tudod visszavonni!', + 'Kirúgás', + false)`) Kirúgás +modalTemplate() block scripts From ea9406a2c5d2c557e96bbd67c31e41bcaa17639e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 16:24:17 +0200 Subject: [PATCH 10/24] Rename GroupKind.Anonymous to GroupKind.Private --- migrations/20210825151547_add_group_kind_to_groups.ts | 2 +- seeds/02_add_groups.ts | 4 ++-- src/components/groups/group.middlewares.ts | 2 +- src/components/groups/group.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/migrations/20210825151547_add_group_kind_to_groups.ts b/migrations/20210825151547_add_group_kind_to_groups.ts index 31ccc4e2..f751bdef 100644 --- a/migrations/20210825151547_add_group_kind_to_groups.ts +++ b/migrations/20210825151547_add_group_kind_to_groups.ts @@ -2,7 +2,7 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { return knex.schema.alterTable('groups', table => { - table.enu('kind', ['CLASSIC', 'ANONYMOUS'], { + table.enu('kind', ['CLASSIC', 'PRIVATE'], { useNative: true, enumName: 'group_kind' }).defaultTo('CLASSIC') diff --git a/seeds/02_add_groups.ts b/seeds/02_add_groups.ts index 64ddb727..b7b3d0a7 100644 --- a/seeds/02_add_groups.ts +++ b/seeds/02_add_groups.ts @@ -39,7 +39,7 @@ export async function seed(knex: Knex): Promise { maxAttendees: 100, createdAt: new Date(), ownerId, - kind: (i + j) % 3 === 0 ? 'CLASSIC' : 'ANONYMOUS' + kind: (i + j) % 3 === 0 ? 'CLASSIC' : 'PRIVATE' } console.log('\x1b[33m%s\x1b[0m', `Group: #${groupId} ${group.name}, floor: ${group.room}, owner: ${ownerId}`) @@ -56,7 +56,7 @@ export async function seed(knex: Knex): Promise { connectCountSum++ for (let k = 1; k < ownerId; ++k) { - const isUnapproved = group.kind == 'ANONYMOUS' && (k % 2 === 0 || k % 3 === 0) + const isUnapproved = group.kind === 'PRIVATE' && (k % 2 === 0 || k % 3 === 0) const connect = { userId: k, groupId, diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index 52d09af5..d5902df2 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -31,7 +31,7 @@ export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: } else if (group.endDate < new Date()) { sendMessage(res, 'Ez a csoport már véget ért!') } else { - role = group.kind === GroupKind.anonymous ? GroupRole.unapproved : GroupRole.member + role = group.kind === GroupKind.private ? GroupRole.unapproved : GroupRole.member } if (role !== null) { diff --git a/src/components/groups/group.ts b/src/components/groups/group.ts index 6e09eba2..9a983121 100644 --- a/src/components/groups/group.ts +++ b/src/components/groups/group.ts @@ -6,7 +6,7 @@ import { GroupRole } from './grouprole' export enum GroupKind { classic = 'CLASSIC', - anonymous = 'ANONYMOUS' + private = 'PRIVATE' } export class Group extends Model { @@ -33,7 +33,7 @@ export class Group extends Model { canSeeMembers(userId: User['id']): boolean { - return this.kind !== GroupKind.anonymous || this.isApproved(userId) + return this.kind !== GroupKind.private || this.isApproved(userId) } $beforeInsert(): void { From ad1ca1fcb3d24c9c9e7edfc4cf00b50fef817908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 17:07:45 +0200 Subject: [PATCH 11/24] Add GroupKind selector to the newGroup form --- src/components/groups/group.middlewares.ts | 8 +++++++- src/components/groups/group.routes.ts | 2 ++ src/components/groups/group.service.ts | 5 +++-- views/group/new.pug | 13 +++++++++++++ views/group/show.pug | 6 ++++++ 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index d5902df2..6b68dd72 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -245,7 +245,13 @@ export const validateGroup = (): ValidationChain[] => { .isLength({ max: 500 }), check('maxAttendees', 'Legalább 1, maximum 100 fő vehet részt!') .optional({ checkFalsy: true }) - .isInt({ min: 1, max: 100 }) + .isInt({ min: 1, max: 100 }), + check('kind', 'A csoport típusa') + .isString() + .trim() + .default(GroupKind.classic) + .toUpperCase() + .isIn(Object.values(GroupKind)) ] } diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 0390e26e..4f6ea804 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -27,6 +27,7 @@ import { } from './group.middlewares' import { createGroup, getGroup, getGroups, updateGroup, removeGroup } from './group.service' import { GroupRole } from './grouprole' +import { GroupKind } from './group' const router = Router() @@ -87,6 +88,7 @@ router.get('/:id', isAdmin, canSeeMembers, canModerate, + GroupKind, GroupRole, userId, userRole: group.users.find(x => x.id === userId)?.groupRole diff --git a/src/components/groups/group.service.ts b/src/components/groups/group.service.ts index 7fb0c3b9..b8bfec5e 100644 --- a/src/components/groups/group.service.ts +++ b/src/components/groups/group.service.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express' -import { Group } from './group' +import { Group, GroupKind } from './group' import { User } from '../users/user' import { formatMdToSafeHTML } from '../../util/convertMarkdown' import { asyncWrapper } from '../../util/asyncWrapper' @@ -53,7 +53,8 @@ export const createGroup = asyncWrapper(async (req: Request, res: Response, next startDate: new Date(req.body.startDate), endDate: new Date(req.body.endDate), ownerId: (req.user as User).id, - maxAttendees: parseInt(req.body.maxAttendees) || 100 + maxAttendees: parseInt(req.body.maxAttendees) || 100, + kind: req.body.kind as GroupKind } ) diff --git a/views/group/new.pug b/views/group/new.pug index a7fceb91..c0194f7c 100644 --- a/views/group/new.pug +++ b/views/group/new.pug @@ -51,6 +51,19 @@ block content div(class='flex flex-col items-start') label(class='text-lg font-bold' for="pickerEnd") Befejezés ideje input(type="text", id="pickerEnd", name="endDate", required, data-input, class="flatpickr") + div + label(for="kind" class='text-lg font-bold') Csoport típusa + br + select(id="kind" name="kind" class='flatpickr') + option(value="CLASSIC" selected) Klasszikus + option(value="PRIVATE") Privát + ul + li + span(class='font-bold') Klasszikus - + span Bárki csatlakozhat a csoporthoz + li + span(class='font-bold') Privát - + span A csatlakozást meg kell erősítenie a szervezőnek. Külsősök számára a taglista rejtett. div label(for="desc" class='text-lg font-bold') Leírás textarea(name="description", id="desc") diff --git a/views/group/show.pug b/views/group/show.pug index 378b4725..e3c14941 100644 --- a/views/group/show.pug +++ b/views/group/show.pug @@ -89,6 +89,12 @@ block content path(stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z') span Nyílt sup(title='Szabad a csatlakozás') ? + if group.kind === GroupKind.private + li(class='flex flex-row items-center space-x-2') + //- Heroicon name: user-group + svg.h-6.w-6(xmlns='http://www.w3.org/2000/svg' fill='none' viewbox='0 0 24 24' stroke='currentColor') + path(stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z') + span Privát csoport li(class='flex flex-row items-center space-x-2') if group.room //- Heroicon name: office-building From ebd94d0cc2809edcdd34608370028ed2de07cae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 17:38:18 +0200 Subject: [PATCH 12/24] Send different email for private groups --- src/components/groups/group.middlewares.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index 6b68dd72..9db8e7c7 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -1,3 +1,4 @@ +import { Email } from './../../util/sendEmail' import { NextFunction, Request, Response } from 'express' import { check, ValidationChain } from 'express-validator' import { writeFileSync } from 'fs' @@ -53,12 +54,21 @@ export const sendEmailToOwner = asyncWrapper( const group = req.group const emailRecepient = await User.query().findOne({ id: group.ownerId }) - sendEmail([emailRecepient], { - subject: 'Csatlakoztak egy csoportodba!', - body: `${user.name} csatlakozott a(z) ${group.name} csoportodba!`, - link: `/groups/${group.id}`, - linkTitle: 'Csoport megtekintése' - }) + const emails: Record = { + [GroupKind.classic]: { + subject: 'Csatlakoztak egy csoportodba!', + body: `${user.name} csatlakozott a(z) ${group.name} csoportodba!`, + link: `/groups/${group.id}`, + linkTitle: 'Csoport megtekintése' + }, + [GroupKind.private]: { + subject: 'Csatlakoznának egy csoportodba!', + body: `${user.name} csatlakozna a(z) ${group.name} csoportodba!`, + link: `/groups/${group.id}`, + linkTitle: 'Csoport megtekintése' + } + } + sendEmail([emailRecepient], emails[group.kind]) next() }) export const leaveGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { From d73f54e92069b959ad39f7e0a8529dfe8a851819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 17:38:51 +0200 Subject: [PATCH 13/24] Implement GroupKind changing for group editing --- src/components/groups/group.routes.ts | 4 +++- src/components/groups/group.service.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 4f6ea804..4c66e95d 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -150,6 +150,7 @@ router.get('/:id/copy', name: req.group.name, description: req.group.description, tags: req.group.tags, + kind: req.group.kind, ROOMS }) ) @@ -173,7 +174,8 @@ router.get('/:id/edit', ROOMS, isEditing: true, groupId: req.group.id, - maxAttendees: req.group.maxAttendees + maxAttendees: req.group.maxAttendees, + kind: req.group.kind, }) ) diff --git a/src/components/groups/group.service.ts b/src/components/groups/group.service.ts index b8bfec5e..e0b29d7b 100644 --- a/src/components/groups/group.service.ts +++ b/src/components/groups/group.service.ts @@ -74,7 +74,8 @@ export const updateGroup = asyncWrapper(async (req: Request, res: Response, next doNotDisturb: !!req.body.doNotDisturb, startDate: new Date(req.body.startDate), endDate: new Date(req.body.endDate), - maxAttendees: parseInt(req.body.maxAttendees) || 100 + maxAttendees: parseInt(req.body.maxAttendees) || 100, + kind: req.body.kind }) .findById(req.params.id) .catch((err) => { From df5451cf0be38b6ee0428556b3de4efc5bd4ed7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 17:53:48 +0200 Subject: [PATCH 14/24] Implement GroupKind for group copy --- src/components/groups/group.routes.ts | 3 +++ views/group/new.pug | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 4c66e95d..912c040e 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -50,6 +50,7 @@ router.get('/new', isAuthenticated, (req, res) => start: (req.query?.start as string)?.split(' ')[0].slice(0, -3), end: (req.query?.end as string)?.split(' ')[0].slice(0, -3), roomId: req.query?.roomId, + GroupKind, ROOMS }) ) @@ -151,6 +152,7 @@ router.get('/:id/copy', description: req.group.description, tags: req.group.tags, kind: req.group.kind, + GroupKind, ROOMS }) ) @@ -176,6 +178,7 @@ router.get('/:id/edit', groupId: req.group.id, maxAttendees: req.group.maxAttendees, kind: req.group.kind, + GroupKind }) ) diff --git a/views/group/new.pug b/views/group/new.pug index c0194f7c..0bdfcc5e 100644 --- a/views/group/new.pug +++ b/views/group/new.pug @@ -55,8 +55,8 @@ block content label(for="kind" class='text-lg font-bold') Csoport típusa br select(id="kind" name="kind" class='flatpickr') - option(value="CLASSIC" selected) Klasszikus - option(value="PRIVATE") Privát + option(value=`${GroupKind.classic}` selected=(kind === GroupKind.classic || !kind)) Klasszikus + option(value=`${GroupKind.private}` selected=(kind === GroupKind.private)) Privát ul li span(class='font-bold') Klasszikus - From 787584db652f03512eb1215ec3b10144fd3bc1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 18:30:05 +0200 Subject: [PATCH 15/24] Show private groups on user profile when viewing self --- src/components/users/user.routes.ts | 7 ++++++- views/user/show.pug | 28 ++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/components/users/user.routes.ts b/src/components/users/user.routes.ts index 61e8bf98..ccc861b7 100644 --- a/src/components/users/user.routes.ts +++ b/src/components/users/user.routes.ts @@ -7,12 +7,17 @@ import { handleValidationError, checkIdParam } from '../../util/validators' import { RoleType, User } from './user' import { isSameUser } from './user.middlewares' import { getUser, updateRole, updateUser } from './user.service' +import { GroupKind } from '../groups/group' +import { GroupRole } from './../groups/grouprole' const router = Router() router.get('/:id', isAuthenticated, checkIdParam, getUser, (req, res) => res.render('user/show', { userToShow: req.userToShow, + GroupKind, + GroupRole, + userId: (req.user as User).id, ROLES: ROLES }) ) @@ -21,7 +26,7 @@ router.patch('/:id/role', requireRoles(RoleType.ADMIN), check('role') .isString() - .custom((input) => { + .custom((input) => { return [...ROLES.keys()] .some((element) => element == input) }) diff --git a/views/user/show.pug b/views/user/show.pug index 444ea92e..e372ea03 100644 --- a/views/user/show.pug +++ b/views/user/show.pug @@ -23,14 +23,19 @@ block content option(value=`${key}`, selected=key==userToShow.role) #{ROLES.get(key)} button(type="button", id="submitBtn", onclick=`updateRole(${userToShow.id})`, class='ml-2 btn btn-primary animate-hover') Módosítás - - const isUpcoming = (element) => element.endDate > Date.now(); - - const isOld = (element) => element.endDate <= Date.now() + - const isPublic = (group) => group.kind === GroupKind.classic + - const isVisible = (group) => isPublic(group) || group.groupRole === GroupRole.owner || userToShow.id === userId + - const isUpcoming = (group) => group.endDate > Date.now(); + - const isOld = (group) => group.endDate <= Date.now() + + - console.log(userToShow.groups.filter(isVisible)) div(class='space-y-2') - if userToShow.groups.some(isUpcoming) + - console.log('&###########################################x', userToShow.groups.filter(isVisible)) + if userToShow.groups.filter(isVisible).some(isUpcoming) h3(class='mb-1 text-lg') Közelgő csoportesemények div(class='grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4') - each group in userToShow.groups.filter(isUpcoming) + each group in userToShow.groups.filter(isVisible).filter(isUpcoming) a(href=`/groups/${group.id}` class='transition duration-300 ease-in-out transform hover:-translate-y-2') div(class='flex items-center justify-center p-6 border rounded-md dark:border-gray-500 hover:border-gray-400 animate-hover dark:hover:border-gray-300') div(class='flex flex-row items-center') @@ -39,11 +44,17 @@ block content //- Heroicon name: shield-check svg(xmlns="http://www.w3.org/2000/svg" alt="Saját esemény" aria-label="Saját esemény" viewbox="0 0 20 20" fill="currentColor" class="w-4 h-4 ml-1") path(fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd") + if !isPublic(group) + //- Heroicon name: eye-off + svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' alt="Privát csoport" aria-label="Privát csoport" viewbox='0 0 20 20' fill='currentColor') + path(fill-rule='evenodd' d='M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z' clip-rule='evenodd') + path(d='M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z') + - if userToShow.groups.some(isOld) + if userToShow.groups.filter(isVisible).some(isOld) h3(class='mb-2 text-lg') Elmúlt csoportesemények div(class='grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4') - each group in userToShow.groups.filter(isOld) + each group in userToShow.groups.filter(isVisible).filter(isOld) a(href=`/groups/${group.id}` class='transition duration-300 ease-in-out transform hover:-translate-y-2') div(class='flex items-center justify-center p-6 border rounded-md dark:border-gray-500 hover:border-gray-400 animate-hover dark:hover:border-gray-300') div(class='flex flex-row items-center') @@ -52,6 +63,11 @@ block content //- Heroicon name: shield-check svg(xmlns="http://www.w3.org/2000/svg" alt="Saját esemény" aria-label="Saját esemény" viewbox="0 0 20 20" fill="currentColor" class="w-4 h-4 ml-1") path(fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd") + if !isPublic(group) + //- Heroicon name: eye-off + svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' alt="Privát csoport" aria-label="Privát csoport" viewbox='0 0 20 20' fill='currentColor') + path(fill-rule='evenodd' d='M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z' clip-rule='evenodd') + path(d='M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z') block scripts script(src="/js/user.js") From 450357156ed88413099c2d544a285c89ae2449f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 18:42:23 +0200 Subject: [PATCH 16/24] Show event on the user's profile if the event is owned by the viewer --- views/user/show.pug | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/user/show.pug b/views/user/show.pug index e372ea03..f7fbeddc 100644 --- a/views/user/show.pug +++ b/views/user/show.pug @@ -24,7 +24,10 @@ block content button(type="button", id="submitBtn", onclick=`updateRole(${userToShow.id})`, class='ml-2 btn btn-primary animate-hover') Módosítás - const isPublic = (group) => group.kind === GroupKind.classic - - const isVisible = (group) => isPublic(group) || group.groupRole === GroupRole.owner || userToShow.id === userId + - const isOwner = (group) => group.groupRole === GroupRole.owner + - const viewingSelf = userToShow.id === userId + - const ownsGroup = (group) => group.ownerId === userId + - const isVisible = (group) => isPublic(group) || isOwner(group) || viewingSelf || ownsGroup(group) - const isUpcoming = (group) => group.endDate > Date.now(); - const isOld = (group) => group.endDate <= Date.now() From 858a174517bc31d38cd038a6ed0407ef7e3954ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 18:42:51 +0200 Subject: [PATCH 17/24] Remove a console.log --- views/user/show.pug | 2 -- 1 file changed, 2 deletions(-) diff --git a/views/user/show.pug b/views/user/show.pug index f7fbeddc..a9400a80 100644 --- a/views/user/show.pug +++ b/views/user/show.pug @@ -31,8 +31,6 @@ block content - const isUpcoming = (group) => group.endDate > Date.now(); - const isOld = (group) => group.endDate <= Date.now() - - console.log(userToShow.groups.filter(isVisible)) - div(class='space-y-2') - console.log('&###########################################x', userToShow.groups.filter(isVisible)) if userToShow.groups.filter(isVisible).some(isUpcoming) From d573f3b8d262da77d72570f28737749f5fa24e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Wed, 30 Jun 2021 18:18:53 +0200 Subject: [PATCH 18/24] Revert "Temporary change authentication" This reverts commit 9d26d2b923b2a6105764cfeb8fe7354345b2eebb. --- src/config/passport.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/config/passport.ts b/src/config/passport.ts index 74a78778..e9450378 100644 --- a/src/config/passport.ts +++ b/src/config/passport.ts @@ -27,10 +27,9 @@ passport.use( _profile: unknown, done: (err: Error, user: User) => void ) => { - const randId = Math.random() * 100 | 0 - const responseUser = { - 'internal_id': randId+'', displayName: 'testuser' + randId, mail: randId+'test@test.net' - } + const responseUser = await fetch( + `${AUTH_SCH_URL}/api/profile?access_token=${accessToken}` + ).then(res => res.json()) const user = await User.query().findOne({ authSchId: responseUser.internal_id }) From 8a33968f2a8143ee46d0d4d7cae815d4d665b5af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Fri, 27 Aug 2021 19:21:54 +0200 Subject: [PATCH 19/24] Rename GroupKind to GroupType --- ...20210825151547_add_group_kind_to_groups.ts | 6 +++--- seeds/02_add_groups.ts | 4 ++-- src/components/groups/group.middlewares.ts | 21 +++++++++++-------- src/components/groups/group.routes.ts | 14 ++++++------- src/components/groups/group.service.ts | 6 +++--- src/components/groups/group.ts | 8 +++---- src/components/users/user.routes.ts | 4 ++-- views/group/new.pug | 8 +++---- views/group/show.pug | 2 +- views/user/show.pug | 2 +- 10 files changed, 39 insertions(+), 36 deletions(-) diff --git a/migrations/20210825151547_add_group_kind_to_groups.ts b/migrations/20210825151547_add_group_kind_to_groups.ts index f751bdef..b7d4a5ba 100644 --- a/migrations/20210825151547_add_group_kind_to_groups.ts +++ b/migrations/20210825151547_add_group_kind_to_groups.ts @@ -2,15 +2,15 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { return knex.schema.alterTable('groups', table => { - table.enu('kind', ['CLASSIC', 'PRIVATE'], { + table.enu('type', ['CLASSIC', 'PRIVATE'], { useNative: true, - enumName: 'group_kind' + enumName: 'group_type' }).defaultTo('CLASSIC') }) } export async function down(knex: Knex): Promise { return knex.schema.alterTable('groups', table => { - table.dropColumn('kind') + table.dropColumn('type') }) } diff --git a/seeds/02_add_groups.ts b/seeds/02_add_groups.ts index b7b3d0a7..36674b1f 100644 --- a/seeds/02_add_groups.ts +++ b/seeds/02_add_groups.ts @@ -39,7 +39,7 @@ export async function seed(knex: Knex): Promise { maxAttendees: 100, createdAt: new Date(), ownerId, - kind: (i + j) % 3 === 0 ? 'CLASSIC' : 'PRIVATE' + type: (i + j) % 3 === 0 ? 'CLASSIC' : 'PRIVATE' } console.log('\x1b[33m%s\x1b[0m', `Group: #${groupId} ${group.name}, floor: ${group.room}, owner: ${ownerId}`) @@ -56,7 +56,7 @@ export async function seed(knex: Knex): Promise { connectCountSum++ for (let k = 1; k < ownerId; ++k) { - const isUnapproved = group.kind === 'PRIVATE' && (k % 2 === 0 || k % 3 === 0) + const isUnapproved = group.type === 'PRIVATE' && (k % 2 === 0 || k % 3 === 0) const connect = { userId: k, groupId, diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index 9db8e7c7..ce1b0c66 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -7,7 +7,7 @@ import winston from 'winston' import { differenceInMinutes } from 'date-fns' import { RoleType, User } from '../users/user' -import { Group, GroupKind } from './group' +import { Group, GroupType } from './group' import { asyncWrapper } from '../../util/asyncWrapper' import sendMessage from '../../util/sendMessage' import { sendEmail } from '../../util/sendEmail' @@ -32,7 +32,7 @@ export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: } else if (group.endDate < new Date()) { sendMessage(res, 'Ez a csoport már véget ért!') } else { - role = group.kind === GroupKind.private ? GroupRole.unapproved : GroupRole.member + role = group.type === GroupType.private ? GroupRole.unapproved : GroupRole.member } if (role !== null) { @@ -54,21 +54,21 @@ export const sendEmailToOwner = asyncWrapper( const group = req.group const emailRecepient = await User.query().findOne({ id: group.ownerId }) - const emails: Record = { - [GroupKind.classic]: { + const emails: Record = { + [GroupType.classic]: { subject: 'Csatlakoztak egy csoportodba!', body: `${user.name} csatlakozott a(z) ${group.name} csoportodba!`, link: `/groups/${group.id}`, linkTitle: 'Csoport megtekintése' }, - [GroupKind.private]: { + [GroupType.private]: { subject: 'Csatlakoznának egy csoportodba!', body: `${user.name} csatlakozna a(z) ${group.name} csoportodba!`, link: `/groups/${group.id}`, linkTitle: 'Csoport megtekintése' } } - sendEmail([emailRecepient], emails[group.kind]) + sendEmail([emailRecepient], emails[group.type]) next() }) export const leaveGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { @@ -256,12 +256,15 @@ export const validateGroup = (): ValidationChain[] => { check('maxAttendees', 'Legalább 1, maximum 100 fő vehet részt!') .optional({ checkFalsy: true }) .isInt({ min: 1, max: 100 }), - check('kind', 'A csoport típusa') + check('groupType', 'Hibás a csoport típusa') + .custom(x => { console.log(x); return true }) .isString() .trim() - .default(GroupKind.classic) + .optional() + .default(GroupType.classic) .toUpperCase() - .isIn(Object.values(GroupKind)) + .isIn(Object.values(GroupType)) + .withMessage(x => `${x}`) ] } diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 912c040e..d2991e96 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -27,7 +27,7 @@ import { } from './group.middlewares' import { createGroup, getGroup, getGroups, updateGroup, removeGroup } from './group.service' import { GroupRole } from './grouprole' -import { GroupKind } from './group' +import { GroupType } from './group' const router = Router() @@ -50,7 +50,7 @@ router.get('/new', isAuthenticated, (req, res) => start: (req.query?.start as string)?.split(' ')[0].slice(0, -3), end: (req.query?.end as string)?.split(' ')[0].slice(0, -3), roomId: req.query?.roomId, - GroupKind, + GroupType, ROOMS }) ) @@ -89,7 +89,7 @@ router.get('/:id', isAdmin, canSeeMembers, canModerate, - GroupKind, + GroupType, GroupRole, userId, userRole: group.users.find(x => x.id === userId)?.groupRole @@ -151,8 +151,8 @@ router.get('/:id/copy', name: req.group.name, description: req.group.description, tags: req.group.tags, - kind: req.group.kind, - GroupKind, + type: req.group.type, + GroupType, ROOMS }) ) @@ -177,8 +177,8 @@ router.get('/:id/edit', isEditing: true, groupId: req.group.id, maxAttendees: req.group.maxAttendees, - kind: req.group.kind, - GroupKind + type: req.group.type, + GroupType }) ) diff --git a/src/components/groups/group.service.ts b/src/components/groups/group.service.ts index e0b29d7b..00cc64e5 100644 --- a/src/components/groups/group.service.ts +++ b/src/components/groups/group.service.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express' -import { Group, GroupKind } from './group' +import { Group, GroupType } from './group' import { User } from '../users/user' import { formatMdToSafeHTML } from '../../util/convertMarkdown' import { asyncWrapper } from '../../util/asyncWrapper' @@ -54,7 +54,7 @@ export const createGroup = asyncWrapper(async (req: Request, res: Response, next endDate: new Date(req.body.endDate), ownerId: (req.user as User).id, maxAttendees: parseInt(req.body.maxAttendees) || 100, - kind: req.body.kind as GroupKind + type: req.body.groupType as GroupType } ) @@ -75,7 +75,7 @@ export const updateGroup = asyncWrapper(async (req: Request, res: Response, next startDate: new Date(req.body.startDate), endDate: new Date(req.body.endDate), maxAttendees: parseInt(req.body.maxAttendees) || 100, - kind: req.body.kind + type: req.body.groupType }) .findById(req.params.id) .catch((err) => { diff --git a/src/components/groups/group.ts b/src/components/groups/group.ts index 9a983121..f9adcb30 100644 --- a/src/components/groups/group.ts +++ b/src/components/groups/group.ts @@ -4,7 +4,7 @@ import { User } from '../users/user' import { GroupMember } from './groupMember' import { GroupRole } from './grouprole' -export enum GroupKind { +export enum GroupType { classic = 'CLASSIC', private = 'PRIVATE' } @@ -24,7 +24,7 @@ export class Group extends Model { users: GroupMember[] createdAt: Date maxAttendees: number - kind = GroupKind.classic + type = GroupType.classic isApproved(userId: User['id']): boolean { return this.users.some( @@ -33,7 +33,7 @@ export class Group extends Model { canSeeMembers(userId: User['id']): boolean { - return this.kind !== GroupKind.private || this.isApproved(userId) + return this.type !== GroupType.private || this.isApproved(userId) } $beforeInsert(): void { @@ -83,7 +83,7 @@ export class Group extends Model { startDate: { type: 'datetime' }, endDate: { type: 'datetime' }, maxAttendees: { type: 'integer' }, - kind: { type: 'string' } + type: { type: 'string' } } } } diff --git a/src/components/users/user.routes.ts b/src/components/users/user.routes.ts index ccc861b7..06764af2 100644 --- a/src/components/users/user.routes.ts +++ b/src/components/users/user.routes.ts @@ -7,7 +7,7 @@ import { handleValidationError, checkIdParam } from '../../util/validators' import { RoleType, User } from './user' import { isSameUser } from './user.middlewares' import { getUser, updateRole, updateUser } from './user.service' -import { GroupKind } from '../groups/group' +import { GroupType } from '../groups/group' import { GroupRole } from './../groups/grouprole' const router = Router() @@ -15,7 +15,7 @@ const router = Router() router.get('/:id', isAuthenticated, checkIdParam, getUser, (req, res) => res.render('user/show', { userToShow: req.userToShow, - GroupKind, + GroupType, GroupRole, userId: (req.user as User).id, ROLES: ROLES diff --git a/views/group/new.pug b/views/group/new.pug index 0bdfcc5e..ec09fe6f 100644 --- a/views/group/new.pug +++ b/views/group/new.pug @@ -52,11 +52,11 @@ block content label(class='text-lg font-bold' for="pickerEnd") Befejezés ideje input(type="text", id="pickerEnd", name="endDate", required, data-input, class="flatpickr") div - label(for="kind" class='text-lg font-bold') Csoport típusa + label(for="groupType" class='text-lg font-bold') Csoport típusa br - select(id="kind" name="kind" class='flatpickr') - option(value=`${GroupKind.classic}` selected=(kind === GroupKind.classic || !kind)) Klasszikus - option(value=`${GroupKind.private}` selected=(kind === GroupKind.private)) Privát + select(id="groupType" name="groupType" class='flatpickr') + option(value=`${GroupType.classic}` selected=(type === GroupType.classic || !type)) Klasszikus + option(value=`${GroupType.private}` selected=(type === GroupType.private)) Privát ul li span(class='font-bold') Klasszikus - diff --git a/views/group/show.pug b/views/group/show.pug index e3c14941..ca475bbd 100644 --- a/views/group/show.pug +++ b/views/group/show.pug @@ -89,7 +89,7 @@ block content path(stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z') span Nyílt sup(title='Szabad a csatlakozás') ? - if group.kind === GroupKind.private + if group.type === GroupType.private li(class='flex flex-row items-center space-x-2') //- Heroicon name: user-group svg.h-6.w-6(xmlns='http://www.w3.org/2000/svg' fill='none' viewbox='0 0 24 24' stroke='currentColor') diff --git a/views/user/show.pug b/views/user/show.pug index a9400a80..6e11caef 100644 --- a/views/user/show.pug +++ b/views/user/show.pug @@ -23,7 +23,7 @@ block content option(value=`${key}`, selected=key==userToShow.role) #{ROLES.get(key)} button(type="button", id="submitBtn", onclick=`updateRole(${userToShow.id})`, class='ml-2 btn btn-primary animate-hover') Módosítás - - const isPublic = (group) => group.kind === GroupKind.classic + - const isPublic = (group) => group.type === GroupType.classic - const isOwner = (group) => group.groupRole === GroupRole.owner - const viewingSelf = userToShow.id === userId - const ownsGroup = (group) => group.ownerId === userId From 0cc3ec29a347c4f5be0df76800cdcc0ad1cc1d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Sat, 28 Aug 2021 02:55:31 +0200 Subject: [PATCH 20/24] Remove a console.log --- src/components/groups/group.middlewares.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index ce1b0c66..d08a9a9b 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -257,14 +257,12 @@ export const validateGroup = (): ValidationChain[] => { .optional({ checkFalsy: true }) .isInt({ min: 1, max: 100 }), check('groupType', 'Hibás a csoport típusa') - .custom(x => { console.log(x); return true }) + .optional() .isString() .trim() - .optional() .default(GroupType.classic) .toUpperCase() .isIn(Object.values(GroupType)) - .withMessage(x => `${x}`) ] } From 2cda550aba0c645be485d319377f6971b1f22779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Sat, 28 Aug 2021 18:08:56 +0200 Subject: [PATCH 21/24] Create service for fetching groups of the user --- src/components/users/user.service.ts | 10 ++++++++++ src/types/index.d.ts | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/components/users/user.service.ts b/src/components/users/user.service.ts index 8c33d5ee..e849c214 100644 --- a/src/components/users/user.service.ts +++ b/src/components/users/user.service.ts @@ -27,6 +27,16 @@ export const getUser = asyncWrapper(async (req: Request, res: Response, next: Ne } }) +export const getGroupsOfTheUser = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + const user = await User.query() + .findOne({ id: (req.user as User).id }) + .withGraphFetched('groups') + + req.groupsOfTheUser = user.groups + next() + }) + export const updateRole = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { const user = await User.query().findOne({ id: parseInt(req.params.id) }) diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 6b1357f9..e9459aea 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -2,6 +2,7 @@ import { Group } from '../components/groups/group' import { Ticket } from '../components/tickets/ticket' import { User as LocalUser } from '../components/users/user' import { PaginationOptions } from '../components/groups/paginationOptions' +import { GroupOfTheUser } from '../components/users/groupOfTheUser' declare global { namespace Express { @@ -12,6 +13,8 @@ declare global { paginationOptions: PaginationOptions group: Group userToShow: LocalUser + + groupsOfTheUser: GroupOfTheUser[] } } } From 287a065fed2a8ff0c2f32ce523f418a535fcfb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Sat, 28 Aug 2021 18:10:06 +0200 Subject: [PATCH 22/24] Do not hide group members and show role labels Refactor the group widgets into a mixin --- src/components/users/user.routes.ts | 24 +++++++----- views/user/show.pug | 60 ++++++++++++++--------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/components/users/user.routes.ts b/src/components/users/user.routes.ts index 06764af2..948aef38 100644 --- a/src/components/users/user.routes.ts +++ b/src/components/users/user.routes.ts @@ -6,20 +6,26 @@ import { requireRoles, isAuthenticated } from '../../config/passport' import { handleValidationError, checkIdParam } from '../../util/validators' import { RoleType, User } from './user' import { isSameUser } from './user.middlewares' -import { getUser, updateRole, updateUser } from './user.service' +import { getGroupsOfTheUser, getUser, updateRole, updateUser } from './user.service' import { GroupType } from '../groups/group' import { GroupRole } from './../groups/grouprole' const router = Router() -router.get('/:id', isAuthenticated, checkIdParam, getUser, (req, res) => - res.render('user/show', { - userToShow: req.userToShow, - GroupType, - GroupRole, - userId: (req.user as User).id, - ROLES: ROLES - }) +router.get('/:id', + isAuthenticated, + checkIdParam, + getUser, + getGroupsOfTheUser, + (req, res) => + res.render('user/show', { + userToShow: req.userToShow, + groupsOfTheUser: req.groupsOfTheUser, + GroupType, + GroupRole, + userId: (req.user as User).id, + ROLES: ROLES + }) ) router.patch('/:id/role', diff --git a/views/user/show.pug b/views/user/show.pug index 6e11caef..dfa7adab 100644 --- a/views/user/show.pug +++ b/views/user/show.pug @@ -27,48 +27,48 @@ block content - const isOwner = (group) => group.groupRole === GroupRole.owner - const viewingSelf = userToShow.id === userId - const ownsGroup = (group) => group.ownerId === userId - - const isVisible = (group) => isPublic(group) || isOwner(group) || viewingSelf || ownsGroup(group) + - const isAlsoMember = (group) => groupsOfTheUser.some(g => g.id === group.id && g.groupRole !== GroupRole.unapproved) + - const isVisible = (group) => isPublic(group) || isOwner(group) || viewingSelf || ownsGroup(group) || isAlsoMember(group) - const isUpcoming = (group) => group.endDate > Date.now(); - const isOld = (group) => group.endDate <= Date.now() + mixin groupWidget(group) + a(href=`/groups/${group.id}` class='transition duration-300 ease-in-out transform hover:-translate-y-2') + div(class='flex flex-col items-center p-6 border rounded-md dark:border-gray-500 hover:border-gray-400 animate-hover dark:hover:border-gray-300') + span= group.name + div(class='flex items-center') + mixin roleLabel(color, title) + span(class=`px-3 py-1 mx-1 mt-1 text-sm text-white ${color} rounded-full`)= title + case group.groupRole + when GroupRole.owner + +roleLabel('bg-purple-800', 'Szervező') + when GroupRole.unapproved + +roleLabel('bg-gray-800', 'Jelentkező') + when GroupRole.member + +roleLabel('bg-green-800', 'Csoporttag') + div(class='flex mt-1 items-center justify-center') + if !isPublic(group) + //- Heroicon name: eye-off + svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' alt="Privát csoport" aria-label="Privát csoport" viewbox='0 0 20 20' fill='currentColor') + path(fill-rule='evenodd' d='M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z' clip-rule='evenodd') + path(d='M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z') + if user.id === group.ownerId + //- Heroicon name: shield-check + svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' alt="Saját esemény" aria-label="Saját esemény" viewbox='0 0 20 20' fill='currentColor') + path(fill-rule='evenodd' d='M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z' clip-rule='evenodd') + + div(class='space-y-2') - - console.log('&###########################################x', userToShow.groups.filter(isVisible)) if userToShow.groups.filter(isVisible).some(isUpcoming) h3(class='mb-1 text-lg') Közelgő csoportesemények div(class='grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4') each group in userToShow.groups.filter(isVisible).filter(isUpcoming) - a(href=`/groups/${group.id}` class='transition duration-300 ease-in-out transform hover:-translate-y-2') - div(class='flex items-center justify-center p-6 border rounded-md dark:border-gray-500 hover:border-gray-400 animate-hover dark:hover:border-gray-300') - div(class='flex flex-row items-center') - span= group.name - if user.id === group.ownerId - //- Heroicon name: shield-check - svg(xmlns="http://www.w3.org/2000/svg" alt="Saját esemény" aria-label="Saját esemény" viewbox="0 0 20 20" fill="currentColor" class="w-4 h-4 ml-1") - path(fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd") - if !isPublic(group) - //- Heroicon name: eye-off - svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' alt="Privát csoport" aria-label="Privát csoport" viewbox='0 0 20 20' fill='currentColor') - path(fill-rule='evenodd' d='M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z' clip-rule='evenodd') - path(d='M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z') - + +groupWidget(group) if userToShow.groups.filter(isVisible).some(isOld) h3(class='mb-2 text-lg') Elmúlt csoportesemények div(class='grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4') each group in userToShow.groups.filter(isVisible).filter(isOld) - a(href=`/groups/${group.id}` class='transition duration-300 ease-in-out transform hover:-translate-y-2') - div(class='flex items-center justify-center p-6 border rounded-md dark:border-gray-500 hover:border-gray-400 animate-hover dark:hover:border-gray-300') - div(class='flex flex-row items-center') - span= group.name - if user.id === group.ownerId - //- Heroicon name: shield-check - svg(xmlns="http://www.w3.org/2000/svg" alt="Saját esemény" aria-label="Saját esemény" viewbox="0 0 20 20" fill="currentColor" class="w-4 h-4 ml-1") - path(fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd") - if !isPublic(group) - //- Heroicon name: eye-off - svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' alt="Privát csoport" aria-label="Privát csoport" viewbox='0 0 20 20' fill='currentColor') - path(fill-rule='evenodd' d='M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z' clip-rule='evenodd') - path(d='M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z') - + +groupWidget(group) block scripts script(src="/js/user.js") From e7118e4cdbe9da95f6fc21d66bf58037aff76ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Sat, 28 Aug 2021 18:20:12 +0200 Subject: [PATCH 23/24] Remove duplicate role checks --- views/user/show.pug | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/views/user/show.pug b/views/user/show.pug index dfa7adab..f687be2c 100644 --- a/views/user/show.pug +++ b/views/user/show.pug @@ -25,10 +25,8 @@ block content - const isPublic = (group) => group.type === GroupType.classic - const isOwner = (group) => group.groupRole === GroupRole.owner - - const viewingSelf = userToShow.id === userId - - const ownsGroup = (group) => group.ownerId === userId - const isAlsoMember = (group) => groupsOfTheUser.some(g => g.id === group.id && g.groupRole !== GroupRole.unapproved) - - const isVisible = (group) => isPublic(group) || isOwner(group) || viewingSelf || ownsGroup(group) || isAlsoMember(group) + - const isVisible = (group) => isPublic(group) || isOwner(group) || isAlsoMember(group) - const isUpcoming = (group) => group.endDate > Date.now(); - const isOld = (group) => group.endDate <= Date.now() From 45060df89e3ae7f184009aeaf286b629825f49fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCdi=20Tam=C3=A1s?= Date: Sat, 28 Aug 2021 19:42:21 +0200 Subject: [PATCH 24/24] Merge branch 'master' into feat/330-anon-join --- public/js/group/create.js | 18 +++++++++--------- src/components/groups/group.middlewares.ts | 17 ++++++++--------- src/components/groups/group.routes.ts | 9 +++++---- src/components/groups/group.service.ts | 9 ++++++--- src/components/tickets/ticket.service.ts | 8 ++++---- src/components/users/user.middlewares.ts | 4 +--- src/components/users/user.routes.ts | 6 +++--- src/components/users/user.service.ts | 4 ++-- src/config/passport.ts | 2 +- src/types/index.d.ts | 2 ++ views/group/index.pug | 11 +++++++++++ views/group/pagination/index.pug | 15 ++++++++------- 12 files changed, 60 insertions(+), 45 deletions(-) diff --git a/public/js/group/create.js b/public/js/group/create.js index 9092ab1b..6d4b43d6 100644 --- a/public/js/group/create.js +++ b/public/js/group/create.js @@ -1,5 +1,5 @@ let meetingPlace = 'floor' -const MEETING_PLACES = ['floor', 'link', 'other'] +const MEETING_PLACES = ['floor', 'link', 'other'] // eslint-disable-next-line @typescript-eslint/no-unused-vars function selectMeetingPlace(kind) { @@ -26,12 +26,12 @@ function isValidHttpsUrl(str) { return false } // not catching bad top lvl domain (1 character) - const pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string - '(\\#[-a-z\\d_]*)?$','i') // fragment locator + const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i') // fragment locator // not allowing '(' and ')' // catching 1 character TLD @@ -84,13 +84,13 @@ const validateGroup = (data) => { } const handleResponse = async (res, edited) => { + const data = await res.json() switch (res.status) { case 201: sendMessage(`Csoport sikeresen ${edited ? 'frissítve' : 'létrehozva'}`, 'success') - location.href = '/groups' + location.href = `/groups/${data.id}` break case 400: - const data = await res.json() clearMessages() data.errors.forEach((err) => displayMessage(err.msg)) break diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index d08a9a9b..3cd6a54f 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -14,7 +14,7 @@ import { sendEmail } from '../../util/sendEmail' import { GroupRole } from './grouprole' export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const user = req.user as User + const user = req.user const group = req.group let role: GroupRole | null = null @@ -40,7 +40,7 @@ export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: .for(group.id) .relate({ id: user.id, - group_role: role // eslint-disable-line @typescript-eslint/camelcase + group_role: role } as unknown) return next() } @@ -50,7 +50,7 @@ export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: export const sendEmailToOwner = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { - const user = req.user as User + const user = req.user const group = req.group const emailRecepient = await User.query().findOne({ id: group.ownerId }) @@ -75,7 +75,7 @@ export const leaveGroup = asyncWrapper(async (req: Request, res: Response, next: await Group.relatedQuery('users') .for(req.group.id) .unrelate() - .where('user_id', (req.user as User).id) + .where('user_id', req.user.id) next() }) @@ -95,7 +95,6 @@ export const approveMember = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { await Group.relatedQuery('users') .for(req.group.id) - // eslint-disable-next-line @typescript-eslint/camelcase .patch({ group_role: GroupRole.member } as unknown) .where('user_id', req.params.userid) @@ -126,7 +125,7 @@ export const sendEmailToMember = asyncWrapper( */ export const isGroupOwner = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { - if ((req.user as User)?.id === req.group.ownerId) { + if (req.user?.id === req.group.ownerId) { next() } else { res.render('error/forbidden') @@ -136,8 +135,8 @@ export const isGroupOwner = asyncWrapper( export const isGroupOwnerOrAdmin = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { - if (((req.user as User)?.id === req.group.ownerId) - || ((req.user as User)?.role == RoleType.ADMIN)) { + if ((req.user?.id === req.group.ownerId) + || (req.user?.role == RoleType.ADMIN)) { next() } else { res.render('error/forbidden') @@ -282,7 +281,7 @@ export const checkValidMaxAttendeeLimit = asyncWrapper( export const checkConflicts = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { - const { type, ...group } = req.body as Group & { type: string } + const { type, ...group } = req.body as Omit & { type: string } if (type !== 'floor') { return next() } diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index d2991e96..ec55ad2a 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -10,7 +10,7 @@ import multer from 'multer' import { isAuthenticated } from '../../config/passport' import { DATE_FORMAT, ROOMS } from '../../util/constants' import { handleValidationError, checkIdParam } from '../../util/validators' -import { RoleType, User } from '../users/user' +import { RoleType } from '../users/user' import { joinGroup, sendEmailToOwner, @@ -34,6 +34,7 @@ const router = Router() router.get('/', isAuthenticated, getGroups, (req, res) => { res.render('group/index', { groups: req.groups, + past: req.query.past, paginationOpt: req.paginationOptions, dateFns: { format, @@ -63,7 +64,7 @@ router.post('/', checkConflicts, createGroup, joinGroup, - (req: Request, res: Response) => res.sendStatus(201) + (req: Request, res: Response) => res.status(201).json({ id: req.group.id }) ) router.get('/:id', @@ -72,7 +73,7 @@ router.get('/:id', getGroup, (req, res) => { const { group } = req - const user = req.user as User + const user = req.user const userId = user.id const joined = group.users.some(u => u.id === userId) @@ -193,7 +194,7 @@ router.put('/:id', checkConflicts, checkValidMaxAttendeeLimit, updateGroup, - (req: Request, res: Response) => res.sendStatus(201) + (req: Request, res: Response) => res.status(201).json({ id: req.group.id }) ) router.get('/:id/export', isAuthenticated, checkIdParam, getGroup, createICSEvent) diff --git a/src/components/groups/group.service.ts b/src/components/groups/group.service.ts index 00cc64e5..2cdec0fa 100644 --- a/src/components/groups/group.service.ts +++ b/src/components/groups/group.service.ts @@ -1,14 +1,17 @@ import { Request, Response, NextFunction } from 'express' import { Group, GroupType } from './group' -import { User } from '../users/user' import { formatMdToSafeHTML } from '../../util/convertMarkdown' import { asyncWrapper } from '../../util/asyncWrapper' export const getGroups = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { const page = isNaN(Number(req.query.page)) ? 0 : Number(req.query.page) const limit = 20 - const pageObject = await Group.query().orderBy('createdAt', 'DESC').page(page, limit) + const pageObject = req.query.past === 'true' ? + await Group.query().where('endDate', '<', new Date()) + .orderBy('startDate', 'DESC').page(page, limit) : + await Group.query().where('endDate', '>=', new Date()) + .orderBy('startDate', 'ASC').page(page, limit) req.groups = pageObject.results.map(group => { const raw = group.description.slice(0, 50) + (group.description.length > 50 ? ' ...' : '') group.description = formatMdToSafeHTML(raw) @@ -52,7 +55,7 @@ export const createGroup = asyncWrapper(async (req: Request, res: Response, next doNotDisturb: !!req.body.doNotDisturb, startDate: new Date(req.body.startDate), endDate: new Date(req.body.endDate), - ownerId: (req.user as User).id, + ownerId: req.user.id, maxAttendees: parseInt(req.body.maxAttendees) || 100, type: req.body.groupType as GroupType } diff --git a/src/components/tickets/ticket.service.ts b/src/components/tickets/ticket.service.ts index 814fc57e..ccba89b0 100644 --- a/src/components/tickets/ticket.service.ts +++ b/src/components/tickets/ticket.service.ts @@ -10,7 +10,7 @@ import { User } from '../users/user' export const getOtherTickets = asyncWrapper( async (req: Request, _res: Response, next: NextFunction) => { - req.otherTickets = (await Ticket.query().where('userId', '!=', (req.user as User).id) + req.otherTickets = (await Ticket.query().where('userId', '!=', req.user.id) .orderBy('createdAt', 'ASC')).map(ticket => { ticket.description = formatMdToSafeHTML(ticket.description) return ticket @@ -20,7 +20,7 @@ export const getOtherTickets = asyncWrapper( export const getMyTickets = asyncWrapper( async (req: Request, _res: Response, next: NextFunction) => { - req.myTickets = (await Ticket.query().where('userId', '=', (req.user as User).id) + req.myTickets = (await Ticket.query().where('userId', '=', req.user.id) .orderBy('createdAt', 'ASC')).map(ticket => { ticket.description = formatMdToSafeHTML(ticket.description) return ticket @@ -36,7 +36,7 @@ export const createTicket = asyncWrapper( { roomNumber: +req.body.roomNumber, description: req.body.description, - userId: (req.user as User).id, + userId: req.user.id, } ) }) @@ -84,7 +84,7 @@ export const removeTicket = asyncWrapper( export const checkTicketOwner = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { const ticket = await Ticket.query().findOne({ id: parseInt(req.params.id) }) - if (ticket.userId == (req.user as User).id) { + if (ticket.userId == req.user.id) { next() } else { return res.sendStatus(403) diff --git a/src/components/users/user.middlewares.ts b/src/components/users/user.middlewares.ts index 7cadffa1..aaef629c 100644 --- a/src/components/users/user.middlewares.ts +++ b/src/components/users/user.middlewares.ts @@ -1,9 +1,7 @@ import { Request, Response, NextFunction } from 'express' -import { User } from './user' - export const isSameUser = (req: Request, res: Response, next: NextFunction): void => { - if (parseInt(req.params.id) !== (req.user as User).id) { + if (parseInt(req.params.id) !== req.user.id) { res.status(400).json({ errors: [{ msg: 'Nem találahtó felhasználó a megadott ID-val' }] }) } else { next() diff --git a/src/components/users/user.routes.ts b/src/components/users/user.routes.ts index 948aef38..173a1833 100644 --- a/src/components/users/user.routes.ts +++ b/src/components/users/user.routes.ts @@ -4,7 +4,7 @@ import { ROLES } from '../../util/constants' import { requireRoles, isAuthenticated } from '../../config/passport' import { handleValidationError, checkIdParam } from '../../util/validators' -import { RoleType, User } from './user' +import { RoleType } from './user' import { isSameUser } from './user.middlewares' import { getGroupsOfTheUser, getUser, updateRole, updateUser } from './user.service' import { GroupType } from '../groups/group' @@ -23,7 +23,7 @@ router.get('/:id', groupsOfTheUser: req.groupsOfTheUser, GroupType, GroupRole, - userId: (req.user as User).id, + userId: req.user.id, ROLES: ROLES }) ) @@ -45,7 +45,7 @@ router.patch('/:id/role', router.patch('/:id', isAuthenticated, (req, res, next) => { - if ((req.user as User).id !== parseInt(req.params.id)) { + if (req.user.id !== parseInt(req.params.id)) { return res.sendStatus(403) } next() diff --git a/src/components/users/user.service.ts b/src/components/users/user.service.ts index e849c214..f558bb33 100644 --- a/src/components/users/user.service.ts +++ b/src/components/users/user.service.ts @@ -30,7 +30,7 @@ export const getUser = asyncWrapper(async (req: Request, res: Response, next: Ne export const getGroupsOfTheUser = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { const user = await User.query() - .findOne({ id: (req.user as User).id }) + .findOne({ id: req.user.id }) .withGraphFetched('groups') req.groupsOfTheUser = user.groups @@ -51,7 +51,7 @@ export const updateRole = asyncWrapper(async (req: Request, res: Response, next: }) export const updateUser = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const id = (req.user as User).id + const id = req.user.id const { floor, wantEmail } = req.body req.user = await User.query().patchAndFetchById(id, { floor, wantEmail }) diff --git a/src/config/passport.ts b/src/config/passport.ts index e9450378..ff2abbc4 100644 --- a/src/config/passport.ts +++ b/src/config/passport.ts @@ -79,7 +79,7 @@ export const isAuthenticated = export const requireRoles = (...roles: RoleType[]) => { /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ return (req: Request, res: Response, next: NextFunction): Response> => { - const role = (req.user as User)?.role + const role = req.user?.role if (roles.some((element) => role == element)) { next() } else { diff --git a/src/types/index.d.ts b/src/types/index.d.ts index e9459aea..e46dd3f8 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -16,6 +16,8 @@ declare global { groupsOfTheUser: GroupOfTheUser[] } + + interface User extends LocalUser {} } } diff --git a/views/group/index.pug b/views/group/index.pug index 8c4aed2d..ea135769 100644 --- a/views/group/index.pug +++ b/views/group/index.pug @@ -1,9 +1,20 @@ extends ../layouts/main +include ./pagination/activeButton.pug +include ./pagination/inactiveButton.pug + block content div(class='flex flex-row items-center space-x-4') h1(class='title') Csoportok a(href='/groups/new' class='text-xl btn btn-primary animate-hover') Új csoport + div(class='w-1/2 mx-auto text-xl font-bold sm:text-lg') + div(class='my-5 flex flex-row items-center justify-center space-x-2') + if past === 'true' + +inactiveButton('/groups', 'Következők') + +activeButton('/groups?past=true', 'Korábbiak') + else + +activeButton('/groups', 'Következők') + +inactiveButton('/groups?past=true', 'Korábbiak') if paginationOpt.pageNum > 1 include ./pagination/index diff --git a/views/group/pagination/index.pug b/views/group/pagination/index.pug index 255e0fb0..9875968d 100644 --- a/views/group/pagination/index.pug +++ b/views/group/pagination/index.pug @@ -3,26 +3,27 @@ include ./inactiveButton.pug div(class='my-5') nav(role="pagination", aria-label="pagination" class='w-1/2 mx-auto text-xl font-bold sm:text-lg') + -var baseUrl = `/groups?${past ? 'past=' + past + '&' : ''}page=` if paginationOpt.pageNum <= 5 div(class='flex flex-row items-center justify-center space-x-2') - for (i = 0; i < paginationOpt.pageNum; i++) { if i == paginationOpt.current //- current page - +activeButton(`/groups?page=${i}`, i + 1) + +activeButton(baseUrl + `${i}`, i + 1) else - +inactiveButton(`/groups?page=${i}`, i + 1) + +inactiveButton(baseUrl + `${i}`, i + 1) - } else div(class='flex flex-row items-center justify-center space-x-2') if paginationOpt.current >= 2 - +inactiveButton(`/groups?page=0`, 1) + +inactiveButton(baseUrl + '0', 1) span … if paginationOpt.current >= 1 - +inactiveButton(`/groups?page=${paginationOpt.current - 1}`, paginationOpt.current) + +inactiveButton(baseUrl + `${paginationOpt.current - 1}`, paginationOpt.current) //- current page - +activeButton(`/groups?page=${paginationOpt.current}`, paginationOpt.current + 1) + +activeButton(baseUrl + `${paginationOpt.current}`, paginationOpt.current + 1) if paginationOpt.current <= paginationOpt.pageNum - 2 - +inactiveButton(`/groups?page=${paginationOpt.current + 1}`, paginationOpt.current + 2) + +inactiveButton(baseUrl + `${paginationOpt.current + 1}`, paginationOpt.current + 2) if paginationOpt.current <= paginationOpt.pageNum - 3 span … - +inactiveButton(`/groups?page=${paginationOpt.pageNum - 1}`, paginationOpt.pageNum) + +inactiveButton(baseUrl + `${paginationOpt.pageNum - 1}`, paginationOpt.pageNum)