diff --git a/.eslintrc b/.eslintrc index 802803ef..4ad774a5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,29 +1,15 @@ { "parser": "@typescript-eslint/parser", "extends": [ - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" ], "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { - "indent": [ - "warn", - 2 - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "warn", - "never" - ], - "max-len": [ - "error", - 100 - ], + "prettier/prettier": ["warn"], "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-explicit-any": 1, "@typescript-eslint/no-inferrable-types": [ @@ -33,18 +19,6 @@ } ], "@typescript-eslint/no-unused-vars": "warn", - "@typescript-eslint/member-delimiter-style": [ - "warn", - { - "multiline": { - "delimiter": "none", - "requireLast": false - }, - "singleline": { - "delimiter": "comma", - "requireLast": false - } - } - ] + "@typescript-eslint/member-delimiter-style": ["off"] } -} \ No newline at end of file +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 85628031..d21e4cee 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,7 @@ { - "recommendations": ["dbaeumer.vscode-eslint", "mikestead.dotenv"] + "recommendations": [ + "dbaeumer.vscode-eslint", + "mikestead.dotenv", + "esbenp.prettier-vscode" + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 905fabd6..384ecce2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,15 +1,15 @@ { - // Use IntelliSense to learn about possible Node.js debug attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "attach", - "name": "Attach by Process ID", - "processId": "${command:PickProcess}", - "protocol": "inspector" - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible Node.js debug attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach by Process ID", + "processId": "${command:PickProcess}", + "protocol": "inspector" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 7e3c5d80..e706eef8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,12 +7,20 @@ }, "search.exclude": { "**/node_modules": true, - "**/dist": true, + "**/dist": true }, "typescript.referencesCodeLens.enabled": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "files.insertFinalNewline": true, - "eslint.enable": true + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8dca938e..1a64ce7f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,15 +1,15 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "build", - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/cypress.json b/cypress.json index 18893b6d..17ef242e 100644 --- a/cypress.json +++ b/cypress.json @@ -1,3 +1,3 @@ { - "baseUrl": "http://localhost:3000" + "baseUrl": "http://localhost:3000" } diff --git a/cypress/integration/calendar_test.ts b/cypress/integration/calendar_test.ts index b454c489..a3094d09 100644 --- a/cypress/integration/calendar_test.ts +++ b/cypress/integration/calendar_test.ts @@ -8,13 +8,17 @@ describe('Calendar page test', () => { const today: Date = new Date() - cy.get('.fc-day-today').children().first() + cy.get('.fc-day-today') + .children() + .first() .should('include.text', `${today.getMonth() + 1}. ${today.getDate()}.`) cy.get('.fc-icon-chevron-right').click() cy.contains('ma').click() - cy.get('.fc-day-today').children().first() + cy.get('.fc-day-today') + .children() + .first() .should('include.text', `${today.getMonth() + 1}. ${today.getDate()}.`) }) }) diff --git a/cypress/integration/darkmode_cookie_test.ts b/cypress/integration/darkmode_cookie_test.ts index eb62ae6c..358f174f 100644 --- a/cypress/integration/darkmode_cookie_test.ts +++ b/cypress/integration/darkmode_cookie_test.ts @@ -4,12 +4,28 @@ describe('Theme tests', () => { cy.get('.cursor-pointer').last().find('svg').click() cy.getCookie('theme').should('have.property', 'value', 'dark') - cy.contains('Tanulószobák').should('css', 'background-color', 'rgb(79, 70, 229)') - cy.contains('Foglalás').should('css', 'background-color', 'rgb(167, 139, 250)') + cy.contains('Tanulószobák').should( + 'css', + 'background-color', + 'rgb(79, 70, 229)' + ) + cy.contains('Foglalás').should( + 'css', + 'background-color', + 'rgb(167, 139, 250)' + ) cy.get('.cursor-pointer').last().find('svg').click() cy.getCookie('theme').should('have.property', 'value', 'light') - cy.contains('Tanulószobák').should('css', 'background-color', 'rgb(249, 250, 251)') - cy.contains('Foglalás').should('css', 'background-color', 'rgb(254, 215, 170)') + cy.contains('Tanulószobák').should( + 'css', + 'background-color', + 'rgb(249, 250, 251)' + ) + cy.contains('Foglalás').should( + 'css', + 'background-color', + 'rgb(254, 215, 170)' + ) }) }) diff --git a/cypress/integration/logged_out_tests.ts b/cypress/integration/logged_out_tests.ts index 17f48b9b..1a6ab2e6 100644 --- a/cypress/integration/logged_out_tests.ts +++ b/cypress/integration/logged_out_tests.ts @@ -1,7 +1,7 @@ describe('Logged out tests', () => { it('checks responses when the user is not logged in', () => { const loggedOutText = 'Az oldal megtekintéséhez bejelentkezés szükséges!' - + cy.visit('/') cy.contains('Foglalás').click() @@ -24,4 +24,3 @@ describe('Logged out tests', () => { cy.contains(loggedOutText) }) }) - \ No newline at end of file diff --git a/knexfile.js b/knexfile.js index ccf54014..7061fa9b 100644 --- a/knexfile.js +++ b/knexfile.js @@ -10,16 +10,16 @@ module.exports = { port: +process.env.DB_PORT, user: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE + database: process.env.DB_DATABASE, }, pool: { min: 2, - max: 10 + max: 10, }, migrations: { tableName: 'migrationTable', extension: 'ts', - directory: './migrations' + directory: './migrations', }, - ...knexSnakeCaseMappers() + ...knexSnakeCaseMappers(), } diff --git a/migrations/20200328131352_initial_schema.ts b/migrations/20200328131352_initial_schema.ts index 0d81e6f4..50b525af 100644 --- a/migrations/20200328131352_initial_schema.ts +++ b/migrations/20200328131352_initial_schema.ts @@ -2,14 +2,14 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { return knex.schema - .createTable('tickets', table => { + .createTable('tickets', (table) => { table.increments('id').primary() table.string('description') table.integer('roomNumber') table.dateTime('createdAt') }) - .createTable('users', table => { + .createTable('users', (table) => { table.increments('id').primary() table.string('name') @@ -17,7 +17,7 @@ export async function up(knex: Knex): Promise { table.string('authSchId') table.boolean('admin') }) - .createTable('groups', table => { + .createTable('groups', (table) => { table.increments('id').primary() table.string('name') @@ -29,7 +29,7 @@ export async function up(knex: Knex): Promise { table.boolean('doNotDisturb') table.dateTime('createdAt') }) - .createTable('users_groups', table => { + .createTable('users_groups', (table) => { table.increments('id').primary() table diff --git a/migrations/20200328173956_add_owner_id_to_group.ts b/migrations/20200328173956_add_owner_id_to_group.ts index 7cca3516..7af0c3d7 100644 --- a/migrations/20200328173956_add_owner_id_to_group.ts +++ b/migrations/20200328173956_add_owner_id_to_group.ts @@ -1,8 +1,9 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return knex.schema.alterTable('groups', table => { - table.integer('ownerId') + return knex.schema.alterTable('groups', (table) => { + table + .integer('ownerId') .unsigned() .references('id') .inTable('users') @@ -11,8 +12,7 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - return knex.schema.alterTable('groups', table => { - table.dropColumn('ownerId') - .dropIndex('ownerId') + return knex.schema.alterTable('groups', (table) => { + table.dropColumn('ownerId').dropIndex('ownerId') }) } diff --git a/migrations/20200408124626_add_non_nullable.ts b/migrations/20200408124626_add_non_nullable.ts index b2986fba..f9c8a361 100644 --- a/migrations/20200408124626_add_non_nullable.ts +++ b/migrations/20200408124626_add_non_nullable.ts @@ -2,16 +2,16 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { return knex.schema - .alterTable('tickets', table => { + .alterTable('tickets', (table) => { table.integer('roomNumber').notNullable().alter() table.string('description').notNullable().alter() }) - .alterTable('users', table => { + .alterTable('users', (table) => { table.string('name').notNullable().alter() table.string('email').notNullable().alter() table.string('authSchId').notNullable().alter() }) - .alterTable('groups', table => { + .alterTable('groups', (table) => { table.string('name').notNullable().alter() table.dateTime('startDate').notNullable().alter() table.dateTime('endDate').notNullable().alter() @@ -22,16 +22,16 @@ export async function up(knex: Knex): Promise { export async function down(knex: Knex): Promise { return knex.schema - .alterTable('tickets', table => { + .alterTable('tickets', (table) => { table.integer('roomNumber').nullable().alter() table.string('description').nullable().alter() }) - .alterTable('users', table => { + .alterTable('users', (table) => { table.string('name').nullable().alter() table.string('email').nullable().alter() table.string('authSchId').nullable().alter() }) - .alterTable('groups', table => { + .alterTable('groups', (table) => { table.string('name').nullable().alter() table.dateTime('startDate').nullable().alter() table.dateTime('endDate').nullable().alter() diff --git a/migrations/20200510001427_add_floor_to_profile.ts b/migrations/20200510001427_add_floor_to_profile.ts index 677787db..9217033c 100644 --- a/migrations/20200510001427_add_floor_to_profile.ts +++ b/migrations/20200510001427_add_floor_to_profile.ts @@ -1,15 +1,13 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return knex.schema.table('users', table => { - table.integer('floor') - .unsigned() - .nullable() + return knex.schema.table('users', (table) => { + table.integer('floor').unsigned().nullable() }) } export async function down(knex: Knex): Promise { - return knex.schema.table('users', table => { + return knex.schema.table('users', (table) => { table.dropColumn('floor') }) } diff --git a/migrations/20200527121145_rename_column_in_groups.ts b/migrations/20200527121145_rename_column_in_groups.ts index 9635af1c..f8e3ea69 100644 --- a/migrations/20200527121145_rename_column_in_groups.ts +++ b/migrations/20200527121145_rename_column_in_groups.ts @@ -1,13 +1,13 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return knex.schema.table('groups', table => { + return knex.schema.table('groups', (table) => { table.renameColumn('subject', 'tags') }) } export async function down(knex: Knex): Promise { - return knex.schema.table('groups', table => { + return knex.schema.table('groups', (table) => { table.renameColumn('tags', 'subject') }) } diff --git a/migrations/20200721213650_update_description_lengths.ts b/migrations/20200721213650_update_description_lengths.ts index 8a80a751..0a0d1d35 100644 --- a/migrations/20200721213650_update_description_lengths.ts +++ b/migrations/20200721213650_update_description_lengths.ts @@ -1,28 +1,23 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return Promise.all( - [ - knex.schema.alterTable('groups', table => { - table.string('description', 500).alter() - }), - knex.schema.alterTable('tickets', table => { - table.string('description', 500).alter() - }) - ] - ) + return Promise.all([ + knex.schema.alterTable('groups', (table) => { + table.string('description', 500).alter() + }), + knex.schema.alterTable('tickets', (table) => { + table.string('description', 500).alter() + }), + ]) } export async function down(knex: Knex): Promise { - return Promise.all( - [ - knex.schema.alterTable('groups', table => { - table.string('description').alter() - }), - knex.schema.alterTable('tickets', table => { - table.string('description').alter() - }) - ] - ) + return Promise.all([ + knex.schema.alterTable('groups', (table) => { + table.string('description').alter() + }), + knex.schema.alterTable('tickets', (table) => { + table.string('description').alter() + }), + ]) } - diff --git a/migrations/20200828113656_migration_add_status_for_tickets.ts b/migrations/20200828113656_migration_add_status_for_tickets.ts index 748297bf..e2c62cee 100644 --- a/migrations/20200828113656_migration_add_status_for_tickets.ts +++ b/migrations/20200828113656_migration_add_status_for_tickets.ts @@ -1,18 +1,11 @@ import * as Knex from 'knex' - export async function up(knex: Knex): Promise { - return knex.schema.table('tickets', table => { - table.enu('status', - [ - 'SENT', - 'IN_PROGRESS', - 'DONE', - 'ARCHIVED' - ], - { + return knex.schema.table('tickets', (table) => { + table + .enu('status', ['SENT', 'IN_PROGRESS', 'DONE', 'ARCHIVED'], { useNative: true, - enumName: 'status_type' + enumName: 'status_type', }) .defaultTo('SENT') .notNullable() @@ -20,8 +13,7 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - return knex.schema.table('tickets', table => { + return knex.schema.table('tickets', (table) => { table.dropColumn('status') }) } - diff --git a/migrations/20201012224257_add_max_attendees.ts b/migrations/20201012224257_add_max_attendees.ts index 863d4ef0..2ef4b5d0 100644 --- a/migrations/20201012224257_add_max_attendees.ts +++ b/migrations/20201012224257_add_max_attendees.ts @@ -1,16 +1,13 @@ import * as Knex from 'knex' - export async function up(knex: Knex): Promise { - return knex.schema.alterTable('groups', table => { + return knex.schema.alterTable('groups', (table) => { table.integer('max_attendees').defaultTo(100) }) } - export async function down(knex: Knex): Promise { - return knex.schema.alterTable('groups', table => { + return knex.schema.alterTable('groups', (table) => { table.dropColumn('max_attendees') }) } - diff --git a/migrations/20201013225752_add_role_to_user.ts b/migrations/20201013225752_add_role_to_user.ts index 84b5455c..948aa9c2 100644 --- a/migrations/20201013225752_add_role_to_user.ts +++ b/migrations/20201013225752_add_role_to_user.ts @@ -1,17 +1,12 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return knex.schema.table('users', table => { + return knex.schema.table('users', (table) => { table.dropColumn('admin') - table.enu('role', - [ - 'ADMIN', - 'TICKET_ADMIN', - 'USER' - ], - { + table + .enu('role', ['ADMIN', 'TICKET_ADMIN', 'USER'], { useNative: true, - enumName: 'role_type' + enumName: 'role_type', }) .defaultTo('USER') .notNullable() @@ -19,9 +14,8 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - return knex.schema.table('users', table => { + return knex.schema.table('users', (table) => { table.dropColumn('role') table.boolean('admin') }) } - diff --git a/migrations/20210418152354_add_user_id_to_tickets.ts b/migrations/20210418152354_add_user_id_to_tickets.ts index bb551d4e..c626f69c 100644 --- a/migrations/20210418152354_add_user_id_to_tickets.ts +++ b/migrations/20210418152354_add_user_id_to_tickets.ts @@ -1,19 +1,13 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return knex.schema.alterTable('tickets', table => { - table.integer('userId') - .unsigned() - .references('id') - .inTable('users') - .index() + return knex.schema.alterTable('tickets', (table) => { + table.integer('userId').unsigned().references('id').inTable('users').index() }) } - export async function down(knex: Knex): Promise { - return knex.schema.alterTable('tickets', table => { - table.dropColumn('userId') - .dropIndex('userId') + return knex.schema.alterTable('tickets', (table) => { + table.dropColumn('userId').dropIndex('userId') }) } diff --git a/migrations/20210611222535_add_place_and_link_to_groups.ts b/migrations/20210611222535_add_place_and_link_to_groups.ts index f0fec554..552c158a 100644 --- a/migrations/20210611222535_add_place_and_link_to_groups.ts +++ b/migrations/20210611222535_add_place_and_link_to_groups.ts @@ -1,16 +1,15 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return knex.schema.alterTable('groups', table => { + return knex.schema.alterTable('groups', (table) => { table.integer('room').nullable().alter() table.string('link') table.string('place') }) } - export async function down(knex: Knex): Promise { - return knex.schema.alterTable('groups', table => { + return knex.schema.alterTable('groups', (table) => { table.integer('room').notNullable().alter() table.dropColumn('link') table.dropColumn('place') diff --git a/migrations/20210626192847_add_wantemail_to_user.ts b/migrations/20210626192847_add_wantemail_to_user.ts index bf7e3834..27b79d04 100644 --- a/migrations/20210626192847_add_wantemail_to_user.ts +++ b/migrations/20210626192847_add_wantemail_to_user.ts @@ -1,14 +1,13 @@ import * as Knex from 'knex' export async function up(knex: Knex): Promise { - return knex.schema.table('users', table => { - table.boolean('wantEmail') - .defaultTo(true) + return knex.schema.table('users', (table) => { + table.boolean('wantEmail').defaultTo(true) }) } export async function down(knex: Knex): Promise { - return knex.schema.table('users', table => { + return knex.schema.table('users', (table) => { table.dropColumn('wantEmail') }) } diff --git a/nodemon.json b/nodemon.json index c43059c6..b320ed3e 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,10 +1,6 @@ { - "watch": [ - "src" - ], - "ignore": [ - "src/**/*.test.ts" - ], + "watch": ["src"], + "ignore": ["src/**/*.test.ts"], "ext": "ts,mjs,js,json,graphql", "exec": "etsc && node ./dist/src/server.js", "legacyWatch": true diff --git a/package.json b/package.json index c91027db..7b31a3d8 100644 --- a/package.json +++ b/package.json @@ -79,9 +79,13 @@ "cypress": "7.7.0", "esbuild-node-tsc": "1.6.1", "eslint": "7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^3.4.1", "faker": "5.5.3", "nodemon": "2.0.12", "postcss-cli": "8.3.1", + "prettier": "^2.3.2", + "prettier-eslint": "^13.0.0", "shelljs": "0.8.4", "ts-node": "10.2.0", "typescript": "4.3.5" diff --git a/postcss.config.js b/postcss.config.js index 6fdadb7d..27970ba1 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -7,4 +7,3 @@ module.exports = { // ... ], } - diff --git a/public/js/group/create.js b/public/js/group/create.js index 6d4b43d6..8f2dd27c 100644 --- a/public/js/group/create.js +++ b/public/js/group/create.js @@ -11,8 +11,10 @@ function selectMeetingPlace(kind) { selectedInputField.required = true selectedWrapper.classList.remove('hidden') - MEETING_PLACES.filter(it => it !== kind).forEach(otherPlace => { - document.getElementById(`${otherPlace}Btn`).classList.remove('btn-meeting-selected') + MEETING_PLACES.filter((it) => it !== kind).forEach((otherPlace) => { + document + .getElementById(`${otherPlace}Btn`) + .classList.remove('btn-meeting-selected') document.getElementById(`${otherPlace}Input`).required = false document.getElementById(`${otherPlace}Div`).classList.add('hidden') }) @@ -26,12 +28,15 @@ 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 @@ -61,7 +66,10 @@ const validateGroup = (data) => { errors.push('A név kitöltése kötelező') } - if (meetingPlace === 'floor' && (typeof room !== 'number' || room < 3 || room > 18)) { + if ( + meetingPlace === 'floor' && + (typeof room !== 'number' || room < 3 || room > 18) + ) { errors.push('A szint csak 3 és 18 közötti szám lehet') } @@ -86,17 +94,20 @@ 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/${data.id}` - break - case 400: - clearMessages() - data.errors.forEach((err) => displayMessage(err.msg)) - break - case 401: - displayMessage(UNAUTHORIZED_MESSAGE) - break + case 201: + sendMessage( + `Csoport sikeresen ${edited ? 'frissítve' : 'létrehozva'}`, + 'success' + ) + location.href = `/groups/${data.id}` + break + case 400: + clearMessages() + data.errors.forEach((err) => displayMessage(err.msg)) + break + case 401: + displayMessage(UNAUTHORIZED_MESSAGE) + break } } @@ -150,14 +161,13 @@ const editGroup = (event) => { const formEl = document.getElementById('group-form') formEl.addEventListener('submit', (event) => { - if (typeof isEditing !== 'undefined' && isEditing) - editGroup(event) - else - addGroup(event) + if (typeof isEditing !== 'undefined' && isEditing) editGroup(event) + else addGroup(event) }) // eslint-disable-next-line @typescript-eslint/no-unused-vars -const calendarOptions = { // TODO: side-calendar, eslint-disable not needed +const calendarOptions = { + // TODO: side-calendar, eslint-disable not needed plugins: ['timeGrid'], views: { timeGridOneDay: { @@ -182,15 +192,15 @@ const calendarOptions = { // TODO: side-calendar, eslint-disable not needed }, defaultView: 'timeGridOneDay', footer: { - center: 'prevWeek,prev,today,next,nextWeek' + center: 'prevWeek,prev,today,next,nextWeek', }, titleFormat: { year: 'numeric', month: 'short', weekday: 'short', - day: 'numeric' + day: 'numeric', }, - aspectRatio: 0.7 + aspectRatio: 0.7, } // TODO: side-calendar diff --git a/public/js/main.js b/public/js/main.js index 75b7ebfe..1bcda75f 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -12,9 +12,17 @@ function displayMessage(message, type = 'danger') { if (type === 'danger') { messageEL.classList.add('text-gray-900', 'bg-red-100', 'border-red-500') } else if (type === 'success') { - messageEL.classList.add('text-green-900', 'bg-green-100', 'border-green-500') + messageEL.classList.add( + 'text-green-900', + 'bg-green-100', + 'border-green-500' + ) } else { - messageEL.classList.add('text-yellow-900', 'bg-yellow-100', 'border-yellow-500') + messageEL.classList.add( + 'text-yellow-900', + 'bg-yellow-100', + 'border-yellow-500' + ) } const messageBodyEl = document.createElement('div') @@ -34,8 +42,8 @@ function displayMessage(message, type = 'danger') { // eslint-disable-next-line @typescript-eslint/no-unused-vars function sendMessage(message, type = 'danger') { document.cookie = `message=${JSON.stringify({ - mes: message, - type: type + mes: message, + type: type, })};path=/;SameSite=Lax;` } @@ -43,14 +51,15 @@ function sendMessage(message, type = 'danger') { window.addEventListener('DOMContentLoaded', () => { const cookie = getCookie('message') if (cookie) { - const {mes, type} = JSON.parse(cookie) + const { mes, type } = JSON.parse(cookie) displayMessage(mes, type) - document.cookie = 'message= ;path=/;SameSite=Lax;expires = Thu, 01 Jan 1970 00:00:00 GMT' + document.cookie = + 'message= ;path=/;SameSite=Lax;expires = Thu, 01 Jan 1970 00:00:00 GMT' } }) // eslint-disable-next-line @typescript-eslint/no-unused-vars function clearMessages() { const alertEl = document.getElementById('alert') - alertEl.childNodes.forEach(el => el.remove()) + alertEl.childNodes.forEach((el) => el.remove()) } diff --git a/public/js/md-editor.js b/public/js/md-editor.js index fc4fc99b..7297f5ff 100644 --- a/public/js/md-editor.js +++ b/public/js/md-editor.js @@ -7,8 +7,7 @@ function listenLength(el, characterCount) { el.innerHTML = 'Túl hosszú!' el.setAttribute('style', 'color:red;') submitBtn.disabled = true - } - else { + } else { el.innerHTML = characterCount + ' / ' + charLimit submitBtn.disabled = false } @@ -17,23 +16,34 @@ function listenLength(el, characterCount) { const easyMDE = new EasyMDE({ element: textArea, toolbar: [ - 'bold', 'italic', 'heading', '|', 'unordered-list', 'ordered-list', - '|', 'undo', 'redo', '|', 'guide' + 'bold', + 'italic', + 'heading', + '|', + 'unordered-list', + 'ordered-list', + '|', + 'undo', + 'redo', + '|', + 'guide', ], forceSync: true, autoDownloadFontAwesome: true, spellChecker: false, placeholder: 'Kattints az utmutató gombjára a részletekért...', - status: [{ - className: 'chars', - defaultValue: (el) => { - el.innerHTML = '0 / ' + charLimit + status: [ + { + className: 'chars', + defaultValue: (el) => { + el.innerHTML = '0 / ' + charLimit + }, + onUpdate: (el) => { + characterCount = easyMDE.value().length + listenLength(el, characterCount) + }, }, - onUpdate: (el) => { - characterCount = easyMDE.value().length - listenLength(el, characterCount) - } - }] + ], }) if (typeof description !== 'undefined' && description !== null) { diff --git a/public/js/modal.js b/public/js/modal.js index ddd690dd..ffbd64c5 100644 --- a/public/js/modal.js +++ b/public/js/modal.js @@ -1,6 +1,12 @@ // omitted parameters won't be changed // eslint-disable-next-line @typescript-eslint/no-unused-vars -function toggleModal(newAction, newTitle, newDesc, newButtonText, newIsActionFunction = undefined) { +function toggleModal( + newAction, + newTitle, + newDesc, + newButtonText, + newIsActionFunction = undefined +) { const title = document.getElementById('modal-title') const description = document.getElementById('modal-desc') const submitButton = document.getElementById('submit-button') @@ -19,7 +25,10 @@ function toggleModal(newAction, newTitle, newDesc, newButtonText, newIsActionFun } if (newAction) { form.action = newAction - funcButton.setAttribute( 'onClick', `javascript: toggleModal(); ${newAction};`) + funcButton.setAttribute( + 'onClick', + `javascript: toggleModal(); ${newAction};` + ) } if (typeof newIsActionFunction !== 'undefined') { diff --git a/public/js/picker.js b/public/js/picker.js index 3aad9ee4..8ce5bffa 100644 --- a/public/js/picker.js +++ b/public/js/picker.js @@ -1,12 +1,12 @@ //Adding hours and minutes are be easier with these prototype methods -Date.prototype.addHours = function(h) { +Date.prototype.addHours = function (h) { const temp = new Date() - temp.setTime(this.getTime() + (h * 60 * 60 * 1000)) + temp.setTime(this.getTime() + h * 60 * 60 * 1000) return temp } -Date.prototype.addMinutes = function(m) { +Date.prototype.addMinutes = function (m) { const temp = new Date() - temp.setTime(this.getTime() + (m * 60 * 1000)) + temp.setTime(this.getTime() + m * 60 * 1000) return temp } @@ -22,7 +22,9 @@ if (typeof range !== 'undefined') { startDef = parsedStart endDef = flatpickr.parseDate(range.end, 'Y-m-dTH:i') } else { - displayMessage('Hiba: múltbéli időintervallumban csoport nem hozható létre.') + displayMessage( + 'Hiba: múltbéli időintervallumban csoport nem hozható létre.' + ) } } diff --git a/public/js/polyfills.js b/public/js/polyfills.js index eb619416..c3ede00d 100644 --- a/public/js/polyfills.js +++ b/public/js/polyfills.js @@ -1,4 +1,4 @@ -(function () { +;(function () { const templates = document.querySelectorAll('svg template') let el, template, attribs, attrib, count, child, content for (let i = 0; i < templates.length; i++) { diff --git a/public/js/room/calendar.js b/public/js/room/calendar.js index 16f3e851..df760e37 100644 --- a/public/js/room/calendar.js +++ b/public/js/room/calendar.js @@ -7,10 +7,10 @@ const commonCalendarOptions = { slotLabelFormat: { hour: 'numeric', minute: '2-digit', - omitZeroMinute: false + omitZeroMinute: false, }, - nowIndicator: true - } + nowIndicator: true, + }, }, buttonText: { today: 'ma', @@ -22,7 +22,7 @@ const commonCalendarOptions = { locale: 'hu', selectable: true, eventClick: (calEvent) => - (location.href = `/groups/${calEvent.event.groupId}`) + (location.href = `/groups/${calEvent.event.groupId}`), } function generateWebCalendar(data, calendarEl, room) { @@ -42,7 +42,7 @@ function generateWebCalendar(data, calendarEl, room) { } else { location.href = `/groups/new?start=${info.startStr}&end=${info.endStr}&roomId=${room}` } - } + }, }) return calendar } @@ -53,7 +53,7 @@ function generateMobileCalendar(data, calendarEl, room) { headerToolbar: { left: 'title', center: '', - right: 'timeGridOneDay,dayGridMonth' + right: 'timeGridOneDay,dayGridMonth', }, initialView: 'timeGridOneDay', customButtons: { @@ -61,27 +61,27 @@ function generateMobileCalendar(data, calendarEl, room) { text: '-7', click: () => { calendar.incrementDate({ - days: -7 + days: -7, }) - } + }, }, nextWeek: { text: '+7', click: () => { calendar.incrementDate({ - days: 7 + days: 7, }) - } - } + }, + }, }, footerToolbar: { - center: 'prevWeek,prev,today,next,nextWeek' + center: 'prevWeek,prev,today,next,nextWeek', }, titleFormat: { year: 'numeric', month: 'short', weekday: 'short', - day: 'numeric' + day: 'numeric', }, aspectRatio: 0.75, events: data, @@ -89,11 +89,10 @@ function generateMobileCalendar(data, calendarEl, room) { if (info.view.type === 'dayGridMonth') { calendar.gotoDate(info.start) calendar.changeView('timeGridOneDay') - } - else { + } else { location.href = `/groups/new?start=${info.startStr}&end=${info.endStr}&roomId=${room}` } - } + }, }) return calendar } @@ -104,12 +103,11 @@ document.addEventListener('DOMContentLoaded', () => { const room = window.location.pathname.split('/').pop() fetch(`/rooms/${room}/events`) - .then(response => response.json()) - .then(data => { + .then((response) => response.json()) + .then((data) => { if (window.innerWidth < 768) { generateMobileCalendar(data, calendarEl, room).render() - } - else { + } else { generateWebCalendar(data, calendarEl, room).render() } }) diff --git a/public/js/tags.js b/public/js/tags.js index 1ed95133..8c429698 100644 --- a/public/js/tags.js +++ b/public/js/tags.js @@ -5,7 +5,7 @@ function transformTag(tagData) { const tagsInput = document.querySelector('input[name=tags]') const tagify = new Tagify(tagsInput, { editTags: false, - transformTag: transformTag + transformTag: transformTag, }) if (isEditing && tags) { diff --git a/public/js/ticket.js b/public/js/ticket.js index 916fe702..0d9678be 100644 --- a/public/js/ticket.js +++ b/public/js/ticket.js @@ -1,35 +1,35 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars function deleteTicket(id, own) { const url = `/tickets/${own ? 'own/' : ''}${id}` - + fetch(url, { method: 'DELETE' }) - .then(async res => { - switch(res.status) { - case 204: - const ticket = document.getElementById(`ticket-${id}`) - const container = ticket.parentNode - container.removeChild(ticket) - displayMessage('Hibajegy sikeresen törölve', 'success') - if (container.childElementCount == 0){ - const text = document.createElement('p') - text.classList.add('ml-6') - text.innerHTML = own ? 'Nincsen hibajegyed' : 'Nincsenek hibajegyek' - container.appendChild(text) - } - break - case 401: - displayMessage(UNAUTHORIZED_MESSAGE) - break - case 403: - displayMessage(FORBIDDEN_MESSAGE) - break - case 404: - data = await res.json() - displayMessage(data.message) - break + .then(async (res) => { + switch (res.status) { + case 204: + const ticket = document.getElementById(`ticket-${id}`) + const container = ticket.parentNode + container.removeChild(ticket) + displayMessage('Hibajegy sikeresen törölve', 'success') + if (container.childElementCount == 0) { + const text = document.createElement('p') + text.classList.add('ml-6') + text.innerHTML = own ? 'Nincsen hibajegyed' : 'Nincsenek hibajegyek' + container.appendChild(text) + } + break + case 401: + displayMessage(UNAUTHORIZED_MESSAGE) + break + case 403: + displayMessage(FORBIDDEN_MESSAGE) + break + case 404: + data = await res.json() + displayMessage(data.message) + break } }) - .catch(err => displayMessage(err)) + .catch((err) => displayMessage(err)) } function validateTicket(data) { @@ -57,23 +57,23 @@ function addTicket() { if (errors.length) { clearMessages() - errors.forEach(err => displayMessage(err)) + errors.forEach((err) => displayMessage(err)) } else { fetch('/tickets', { method: 'POST', body: formData }) .then(async (res) => { switch (res.status) { - case 201: - sendMessage('Hibajegy sikeresen létrehozva', 'success') - location.href = '/tickets' - break - case 400: - const data = await res.json() - clearMessages() - data.errors.forEach((err) => displayMessage(err.msg)) - break - case 401: - displayMessage(UNAUTHORIZED_MESSAGE) - break + case 201: + sendMessage('Hibajegy sikeresen létrehozva', 'success') + location.href = '/tickets' + break + case 400: + const data = await res.json() + clearMessages() + data.errors.forEach((err) => displayMessage(err.msg)) + break + case 401: + displayMessage(UNAUTHORIZED_MESSAGE) + break } }) .catch((err) => displayMessage(err)) @@ -86,24 +86,27 @@ function moveTicket(id) { const formData = new FormData(formEl) fetch(`/tickets/${id}`, { method: 'PUT', - body: formData + body: formData, }) .then(async (res) => { switch (res.status) { - case 200: - const status = await res.json() - const labelEl = document.getElementById(`ticket-label-${id}`) - labelEl.innerHTML = status - displayMessage('Hibajegy státusza sikeresen megváltoztatva!', 'success') - break - case 400: - const data = await res.json() - clearMessages() - data.errors.forEach((err) => displayMessage(err.msg)) - break - case 401: - displayMessage(UNAUTHORIZED_MESSAGE) - break + case 200: + const status = await res.json() + const labelEl = document.getElementById(`ticket-label-${id}`) + labelEl.innerHTML = status + displayMessage( + 'Hibajegy státusza sikeresen megváltoztatva!', + 'success' + ) + break + case 400: + const data = await res.json() + clearMessages() + data.errors.forEach((err) => displayMessage(err.msg)) + break + case 401: + displayMessage(UNAUTHORIZED_MESSAGE) + break } }) .catch((err) => displayMessage(err)) diff --git a/public/js/user.js b/public/js/user.js index 1a58b01f..f66cef55 100644 --- a/public/js/user.js +++ b/public/js/user.js @@ -10,29 +10,32 @@ function updateUser(id) { fetch(`/users/${id}`, { method: 'PATCH', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, body: JSON.stringify({ floor, wantEmail }), }) - .then(async res => { + .then(async (res) => { switch (res.status) { - case 200: - const user = await res.json() - floorEl.value = user.floor - emailEl.checked = user.wantEmail - displayMessage('Személyes beállítások sikeresen frissítve!', 'success') - break - case 400: - const data = await res.json() - clearMessages() - data.errors.forEach(err => displayMessage(err.msg)) - break - case 401: - displayMessage(UNAUTHORIZED_MESSAGE) - break - default: - displayMessage('Nem várt hiba történt') - break + case 200: + const user = await res.json() + floorEl.value = user.floor + emailEl.checked = user.wantEmail + displayMessage( + 'Személyes beállítások sikeresen frissítve!', + 'success' + ) + break + case 400: + const data = await res.json() + clearMessages() + data.errors.forEach((err) => displayMessage(err.msg)) + break + case 401: + displayMessage(UNAUTHORIZED_MESSAGE) + break + default: + displayMessage('Nem várt hiba történt') + break } }) .catch((err) => displayMessage(err)) @@ -44,30 +47,33 @@ function updateRole(id) { const roleEl = document.getElementById('role') const role = roleEl.value console.log(role) - if (role == '' || !(role == 'ADMIN' || role == 'TICKET_ADMIN' || role == 'USER')) { + if ( + role == '' || + !(role == 'ADMIN' || role == 'TICKET_ADMIN' || role == 'USER') + ) { displayMessage('A felhasználói jogkör nem megfelelő.') } else { fetch(`/users/${id}/role`, { method: 'PATCH', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, body: JSON.stringify({ role }), }) - .then(async res => { + .then(async (res) => { switch (res.status) { - case 201: - sendMessage('Felhasználó rangja sikeresen frissítve', 'success') - location.href = `/users/${id}` - break - case 400: - const data = await res.json() - clearMessages() - data.errors.forEach((err) => displayMessage(err.msg)) - break - case 403: - displayMessage(UNAUTHORIZED_MESSAGE) - break + case 201: + sendMessage('Felhasználó rangja sikeresen frissítve', 'success') + location.href = `/users/${id}` + break + case 400: + const data = await res.json() + clearMessages() + data.errors.forEach((err) => displayMessage(err.msg)) + break + case 403: + displayMessage(UNAUTHORIZED_MESSAGE) + break } }) .catch((err) => displayMessage(err)) diff --git a/renovate.json b/renovate.json index c84e6c78..79d9edd8 100644 --- a/renovate.json +++ b/renovate.json @@ -1,13 +1,9 @@ { - "extends": [ - "config:base" - ], + "extends": ["config:base"], "packageRules": [ { "groupName": "definitelyTyped", - "matchPackagePrefixes": [ - "@types/" - ] + "matchPackagePrefixes": ["@types/"] } ] } diff --git a/seeds/01_add_users.ts b/seeds/01_add_users.ts index 97ee849f..3f71c11f 100644 --- a/seeds/01_add_users.ts +++ b/seeds/01_add_users.ts @@ -4,7 +4,9 @@ import { RoleType } from '../src/components/users/user' export async function seed(knex: Knex): Promise { // Deletes ALL existing entries not considering FK constraints - await knex.raw('TRUNCATE tickets, users_groups, groups, users RESTART IDENTITY CASCADE') + await knex.raw( + 'TRUNCATE tickets, users_groups, groups, users RESTART IDENTITY CASCADE' + ) const userCount = 16 const userArray = [] @@ -15,7 +17,7 @@ export async function seed(knex: Knex): Promise { authSchId: faker.datatype.uuid(), role: RoleType.USER, } - console.log('\x1b[33m%s\x1b[0m', `User: #${i+1} ${user.name}`) + console.log('\x1b[33m%s\x1b[0m', `User: #${i + 1} ${user.name}`) userArray.push(user) } diff --git a/seeds/02_add_groups.ts b/seeds/02_add_groups.ts index 215d2480..a1e29c41 100644 --- a/seeds/02_add_groups.ts +++ b/seeds/02_add_groups.ts @@ -2,7 +2,6 @@ import * as Knex from 'knex' import faker from 'faker' export async function seed(knex: Knex): Promise { - const startingFloor = 3 const floorCount = 16 const groupsPerFloor = 4 @@ -12,8 +11,9 @@ export async function seed(knex: Knex): Promise { for (let i = 0; i < floorCount; ++i) { for (let j = 0; j < groupsPerFloor; ++j) { // Compose description - let description = faker.datatype.boolean()? - ('## ' + faker.random.words(5)) : faker.random.words(6) + let description = faker.datatype.boolean() + ? '## ' + faker.random.words(5) + : faker.random.words(6) description += '\n' @@ -21,42 +21,48 @@ export async function seed(knex: Knex): Promise { description += '* ' + faker.lorem.words(3) + '\n' } - // 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 + const ownerId = ((i * groupsPerFloor + j) % 16) + 1 + const groupId = i * groupsPerFloor + j + 1 const group = { 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, } - console.log('\x1b[33m%s\x1b[0m', - `Group: #${groupId} ${group.name}, floor: ${group.room}, owner: ${group.ownerId}`) + console.log( + '\x1b[33m%s\x1b[0m', + `Group: #${groupId} ${group.name}, floor: ${group.room}, owner: ${group.ownerId}` + ) groupArray.push(group) // Connect groups and users const connectOwner = { - userId: (i * groupsPerFloor + j) % 16 + 1, - groupId: (i * groupsPerFloor + j) + 1, + userId: ((i * groupsPerFloor + j) % 16) + 1, + groupId: i * groupsPerFloor + j + 1, } - console.log(`Connect: owner #${connectOwner.userId} to #${connectOwner.groupId}`) + console.log( + `Connect: owner #${connectOwner.userId} to #${connectOwner.groupId}` + ) connectArray.push(connectOwner) connectCountSum++ for (let k = 1; k < ownerId; ++k) { const connect = { userId: k, - groupId + groupId, } console.log(`Connect: user #${connect.userId} to #${connect.groupId}`) connectArray.push(connect) @@ -68,6 +74,12 @@ export async function seed(knex: Knex): Promise { // Inserts seed entries await knex('groups').insert(groupArray) await knex('users_groups').insert(connectArray) - console.log('\x1b[32m%s\x1b[0m', `Inserted ${groupsPerFloor * floorCount} groups into db.`) - console.log('\x1b[32m%s\x1b[0m', `Inserted ${connectCountSum} users_groups into db.`) + console.log( + '\x1b[32m%s\x1b[0m', + `Inserted ${groupsPerFloor * floorCount} groups into db.` + ) + console.log( + '\x1b[32m%s\x1b[0m', + `Inserted ${connectCountSum} users_groups into db.` + ) } diff --git a/seeds/03_add_tickets.ts b/seeds/03_add_tickets.ts index da4c3536..9c63b1f9 100644 --- a/seeds/03_add_tickets.ts +++ b/seeds/03_add_tickets.ts @@ -2,7 +2,6 @@ import * as Knex from 'knex' import faker from 'faker' export async function seed(knex: Knex): Promise { - const statuses = ['SENT', 'IN_PROGRESS', 'DONE', 'ARCHIVED'] const ticketCount = 12 const ticketArray = [] @@ -10,10 +9,10 @@ export async function seed(knex: Knex): Promise { const ticket = { description: faker.lorem.sentences(5), roomNumber: faker.datatype.number(15) + 3, - createdAt: (new Date( Date.now() - faker.datatype.number(500) * 1_000_000 )), - status: statuses[faker.datatype.number(3)] + createdAt: new Date(Date.now() - faker.datatype.number(500) * 1_000_000), + status: statuses[faker.datatype.number(3)], } - console.log('\x1b[33m%s\x1b[0m', `Ticket: #${i+1}`) + console.log('\x1b[33m%s\x1b[0m', `Ticket: #${i + 1}`) ticketArray.push(ticket) } diff --git a/src/app.ts b/src/app.ts index c633b841..ca72e3fa 100644 --- a/src/app.ts +++ b/src/app.ts @@ -32,12 +32,14 @@ app.use(compression()) app.use(cookieParser()) app.use(express.json()) app.use(express.urlencoded({ extended: true })) -app.use(session({ - resave: true, - saveUninitialized: true, - secret: SESSION_SECRET, - cookie: { maxAge: 7 * 1000 * 60 * 60 * 24 } -})) +app.use( + session({ + resave: true, + saveUninitialized: true, + secret: SESSION_SECRET, + cookie: { maxAge: 7 * 1000 * 60 * 60 * 24 }, + }) +) app.use(passport.initialize()) app.use(passport.session()) app.use(lusca.xframe('SAMEORIGIN')) @@ -45,8 +47,8 @@ app.use(lusca.xssProtection(true)) // set up rate limiter: maximum requests per minute const limiter = new RateLimit({ - windowMs: 1*60*1000, // 1 minute - max: 1000 // max number of requests + windowMs: 1 * 60 * 1000, // 1 minute + max: 1000, // max number of requests }) // apply rate limiter to all requests app.use(limiter) @@ -58,11 +60,12 @@ app.use((req, res, next) => { }) app.use((req, _res, next) => { // After successful login, redirect back to the intended page - if (!req.user && + if ( + !req.user && !req.path.match(/^\/auth/) && !req.path.match(/\./) && - !req.path.match(/^\/rooms\/\d\/events$/)) { - + !req.path.match(/^\/rooms\/\d\/events$/) + ) { req.session.returnTo = req.path } next() @@ -99,7 +102,8 @@ app.use('/tickets', ticketRouter) * OAuth authentication routes. (Sign in) */ app.get('/auth/oauth', passport.authenticate('oauth2')) -app.get('/auth/oauth/callback', +app.get( + '/auth/oauth/callback', passport.authenticate('oauth2', { failureRedirect: '/' }), (req, res) => res.redirect(req.session.returnTo || '/') ) diff --git a/src/components/groups/group.middlewares.ts b/src/components/groups/group.middlewares.ts index 10f3c76b..6a0dcb54 100644 --- a/src/components/groups/group.middlewares.ts +++ b/src/components/groups/group.middlewares.ts @@ -11,31 +11,31 @@ import { asyncWrapper } from '../../util/asyncWrapper' import sendMessage from '../../util/sendMessage' import { sendEmail } from '../../util/sendEmail' -export const joinGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const user = req.user - const group = req.group +export const joinGroup = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + const user = req.user + const group = req.group - // 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)){ - 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!') - } else if ((group.users?.length || 0) >= group.maxAttendees) { - sendMessage(res, 'Ez a csoport már tele van!') - } else if (group.endDate < new Date()) { - sendMessage(res, 'Ez a csoport már véget ért!') - } else { - await Group.relatedQuery('users') - .for(group.id) - .relate(user.id) - return next() + // 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) { + 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!') + } else if ((group.users?.length || 0) >= group.maxAttendees) { + sendMessage(res, 'Ez a csoport már tele van!') + } else if (group.endDate < new Date()) { + sendMessage(res, 'Ez a csoport már véget ért!') + } else { + await Group.relatedQuery('users').for(group.id).relate(user.id) + return next() + } + res.redirect(`/groups/${req.params.id}`) } - res.redirect(`/groups/${req.params.id}`) -}) +) export const sendEmailToOwner = asyncWrapper( - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const user = req.user const group = req.group @@ -44,48 +44,56 @@ export const sendEmailToOwner = asyncWrapper( subject: 'Csatlakoztak egy csoportodba!', body: `${user.name} csatlakozott a(z) ${group.name} csoportodba!`, link: `/groups/${group.id}`, - linkTitle: 'Csoport megtekintése' + linkTitle: 'Csoport megtekintése', }) next() - }) -export const leaveGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - await Group.relatedQuery('users') - .for(req.group.id) - .unrelate() - .where('user_id', req.user.id) - - next() -}) + } +) +export const leaveGroup = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + await Group.relatedQuery('users') + .for(req.group.id) + .unrelate() + .where('user_id', req.user.id) -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') } -}) +) -export const kickMember = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - await Group.relatedQuery('users') - .for(req.group.id) - .unrelate() - .where('user_id', req.params.userid) +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') + } + } +) + +export const kickMember = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + await Group.relatedQuery('users') + .for(req.group.id) + .unrelate() + .where('user_id', req.params.userid) - next() -}) + 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!', body: `A(z) ${req.group.name} csoport szervezője vagy egy admin kirúgott a csoportból.`, }) next() - }) + } +) /** * @deprecated use isGroupOwnerOrAdmin instead @@ -102,8 +110,10 @@ export const isGroupOwner = asyncWrapper( export const isGroupOwnerOrAdmin = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { - if ((req.user?.id === req.group.ownerId) - || (req.user?.role == RoleType.ADMIN)) { + if ( + req.user?.id === req.group.ownerId || + req.user?.role == RoleType.ADMIN + ) { next() } else { res.render('error/forbidden') @@ -123,18 +133,18 @@ export const createICSEvent = (req: Request, res: Response): void => { startDate.getMonth() + 1, startDate.getDate(), startDate.getHours(), - startDate.getMinutes() + startDate.getMinutes(), ], end: [ endDate.getFullYear(), endDate.getMonth() + 1, endDate.getDate(), endDate.getHours(), - endDate.getMinutes() + endDate.getMinutes(), ], location: `SCH ${group.room.toString()}. emeleti tanuló`, url: `https://tanulo.sch.bme.hu/groups/${group.id}`, - categories: group.tags ? group.tags.split(',') : null + categories: group.tags ? group.tags.split(',') : null, } ics.createEvent(event, (err, value) => { @@ -162,12 +172,15 @@ 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 @@ -176,7 +189,10 @@ function isValidHttpsUrl(str) { export const validateGroup = (): ValidationChain[] => { return [ - check('name', 'A csoport neve max 100 karakter hosszú nem üres szöveg lehet') + check( + 'name', + 'A csoport neve max 100 karakter hosszú nem üres szöveg lehet' + ) .isString() .exists({ checkNull: true, checkFalsy: true }) .notEmpty() @@ -187,52 +203,65 @@ export const validateGroup = (): ValidationChain[] => { .isString() .custom((value: string) => value.split(',').length <= 8) .withMessage('Max 8 címke adható hozzá') - .custom((value: string) => value.split(',').every(it => it.length <= 30)) + .custom((value: string) => + value.split(',').every((it) => it.length <= 30) + ) .withMessage('A címkék egyenként max 30 karakter hosszúak lehetnek'), check() - .custom((value) => (value.type !== 'floor' || value.room)) + .custom((value) => value.type !== 'floor' || value.room) .withMessage('A szint nem lehet üres') - .custom((value) => (value.type !== 'floor' || !(value.room < 3 || value.room > 18))) + .custom( + (value) => + value.type !== 'floor' || !(value.room < 3 || value.room > 18) + ) .withMessage('A szint csak 3 és 18 közötti értéket vehet fel') - .custom((value) => (value.type !== 'link' || value.link)) + .custom((value) => value.type !== 'link' || value.link) .withMessage('A link megadása kötelező') - .custom((value) => (value.type !== 'link' || value.link.length <= 100)) + .custom((value) => value.type !== 'link' || value.link.length <= 100) .withMessage('A link maximum 100 karakter hosszú lehet') - .custom((value) => (value.type !== 'link' || isValidHttpsUrl(value.link))) + .custom((value) => value.type !== 'link' || isValidHttpsUrl(value.link)) .withMessage('Hibás link (helyes: https://valami.valami)') - .custom((value) => (value.type !== 'palce' || value.place)) + .custom((value) => value.type !== 'palce' || value.place) .withMessage('A hely megadása kötelező') - .custom((value) => (value.type !== 'palce' || value.place.length <= 100)) + .custom((value) => value.type !== 'palce' || value.place.length <= 100) .withMessage('A hely maximum 100 karakter hosszú lehet'), check('startDate') .exists({ checkNull: true, checkFalsy: true }) .isAfter() .withMessage('Múltbéli kezdéssel csoport nem hozható létre') - .custom((value, { req }) => new Date(value).getTime() < new Date(req.body.endDate).getTime()) + .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) + .custom( + (value, { req }) => + 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 }), + check('endDate', 'A befejezés időpontja kötelező').exists({ + checkFalsy: true, + checkNull: true, + }), check('description', 'A leírás max 500 karakter lehet') .optional({ nullable: true }) .isString() .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 }), ] } export const checkValidMaxAttendeeLimit = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { 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'}] - } - ) + res.status(400).json({ + errors: [ + { msg: 'Nem lehet kisebb a maximum jelenlét, mint a jelenlegi' }, + ], + }) } else { next() } @@ -249,14 +278,14 @@ export const checkConflicts = asyncWrapper( group.endDate = new Date(req.body.endDate) const conflictingGroups = await Group.query() .where({ room: group.room }) - .andWhere(builder => { + .andWhere((builder) => { builder - .where(bld => { + .where((bld) => { bld .where('startDate', '<', group.endDate) .andWhere('endDate', '>=', group.endDate) }) - .orWhere(bld => { + .orWhere((bld) => { bld .where('endDate', '>', group.startDate) .andWhere('endDate', '<=', group.endDate) @@ -265,12 +294,11 @@ export const checkConflicts = asyncWrapper( .andWhereNot({ id: req.params.id ?? null }) if (conflictingGroups.length) { - res.status(400).json( - { - errors: conflictingGroups.map(group => - ({ msg: `Az időpont ütközik a(z) ${group.name} csoporttal` })) - } - ) + res.status(400).json({ + errors: conflictingGroups.map((group) => ({ + msg: `Az időpont ütközik a(z) ${group.name} csoporttal`, + })), + }) } else { next() } diff --git a/src/components/groups/group.routes.ts b/src/components/groups/group.routes.ts index 021c2857..31b29ef2 100644 --- a/src/components/groups/group.routes.ts +++ b/src/components/groups/group.routes.ts @@ -1,7 +1,7 @@ import { format, formatDistanceToNowStrict, - formatDistanceStrict + formatDistanceStrict, } from 'date-fns' import huLocale from 'date-fns/locale/hu' import { Request, Response, Router } from 'express' @@ -22,9 +22,15 @@ import { checkConflicts, validateGroup, isGroupOwnerOrAdmin, - checkValidMaxAttendeeLimit + checkValidMaxAttendeeLimit, } from './group.middlewares' -import { createGroup, getGroup, getGroups, updateGroup, removeGroup } from './group.service' +import { + createGroup, + getGroup, + getGroups, + updateGroup, + removeGroup, +} from './group.service' const router = Router() @@ -38,8 +44,8 @@ router.get('/', isAuthenticated, getGroups, (req, res) => { formatDistanceStrict, formatDistanceToNowStrict, huLocale, - DATE_FORMAT - } + DATE_FORMAT, + }, }) }) @@ -48,11 +54,12 @@ 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, - ROOMS + ROOMS, }) ) -router.post('/', +router.post( + '/', isAuthenticated, multer().none(), validateGroup(), @@ -63,20 +70,22 @@ router.post('/', (req: Request, res: Response) => res.status(201).json({ id: req.group.id }) ) -router.get('/:id', - isAuthenticated, - checkIdParam, - getGroup, - (req, res) => { - const joined = req.group.users.some(u => u.id === req.user.id) - const isOwner = req.group.ownerId === req.user.id - const isAdmin = req.user.role == RoleType.ADMIN - res.render('group/show', { - group: req.group, joined, isOwner, format, DATE_FORMAT, isAdmin - }) +router.get('/:id', isAuthenticated, checkIdParam, getGroup, (req, res) => { + const joined = req.group.users.some((u) => u.id === req.user.id) + const isOwner = req.group.ownerId === req.user.id + const isAdmin = req.user.role == RoleType.ADMIN + res.render('group/show', { + group: req.group, + joined, + isOwner, + format, + DATE_FORMAT, + isAdmin, }) +}) -router.post('/:id/join', +router.post( + '/:id/join', isAuthenticated, getGroup, joinGroup, @@ -84,13 +93,11 @@ router.post('/:id/join', (req, res) => res.redirect(`/groups/${req.params.id}`) ) -router.post('/:id/leave', - isAuthenticated, - getGroup, - leaveGroup, - (req, res) => res.redirect('/groups') +router.post('/:id/leave', isAuthenticated, getGroup, leaveGroup, (req, res) => + res.redirect('/groups') ) -router.post('/:id/kick/:userid', +router.post( + '/:id/kick/:userid', isAuthenticated, getGroup, isGroupOwnerOrAdmin, @@ -100,7 +107,8 @@ router.post('/:id/kick/:userid', (req, res) => res.redirect(`/groups/${req.params.id}`) ) -router.delete('/:id', +router.delete( + '/:id', isAuthenticated, getGroup, isGroupOwnerOrAdmin, @@ -108,23 +116,20 @@ router.delete('/:id', (req, res) => res.status(204).send('Csoport sikeresen törölve') ) -router.get('/:id/copy', - isAuthenticated, - checkIdParam, - getGroup, - (req, res) => - res.render('group/new', { - roomId: req.group.room, - link: req.group.link, - place: req.group.place, - name: req.group.name, - description: req.group.description, - tags: req.group.tags, - ROOMS - }) +router.get('/:id/copy', isAuthenticated, checkIdParam, getGroup, (req, res) => + res.render('group/new', { + roomId: req.group.room, + link: req.group.link, + place: req.group.place, + name: req.group.name, + description: req.group.description, + tags: req.group.tags, + ROOMS, + }) ) -router.get('/:id/edit', +router.get( + '/:id/edit', isAuthenticated, checkIdParam, getGroup, @@ -143,11 +148,12 @@ router.get('/:id/edit', ROOMS, isEditing: true, groupId: req.group.id, - maxAttendees: req.group.maxAttendees + maxAttendees: req.group.maxAttendees, }) ) -router.put('/:id', +router.put( + '/:id', isAuthenticated, checkIdParam, getGroup, @@ -161,6 +167,12 @@ router.put('/:id', (req: Request, res: Response) => res.status(201).json({ id: req.group.id }) ) -router.get('/:id/export', isAuthenticated, checkIdParam, getGroup, createICSEvent) +router.get( + '/:id/export', + isAuthenticated, + checkIdParam, + getGroup, + createICSEvent +) export default router diff --git a/src/components/groups/group.service.ts b/src/components/groups/group.service.ts index bb4ac8cb..3215a64d 100644 --- a/src/components/groups/group.service.ts +++ b/src/components/groups/group.service.ts @@ -4,69 +4,60 @@ import { Group } from './group' 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 = 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) - return group - }) - - req.paginationOptions = { - pageNum: Math.ceil(pageObject.total / limit), - current: page - } - next() -}) - -export const getGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const group = await Group.query() - .findOne({ id: parseInt(req.params.id) }) - .withGraphFetched('users') +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 = + 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) + return group + }) - 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.paginationOptions = { + pageNum: Math.ceil(pageObject.total / limit), + current: page, + } next() - } else { - res.render('error/not-found') } -}) +) -export const createGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - req.group = await Group.query() - .insert( - { - name: req.body.name, - tags: req.body.tags ?? '', - room: req.body.room ? parseInt(req.body.room) : null, - link: req.body.link, - place: req.body.place, - description: req.body.description, - doNotDisturb: !!req.body.doNotDisturb, - startDate: new Date(req.body.startDate), - endDate: new Date(req.body.endDate), - ownerId: req.user.id, - maxAttendees: parseInt(req.body.maxAttendees) || 100 - } - ) +export const getGroup = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + const group = await Group.query() + .findOne({ id: parseInt(req.params.id) }) + .withGraphFetched('users') - next() -}) - -export const updateGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { + 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 + next() + } else { + res.render('error/not-found') + } + } +) - await Group.query() - .patch({ +export const createGroup = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + req.group = await Group.query().insert({ name: req.body.name, tags: req.body.tags ?? '', room: req.body.room ? parseInt(req.body.room) : null, @@ -76,26 +67,47 @@ 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 - }) - .findById(req.params.id) - .catch((err) => { - console.log(err) - return next(err) + ownerId: req.user.id, + maxAttendees: parseInt(req.body.maxAttendees) || 100, }) - next() + next() + } +) + +export const updateGroup = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + await Group.query() + .patch({ + name: req.body.name, + tags: req.body.tags ?? '', + room: req.body.room ? parseInt(req.body.room) : null, + link: req.body.link, + place: req.body.place, + description: req.body.description, + doNotDisturb: !!req.body.doNotDisturb, + startDate: new Date(req.body.startDate), + endDate: new Date(req.body.endDate), + maxAttendees: parseInt(req.body.maxAttendees) || 100, + }) + .findById(req.params.id) + .catch((err) => { + console.log(err) + return next(err) + }) -}) + next() + } +) -export const removeGroup = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - await Group.transaction(async trx => { - await Group.relatedQuery('users', trx) - .for(req.group.id) - .unrelate() +export const removeGroup = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + await Group.transaction(async (trx) => { + await Group.relatedQuery('users', trx).for(req.group.id).unrelate() - await Group.query(trx).deleteById(req.group.id) - }) + await Group.query(trx).deleteById(req.group.id) + }) - next() -}) + next() + } +) diff --git a/src/components/groups/group.ts b/src/components/groups/group.ts index 598d2469..23b4f3f1 100644 --- a/src/components/groups/group.ts +++ b/src/components/groups/group.ts @@ -38,11 +38,11 @@ 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', }, - to: 'users.id' - } - } + to: 'users.id', + }, + }, } } @@ -50,7 +50,14 @@ export class Group extends Model { static get jsonSchema(): Record { return { type: 'object', - required: ['name', 'description', 'doNotDisturb', 'tags', 'startDate', 'endDate'], + required: [ + 'name', + 'description', + 'doNotDisturb', + 'tags', + 'startDate', + 'endDate', + ], properties: { id: { type: 'integer' }, @@ -63,8 +70,8 @@ export class Group extends Model { place: { type: 'string' }, startDate: { type: 'datetime' }, endDate: { type: 'datetime' }, - maxAttendees: { type: 'integer' } - } + maxAttendees: { type: 'integer' }, + }, } } } diff --git a/src/components/rooms/room.routes.ts b/src/components/rooms/room.routes.ts index 73a112f6..3101467e 100644 --- a/src/components/rooms/room.routes.ts +++ b/src/components/rooms/room.routes.ts @@ -22,11 +22,11 @@ const show = asyncWrapper(async (req: Request, res: Response) => { const getGroupsForRoom = asyncWrapper(async (req: Request, res: Response) => { const events = await getEventsForRoom(+req.params.id) res.json( - events.map(event => ({ + events.map((event) => ({ title: event.name, start: event.startDate, end: event.endDate, - groupId: event.id + groupId: event.id, })) ) }) diff --git a/src/components/rooms/room.service.ts b/src/components/rooms/room.service.ts index 2608c448..552eb64e 100644 --- a/src/components/rooms/room.service.ts +++ b/src/components/rooms/room.service.ts @@ -4,16 +4,19 @@ import { DAYS_OF_WEEK, ROOMS } from '../../util/constants' import { RawUsageData } from './rawusagedata' import { raw } from 'objection' -type ParsedUsageData = Map +type ParsedUsageData = Map< + number, + { + day: typeof DAYS_OF_WEEK[number] + count: number + }[] +> export const getBusyRooms = async (): Promise => { const currentTime = new Date() - return (await Group.query() + return await Group.query() .where('startDate', '<', currentTime) - .andWhere('endDate', '>', currentTime)) + .andWhere('endDate', '>', currentTime) } export const getEventsForRoom = async (roomId: number): Promise => @@ -24,8 +27,7 @@ const fetchUsageData = async (start: Date, end: Date) => { // meeting starts before midnight, ends after // The event will not be registrated for the next day - return RawUsageData - .query() + return RawUsageData.query() .select('room') .select({ day: raw('date(start_date)') }) .count() @@ -35,15 +37,20 @@ const fetchUsageData = async (start: Date, end: Date) => { .groupBy('day') as Promise } -const parseUsageData = (rawData: RawUsageData[], today: Date): ParsedUsageData => { - - const result = new Map() +const parseUsageData = ( + rawData: RawUsageData[], + today: Date +): ParsedUsageData => { + const result = new Map< + number, + { day: typeof DAYS_OF_WEEK[number]; count: number }[] + >() // generate empty data for (const room of ROOMS) { const roomResult = DAYS_OF_WEEK.map((_, d) => ({ day: DAYS_OF_WEEK[addDays(today, d).getDay()], - count: 0 + count: 0, })) result.set(room, roomResult) } diff --git a/src/components/tickets/ticket.routes.ts b/src/components/tickets/ticket.routes.ts index 3a223594..0541c125 100644 --- a/src/components/tickets/ticket.routes.ts +++ b/src/components/tickets/ticket.routes.ts @@ -5,29 +5,37 @@ import multer from 'multer' import { isAuthenticated, requireRoles } from '../../config/passport' import { DATE_FORMAT, ROOMS, STATUSES } from '../../util/constants' -import { createTicket, sendEmailToTicketAdmins, getOtherTickets, getMyTickets, - moveTicket, removeTicket, checkTicketOwner } from './ticket.service' +import { + createTicket, + sendEmailToTicketAdmins, + getOtherTickets, + getMyTickets, + moveTicket, + removeTicket, + checkTicketOwner, +} from './ticket.service' import { handleValidationError } from '../../util/validators' import { RoleType } from '../users/user' const router = Router() -router.get('/', - isAuthenticated, - getOtherTickets, - getMyTickets, (req, res) => - res.render('ticket/index', { - otherTickets: req.otherTickets, - myTickets: req.myTickets, - format, - DATE_FORMAT, - STATUSES - })) +router.get('/', isAuthenticated, getOtherTickets, getMyTickets, (req, res) => + res.render('ticket/index', { + otherTickets: req.otherTickets, + myTickets: req.myTickets, + format, + DATE_FORMAT, + STATUSES, + }) +) -router.get('/new', isAuthenticated, (_req, res) => res.render('ticket/new', { ROOMS })) +router.get('/new', isAuthenticated, (_req, res) => + res.render('ticket/new', { ROOMS }) +) -router.put('/:id', +router.put( + '/:id', isAuthenticated, requireRoles(RoleType.ADMIN, RoleType.TICKET_ADMIN), multer().none(), @@ -35,23 +43,28 @@ router.put('/:id', check('status') .notEmpty() .withMessage('A státusz nem lehet üres') - .isString() + .isString(), ], handleValidationError(400), - moveTicket, (req, res) => - res.json(STATUSES.get(req.body.status)) + moveTicket, + (req, res) => res.json(STATUSES.get(req.body.status)) ) -router.post('/', +router.post( + '/', isAuthenticated, multer().none(), [ check('roomNumber') - .notEmpty() .withMessage('A szint nem lehet üres') - .isInt({ gt: 2, lt: 19 }).withMessage('A szint csak 3 és 18 közötti értéket vehet fel'), + .notEmpty() + .withMessage('A szint nem lehet üres') + .isInt({ gt: 2, lt: 19 }) + .withMessage('A szint csak 3 és 18 közötti értéket vehet fel'), check('description') - .notEmpty() .withMessage('A leírás nem lehet üres') - .isLength({ max: 500 }) .withMessage('A leírás max 500 karakter lehet') + .notEmpty() + .withMessage('A leírás nem lehet üres') + .isLength({ max: 500 }) + .withMessage('A leírás max 500 karakter lehet'), ], handleValidationError(400), createTicket, @@ -59,16 +72,20 @@ router.post('/', (_req: Request, res: Response) => res.sendStatus(201) ) -router.delete('/:id', +router.delete( + '/:id', isAuthenticated, requireRoles(RoleType.ADMIN, RoleType.TICKET_ADMIN), - removeTicket, (_req, res) => res.status(204).send('A hibajegy sikeresen törölve') + removeTicket, + (_req, res) => res.status(204).send('A hibajegy sikeresen törölve') ) -router.delete('/own/:id', +router.delete( + '/own/:id', isAuthenticated, checkTicketOwner, - removeTicket, (_req, res) => res.status(204).send('A hibajegy sikeresen törölve') + removeTicket, + (_req, res) => res.status(204).send('A hibajegy sikeresen törölve') ) export default router diff --git a/src/components/tickets/ticket.service.ts b/src/components/tickets/ticket.service.ts index ccba89b0..2be10895 100644 --- a/src/components/tickets/ticket.service.ts +++ b/src/components/tickets/ticket.service.ts @@ -10,60 +10,72 @@ 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.id) - .orderBy('createdAt', 'ASC')).map(ticket => { + req.otherTickets = ( + await Ticket.query() + .where('userId', '!=', req.user.id) + .orderBy('createdAt', 'ASC') + ).map((ticket) => { ticket.description = formatMdToSafeHTML(ticket.description) return ticket }) next() - }) + } +) export const getMyTickets = asyncWrapper( async (req: Request, _res: Response, next: NextFunction) => { - req.myTickets = (await Ticket.query().where('userId', '=', req.user.id) - .orderBy('createdAt', 'ASC')).map(ticket => { + req.myTickets = ( + await Ticket.query() + .where('userId', '=', req.user.id) + .orderBy('createdAt', 'ASC') + ).map((ticket) => { ticket.description = formatMdToSafeHTML(ticket.description) return ticket }) next() - }) + } +) export const createTicket = asyncWrapper( async (req: Request, _res: Response, next: NextFunction) => { - await Ticket.transaction(async trx => { - return await Ticket.query(trx) - .insert( - { - roomNumber: +req.body.roomNumber, - description: req.body.description, - userId: req.user.id, - } - ) + await Ticket.transaction(async (trx) => { + return await Ticket.query(trx).insert({ + roomNumber: +req.body.roomNumber, + description: req.body.description, + userId: req.user.id, + }) }) next() - }) + } +) export const sendEmailToTicketAdmins = asyncWrapper( - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const ticket = req.body - const emailRecepients = await User.query().where({ role: RoleType.TICKET_ADMIN }) + const emailRecepients = await User.query().where({ + role: RoleType.TICKET_ADMIN, + }) sendEmail(emailRecepients, { subject: `Új hibajegyet vettek fel a ${ticket.roomNumber}. emeleti tanulószobába!`, body: `Új hibajegyet vettek fel a ${ticket.roomNumber}. emeleti tanulószobába! A hibajegy tartalma: "${ticket.description}"`, link: '/tickets', - linkTitle: 'Hibajegy megtekintése' + linkTitle: 'Hibajegy megtekintése', }) next() - }) + } +) export const moveTicket = asyncWrapper( async (req: Request, _res: Response, next: NextFunction) => { const id = parseInt(req.params.id) - await Ticket.query().findById(id).patch({ status: req.body.status || StatusType.SENT }) + await Ticket.query() + .findById(id) + .patch({ status: req.body.status || StatusType.SENT }) next() - }) + } +) export const removeTicket = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { @@ -72,14 +84,17 @@ export const removeTicket = asyncWrapper( const deletedCount = await Ticket.query().deleteById(id) if (deletedCount === 0) { - res.status(404).send({ message: 'Nem található hibajegy a megadott ID-val' }) + res + .status(404) + .send({ message: 'Nem található hibajegy a megadott ID-val' }) } else { next() } } else { res.status(404).send({ message: 'A megadott ID nem megfelelő formátumú' }) } - }) + } +) export const checkTicketOwner = asyncWrapper( async (req: Request, res: Response, next: NextFunction) => { @@ -89,4 +104,5 @@ export const checkTicketOwner = asyncWrapper( } else { return res.sendStatus(403) } - }) + } +) diff --git a/src/components/tickets/ticket.ts b/src/components/tickets/ticket.ts index 2385055d..b57f0311 100644 --- a/src/components/tickets/ticket.ts +++ b/src/components/tickets/ticket.ts @@ -4,7 +4,7 @@ export enum StatusType { SENT = 'SENT', IN_PROGRESS = 'IN_PROGRESS', DONE = 'DONE', - ARCHIVED ='ARCHIVED' + ARCHIVED = 'ARCHIVED', } export class Ticket extends Model { @@ -31,11 +31,11 @@ export class Ticket extends Model { properties: { id: { type: 'integer' }, - description: { type: 'string' , maxLenght: 500 }, + description: { type: 'string', maxLenght: 500 }, roomNumber: { type: 'integer' }, status: { type: 'string' }, userId: { type: 'integer' }, - } + }, } } } diff --git a/src/components/users/user.middlewares.ts b/src/components/users/user.middlewares.ts index aaef629c..40f38a09 100644 --- a/src/components/users/user.middlewares.ts +++ b/src/components/users/user.middlewares.ts @@ -1,8 +1,14 @@ import { Request, Response, NextFunction } from 'express' -export const isSameUser = (req: Request, res: Response, next: NextFunction): void => { +export const isSameUser = ( + req: Request, + res: Response, + next: NextFunction +): void => { if (parseInt(req.params.id) !== req.user.id) { - res.status(400).json({ errors: [{ msg: 'Nem találahtó felhasználó a megadott ID-val' }] }) + 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 488c5e42..b3dea7e0 100644 --- a/src/components/users/user.routes.ts +++ b/src/components/users/user.routes.ts @@ -13,17 +13,17 @@ const router = Router() router.get('/:id', isAuthenticated, checkIdParam, getUser, (req, res) => res.render('user/show', { userToShow: req.userToShow, - ROLES: ROLES + ROLES: ROLES, }) ) -router.patch('/:id/role', +router.patch( + '/:id/role', requireRoles(RoleType.ADMIN), check('role') .isString() .custom((input) => { - return [...ROLES.keys()] - .some((element) => element == input) + return [...ROLES.keys()].some((element) => element == input) }) .withMessage('Nem megfelelő role!'), handleValidationError(400), @@ -31,7 +31,8 @@ router.patch('/:id/role', (req, res) => res.sendStatus(201) ) -router.patch('/:id', +router.patch( + '/:id', isAuthenticated, (req, res, next) => { if (req.user.id !== parseInt(req.params.id)) { diff --git a/src/components/users/user.service.ts b/src/components/users/user.service.ts index 0d00740a..d442eb10 100644 --- a/src/components/users/user.service.ts +++ b/src/components/users/user.service.ts @@ -9,55 +9,56 @@ interface OAuthUser { mail: string } -export const getUser = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const user = await User.query() - .findOne({ id: parseInt(req.params.id) }) - .withGraphFetched('groups(orderByEndDate)') - .modifiers({ - orderByEndDate(builder) { - builder.orderBy('endDate', 'DESC') - } - }) +export const getUser = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + const user = await User.query() + .findOne({ id: parseInt(req.params.id) }) + .withGraphFetched('groups(orderByEndDate)') + .modifiers({ + orderByEndDate(builder) { + builder.orderBy('endDate', 'DESC') + }, + }) - if (!user) { - res.render('error/not-found') - } else { - req.userToShow = user - next() + if (!user) { + res.render('error/not-found') + } else { + req.userToShow = user + next() + } } -}) +) -export const updateRole = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const user = await User.query().findOne({ id: parseInt(req.params.id) }) +export const updateRole = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + const user = await User.query().findOne({ id: parseInt(req.params.id) }) - if (!user) { - res.redirect('/not-found') - } else { - await User.query() - .patch({ role: req.body.role }) - .where({ id: user.id }) - next() + if (!user) { + res.redirect('/not-found') + } else { + await User.query().patch({ role: req.body.role }).where({ id: user.id }) + next() + } } -}) +) -export const updateUser = asyncWrapper(async (req: Request, res: Response, next: NextFunction) => { - const id = req.user.id - const { floor, wantEmail } = req.body - req.user = await User.query().patchAndFetchById(id, { floor, wantEmail }) +export const updateUser = asyncWrapper( + async (req: Request, res: Response, next: NextFunction) => { + const id = req.user.id + const { floor, wantEmail } = req.body + req.user = await User.query().patchAndFetchById(id, { floor, wantEmail }) - next() -}) + next() + } +) export const createUser = async (user: OAuthUser): Promise => { - return await User.transaction(async trx => { - return await User.query(trx) - .insert( - { - name: user.displayName, - email: user.mail, - authSchId: user.internal_id, - role: RoleType.USER - } - ) + return await User.transaction(async (trx) => { + return await User.query(trx).insert({ + name: user.displayName, + email: user.mail, + authSchId: user.internal_id, + role: RoleType.USER, + }) }) } diff --git a/src/components/users/user.ts b/src/components/users/user.ts index d0fd1cbc..439bf8a6 100644 --- a/src/components/users/user.ts +++ b/src/components/users/user.ts @@ -8,7 +8,6 @@ export enum RoleType { USER = 'USER', } - export class User extends Model { id!: number name: string @@ -33,11 +32,11 @@ export class User extends Model { from: 'users.id', through: { from: 'users_groups.userId', - to: 'users_groups.groupId' + to: 'users_groups.groupId', }, - to: 'groups.id' - } - } + to: 'groups.id', + }, + }, } } @@ -52,8 +51,8 @@ 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' }, + }, } } } diff --git a/src/config/email.ts b/src/config/email.ts index 7db65cc5..fbdfe020 100644 --- a/src/config/email.ts +++ b/src/config/email.ts @@ -6,7 +6,7 @@ const transporter = nodemailer.createTransport({ secure: false, // upgrade later with STARTTLS auth: { user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PWD + pass: process.env.EMAIL_PWD, }, }) diff --git a/src/config/passport.ts b/src/config/passport.ts index ff2abbc4..e22f8964 100644 --- a/src/config/passport.ts +++ b/src/config/passport.ts @@ -19,7 +19,7 @@ passport.use( clientID: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, callbackURL: '/auth/oauth/callback', - scope: ['basic', 'displayName', 'mail'] + scope: ['basic', 'displayName', 'mail'], }, async ( accessToken: string, @@ -29,9 +29,11 @@ passport.use( ) => { const responseUser = await fetch( `${AUTH_SCH_URL}/api/profile?access_token=${accessToken}` - ).then(res => res.json()) + ).then((res) => res.json()) - const user = await User.query().findOne({ authSchId: responseUser.internal_id }) + const user = await User.query().findOne({ + authSchId: responseUser.internal_id, + }) if (user) { done(null, user) @@ -55,18 +57,23 @@ passport.deserializeUser(async (id: number, done) => { /** * Login Required middleware. */ -export const isAuthenticated = -/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -(req: Request, res: Response, next: NextFunction): Response> => { +export const isAuthenticated = ( + req: Request, + res: Response, + next: NextFunction + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ +): Response> => { const contentType = req.headers['content-type'] if (req.isAuthenticated()) { next() } else { - if ((contentType && - (contentType.indexOf('application/json') !== 0 || - contentType.indexOf('multipart/form-data') !== 0)) || - req.method !== 'GET') { + if ( + (contentType && + (contentType.indexOf('application/json') !== 0 || + contentType.indexOf('multipart/form-data') !== 0)) || + req.method !== 'GET' + ) { return res.sendStatus(401) } res.render('error/not-authenticated') @@ -77,8 +84,12 @@ export const isAuthenticated = * Authorization Required middleware. */ export const requireRoles = (...roles: RoleType[]) => { - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - return (req: Request, res: Response, next: NextFunction): Response> => { + return ( + req: Request, + res: Response, + next: NextFunction + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + ): Response> => { const role = req.user?.role if (roles.some((element) => role == element)) { next() diff --git a/src/util/asyncWrapper.ts b/src/util/asyncWrapper.ts index 1f09ab02..3059e46c 100644 --- a/src/util/asyncWrapper.ts +++ b/src/util/asyncWrapper.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Request, Response , NextFunction } from 'express' +import { Request, Response, NextFunction } from 'express' -export const asyncWrapper = (fn: (...params: any[]) => any) => +export const asyncWrapper = + (fn: (...params: any[]) => any) => (req: Request, res: Response, next: NextFunction): Promise => - Promise - .resolve(fn(req, res, next)) - .catch(next) + Promise.resolve(fn(req, res, next)).catch(next) diff --git a/src/util/constants.ts b/src/util/constants.ts index 8d63bbaa..38cdd481 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -1,12 +1,22 @@ -export const ROOMS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] as const -export const DAYS_OF_WEEK = ['Va', 'Hé', 'Ke', 'Sze', 'Csü', 'Pé', 'Szo'] as const +export const ROOMS = [ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, +] as const +export const DAYS_OF_WEEK = [ + 'Va', + 'Hé', + 'Ke', + 'Sze', + 'Csü', + 'Pé', + 'Szo', +] as const export const DATE_FORMAT = 'yyyy-MM-dd HH:mm' export const STATUSES = new Map([ ['SENT', 'Elküldve'], ['IN_PROGRESS', 'Folyamatban'], ['DONE', 'Kész'], - ['ARCHIVED', 'Archiválva'] + ['ARCHIVED', 'Archiválva'], ]) export const ROLES = new Map([ diff --git a/src/util/convertMarkdown.ts b/src/util/convertMarkdown.ts index c0d3ce72..4927b7d7 100644 --- a/src/util/convertMarkdown.ts +++ b/src/util/convertMarkdown.ts @@ -5,11 +5,11 @@ export function formatMdToSafeHTML(mdBody: string): string { const dirtyHtml = new markdown({ html: false, breaks: true }).render(mdBody) const cleanHtml = sanitizeHtml(dirtyHtml, { transformTags: { - 'h1': 'h3', - 'h2': 'h4', - 'h3': 'h5', - 'h4': 'h5' - } + h1: 'h3', + h2: 'h4', + h3: 'h5', + h4: 'h5', + }, }) return cleanHtml } diff --git a/src/util/emailTemplate.ts b/src/util/emailTemplate.ts index b718de8b..a21c2144 100644 --- a/src/util/emailTemplate.ts +++ b/src/util/emailTemplate.ts @@ -37,7 +37,7 @@ const styles = { width: max-content;"`, smallText: `"font-size: 12px; margin-top:2rem; - margin-bottom: 0px;"` + margin-bottom: 0px;"`, } export const generateEmailHTML = (user: User, email: Email): string => { @@ -49,13 +49,17 @@ export const generateEmailHTML = (user: User, email: Email): string => {

Kedves ${user.name}!

${email.body}

- + ${email.linkTitle || 'TanulóSCH'}

Ez egy automatikusan gerenált üzenet, kérjük ne válaszolj rá!
Ha nem szeretnél több emailt kapni, akkor ezt a - profilodon beállíthatod.
+ profilodon beállíthatod.
Kérdés esetén írj a kir-dev@sch.bme.hu címre!

diff --git a/src/util/logger.ts b/src/util/logger.ts index 36ee1fd3..561a290c 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -3,10 +3,10 @@ import winston from 'winston' const options: winston.LoggerOptions = { transports: [ new winston.transports.Console({ - level: process.env.NODE_ENV === 'production' ? 'error' : 'debug' + level: process.env.NODE_ENV === 'production' ? 'error' : 'debug', }), - new winston.transports.File({ filename: 'debug.log', level: 'debug' }) - ] + new winston.transports.File({ filename: 'debug.log', level: 'debug' }), + ], } const logger = winston.createLogger(options) diff --git a/src/util/sendEmail.ts b/src/util/sendEmail.ts index c136417a..888bd37c 100644 --- a/src/util/sendEmail.ts +++ b/src/util/sendEmail.ts @@ -11,18 +11,23 @@ export interface Email { export const sendEmail = (recipients: User[], email: Email): void => { if (process.env.NODE_ENV === 'production') { - recipients.filter(user => user.wantEmail).forEach(user => { - transporter.sendMail({ - from: `TanulóSCH <${process.env.EMAIL_USER}>`, - to: user.email, - subject: email.subject, - text: email.body, - html: generateEmailHTML(user, email) - }, (err) => { - if (err) { - console.log(err) - } + recipients + .filter((user) => user.wantEmail) + .forEach((user) => { + transporter.sendMail( + { + from: `TanulóSCH <${process.env.EMAIL_USER}>`, + to: user.email, + subject: email.subject, + text: email.body, + html: generateEmailHTML(user, email), + }, + (err) => { + if (err) { + console.log(err) + } + } + ) }) - }) } } diff --git a/src/util/sendMessage.ts b/src/util/sendMessage.ts index dd4b7967..b2de0e48 100644 --- a/src/util/sendMessage.ts +++ b/src/util/sendMessage.ts @@ -2,10 +2,18 @@ import { Response } from 'express' type MessageType = 'danger' | 'success' | 'warning' -const sendMessage = (res: Response, message: string, type: MessageType = 'danger'): void => { - res.cookie('message', JSON.stringify({ - mes: message, - type: type, - }), { path: '/' }) +const sendMessage = ( + res: Response, + message: string, + type: MessageType = 'danger' +): void => { + res.cookie( + 'message', + JSON.stringify({ + mes: message, + type: type, + }), + { path: '/' } + ) } export default sendMessage diff --git a/src/util/validators.ts b/src/util/validators.ts index cab0d33b..fa88b4a1 100644 --- a/src/util/validators.ts +++ b/src/util/validators.ts @@ -1,7 +1,11 @@ import { Request, Response, NextFunction } from 'express' import { validationResult } from 'express-validator' -type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void +type ExpressMiddleware = ( + req: Request, + res: Response, + next: NextFunction +) => void export interface ValidationError { msg: string @@ -18,7 +22,11 @@ export function handleValidationError(statusCode: number): ExpressMiddleware { } } -export function checkIdParam(req: Request, res: Response, next: NextFunction): void { +export function checkIdParam( + req: Request, + res: Response, + next: NextFunction +): void { if (isNaN(parseInt(req.params.id))) { res.render('error/not-found') } else { diff --git a/tailwind.config.js b/tailwind.config.js index 504cc4b2..4eac0da8 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,7 +4,7 @@ const colors = require('tailwindcss/colors') module.exports = { mode: 'jit', darkMode: 'class', - purge: ['./views/**/*.pug', './public/**/*.js'], + purge: ['./views/**/*.pug', './public/**/*.js'], theme: { extend: { //alert animation @@ -12,20 +12,19 @@ module.exports = { 'fade-in-down': { '0%': { opacity: '0', - transform: 'translateY(-30px)' + transform: 'translateY(-30px)', }, '100%': { opacity: '1', - transform: 'translateY(0)' + transform: 'translateY(0)', }, - } + }, }, animation: { - 'fade-in-down': 'fade-in-down 0.8s ease-out' + 'fade-in-down': 'fade-in-down 0.8s ease-out', }, boxShadow: { - full: - '0px 4px 32px rgba(0, 0, 0, 0.3), 4px 0px 4px rgba(0, 0, 0, 0.1);', + full: '0px 4px 32px rgba(0, 0, 0, 0.3), 4px 0px 4px rgba(0, 0, 0, 0.1);', }, colors: { 'indigo-1000': '#211f5b', @@ -46,44 +45,41 @@ module.exports = { }, h1: { color: 'white', - 'margin-top': '0' + 'margin-top': '0', }, h2: { color: 'white', - 'margin-top': '0' + 'margin-top': '0', }, h3: { color: 'white', - 'margin-top': '0' + 'margin-top': '0', }, h4: { color: 'white', - 'margin-top': '0' + 'margin-top': '0', }, p: { color: 'white', 'margin-top': '0', - 'margin-bottom': '1em' + 'margin-bottom': '1em', }, 'ul > li': { '&::before': { 'background-color': 'white', - 'font-weight': 'bold' - } + 'font-weight': 'bold', + }, }, 'ol > li': { '&::before': { color: 'white', - 'font-weight': 'bold' - } - } - } - } - }) + 'font-weight': 'bold', + }, + }, + }, + }, + }), }, }, - plugins: [ - require('@tailwindcss/typography'), - require('@tailwindcss/forms'), - ], + plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')], } diff --git a/tsconfig.json b/tsconfig.json index b9b3158e..4cf05c36 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,5 @@ } }, "include": ["src/**/*", "public/js"], - "files": [ - "src/types/index.d.ts" - ] + "files": ["src/types/index.d.ts"] } diff --git a/yarn.lock b/yarn.lock index c571898a..b71083b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -253,6 +253,11 @@ dependencies: "@types/express" "*" +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== + "@types/express-serve-static-core@^4.17.18": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" @@ -302,6 +307,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/json-schema@^7.0.3": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + "@types/json-schema@^7.0.7": version "7.0.8" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" @@ -483,6 +493,17 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/experimental-utils@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" + integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + "@typescript-eslint/experimental-utils@4.29.2": version "4.29.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.2.tgz#5f67fb5c5757ef2cb3be64817468ba35c9d4e3b7" @@ -505,6 +526,17 @@ "@typescript-eslint/typescript-estree" "4.29.2" debug "^4.3.1" +"@typescript-eslint/parser@^3.0.0": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" + integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "3.10.1" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" + eslint-visitor-keys "^1.1.0" + "@typescript-eslint/scope-manager@4.29.2": version "4.29.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz#442b0f029d981fa402942715b1718ac7fcd5aa1b" @@ -513,11 +545,30 @@ "@typescript-eslint/types" "4.29.2" "@typescript-eslint/visitor-keys" "4.29.2" +"@typescript-eslint/types@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" + integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== + "@typescript-eslint/types@4.29.2": version "4.29.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.2.tgz#fc0489c6b89773f99109fb0aa0aaddff21f52fcd" integrity sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ== +"@typescript-eslint/typescript-estree@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" + integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== + dependencies: + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/visitor-keys" "3.10.1" + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + "@typescript-eslint/typescript-estree@4.29.2": version "4.29.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz#a0ea8b98b274adbb2577100ba545ddf8bf7dc219" @@ -531,6 +582,13 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/visitor-keys@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" + integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== + dependencies: + eslint-visitor-keys "^1.1.0" + "@typescript-eslint/visitor-keys@4.29.2": version "4.29.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz#d2da7341f3519486f50655159f4e5ecdcb2cd1df" @@ -552,7 +610,7 @@ accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-jsx@^5.3.1: +acorn-jsx@^5.2.0, acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -633,6 +691,16 @@ ansi-escapes@^4.3.0: dependencies: type-fest "^0.21.3" +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -643,6 +711,11 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1061,6 +1134,17 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1307,7 +1391,7 @@ commander@^6.0.0, commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -common-tags@^1.8.0: +common-tags@^1.4.0, common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== @@ -1734,7 +1818,7 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dlv@^1.1.3: +dlv@^1.1.0, dlv@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== @@ -1905,7 +1989,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -1915,7 +1999,19 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-scope@^5.1.1: +eslint-config-prettier@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + +eslint-plugin-prettier@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5" + integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^5.0.0, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -1923,7 +2019,7 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: +eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== @@ -1947,7 +2043,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@7.32.0: +eslint@7.32.0, eslint@^7.9.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -1998,6 +2094,15 @@ esm@^3.2.25: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== +espree@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -2012,7 +2117,7 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: +esquery@^1.0.1, esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== @@ -2221,6 +2326,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -2565,7 +2675,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.0.0, glob@^7.1.3: +glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== @@ -2679,6 +2789,13 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -3483,7 +3600,7 @@ lodash.isplainobject@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= -lodash.merge@^4.6.2: +lodash.merge@^4.6.0, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== @@ -3523,7 +3640,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.20, lodash@^4.17.21: +lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3557,6 +3674,19 @@ logform@^2.2.0: ms "^2.1.1" triple-beam "^1.3.0" +loglevel-colored-level-prefix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e" + integrity sha1-akAhj9x64V/HbD0PPmdsRlOIYD4= + dependencies: + chalk "^1.1.3" + loglevel "^1.4.1" + +loglevel@^1.4.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" + integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== + lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -4456,11 +4586,49 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prettier-eslint@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-13.0.0.tgz#769f5c04057278d071847d893ebc7b9817594184" + integrity sha512-P5K31qWgUOQCtJL/3tpvEe28KfP49qbr6MTVEXC7I2k7ci55bP3YDr+glhyCdhIzxGCVp2f8eobfQ5so52RIIA== + dependencies: + "@typescript-eslint/parser" "^3.0.0" + common-tags "^1.4.0" + dlv "^1.1.0" + eslint "^7.9.0" + indent-string "^4.0.0" + lodash.merge "^4.6.0" + loglevel-colored-level-prefix "^1.0.0" + prettier "^2.0.0" + pretty-format "^23.0.1" + require-relative "^0.8.7" + typescript "^3.9.3" + vue-eslint-parser "~7.1.0" + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.0.0, prettier@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" + integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== + pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== +pretty-format@^23.0.1: + version "23.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760" + integrity sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw== + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -4839,6 +5007,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +require-relative@^0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" + integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= + resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -4968,7 +5141,7 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.5: +semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -5266,6 +5439,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -5295,6 +5475,11 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -5517,7 +5702,7 @@ tsscmp@^1.0.5: resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== -tsutils@^3.21.0: +tsutils@^3.17.1, tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== @@ -5588,6 +5773,11 @@ typescript@4.3.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== +typescript@^3.9.3: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" @@ -5767,6 +5957,18 @@ void-elements@^3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= +vue-eslint-parser@~7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz#c43c1c715ff50778b9a7e9a4e16921185f3425d3" + integrity sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA== + dependencies: + debug "^4.1.1" + eslint-scope "^5.0.0" + eslint-visitor-keys "^1.1.0" + espree "^6.2.1" + esquery "^1.0.1" + lodash "^4.17.15" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"