diff --git a/app/Controllers/Http/EmployeesController.ts b/app/Controllers/Http/EmployeesController.ts index f62633a..afe1681 100644 --- a/app/Controllers/Http/EmployeesController.ts +++ b/app/Controllers/Http/EmployeesController.ts @@ -1,6 +1,5 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' import Employee from 'App/Models/Employee' -import User from 'App/Models/User' import UpdateEmployeeValidator from 'App/Validators/UpdateEmployeeValidator' import CreateEmployeeValidator from 'App/Validators/CreateEmployeeValidator' @@ -8,22 +7,18 @@ import Database from '@ioc:Adonis/Lucid/Database' import Logger from '@ioc:Adonis/Core/Logger' type ResultShow = { - employee: { - id: number, - firstName: string, - lastName: string, - shorthand: string, - phone: string, - mobile: string, - email: string, - contractHours: number, - hasUser: Boolean, - }, - user: { - id?: number, - username?: string - } - } + id: number, + firstName: string, + lastName: string | undefined, + shorthand: string, + phone: string | undefined, + mobile: string | undefined, + email: string | undefined, + contractHours: number | undefined, + username: string | undefined, + role: string, + isActive: boolean +} export default class EmployeesController { public async index ({bouncer, request}: HttpContextContract) { @@ -51,75 +46,56 @@ export default class EmployeesController { public async store ({request}: HttpContextContract) { try { - const payload = await request.validate(CreateEmployeeValidator) - - return await Employee.create({ - firstName: payload.firstName, - lastName: payload.lastName, - shorthand: payload.shorthand, - email: payload.email, - phone: payload.phone, - mobile: payload.mobile, - contractHours: payload.contractHours, - }) + const payload = await request.validate(CreateEmployeeValidator) + return await Employee.create({ + firstName: payload.firstName, + lastName: payload.lastName, + shorthand: payload.shorthand, + email: payload.email, + phone: payload.phone, + mobile: payload.mobile, + contractHours: payload.contractHours, + username: payload.username, + password: payload.password, + role: payload.role, + isActive: payload.isActive + }) } catch (error) { - return error + return error } } - public async show ({params, bouncer, auth}: HttpContextContract) { - let result : ResultShow = { - employee: { - id: NaN, - firstName: '', - lastName: '', - shorthand: '', - phone: '', - mobile: '', - email: '', - contractHours: NaN, - hasUser: false - }, - user: {} - } - let emp - let user + public async show ({params, bouncer, auth}: HttpContextContract) : Promise { + let emp: Employee if(params.id === 'me' && auth.isLoggedIn && auth.user !== undefined){ - user = auth.user - emp = (await auth.user.related('employeeProfile').query().limit(1))[0] + emp = auth.user } else { emp = await Employee.findOrFail(params.id) - user = (await User.find(emp.userId)) ?? undefined } - if(emp !== undefined){ - result.employee.id = emp.id - result.employee.firstName = emp.firstName - result.employee.lastName = emp.lastName - result.employee.shorthand = emp.shorthand - result.employee.phone = emp.phone - result.employee.mobile = emp.mobile - result.employee.email = emp.email - result.employee.contractHours = emp.contractHours - } - if(user !== undefined){ - result.employee.hasUser = true - result.user.username = user.username - result.user.id = user.id - } - await bouncer.authorize('employees.show', emp) - return result + return { + id: emp.id, + firstName: emp.firstName, + lastName: emp.lastName, + shorthand: emp.shorthand, + phone: emp.phone, + mobile: emp.mobile, + email: emp.email, + contractHours: emp.contractHours, + role: emp.role, + username: emp.username, + isActive: emp.isActive + } } public async update ({params, bouncer, response, request}: HttpContextContract) { - const employee : Employee = await Employee.findOrFail(params.id) const editContractHours : boolean = employee.contractHours !== request.input('contractHours') @@ -128,7 +104,7 @@ export default class EmployeesController { const payload = await request.validate(UpdateEmployeeValidator) if (editContractHours){ - employee.contractHours = payload.contractHours ?? 0 + employee.contractHours = payload.contractHours ?? 0 } employee.firstName = payload.firstName @@ -137,6 +113,12 @@ export default class EmployeesController { employee.email = payload.email ?? '' employee.phone = payload.phone ?? '' employee.mobile = payload.mobile ?? '' + employee.isActive = payload.isActive ?? false + + if (payload.username !== undefined && payload.password !== undefined ) { + employee.username = payload.username + employee.password = payload.password + } await employee.save() diff --git a/app/Models/Employee.ts b/app/Models/Employee.ts index d262a50..65a510a 100644 --- a/app/Models/Employee.ts +++ b/app/Models/Employee.ts @@ -1,6 +1,6 @@ import { DateTime } from 'luxon' -import { BaseModel, belongsTo, BelongsTo, column } from '@ioc:Adonis/Lucid/Orm' -import User from 'App/Models/User' +import { BaseModel, beforeSave, column } from '@ioc:Adonis/Lucid/Orm' +import Hash from '@ioc:Adonis/Core/Hash' export default class Employee extends BaseModel { @column({ isPrimary: true }) @@ -22,20 +22,33 @@ export default class Employee extends BaseModel { public shorthand: string @column() - public email : string + public email : string | undefined @column() - public phone : string + public phone : string | undefined @column() - public mobile : string + public mobile : string | undefined @column() - public contractHours : number + public contractHours : number | undefined @column() - public userId : number + public isActive : boolean - @belongsTo(() => User) - public user: BelongsTo + @column() + public role: string + + @column() + public username: string | undefined + + @column({serializeAs: null}) + public password: string + + @beforeSave() + public static async hashPassword(employee: Employee){ + if(employee.$dirty.password){ + employee.password = await Hash.make(employee.password) + } + } } diff --git a/app/Models/User.ts b/app/Models/User.ts deleted file mode 100644 index d13b70c..0000000 --- a/app/Models/User.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { DateTime } from 'luxon' -import { BaseModel, beforeSave, column, hasOne, HasOne } from '@ioc:Adonis/Lucid/Orm' -import Hash from '@ioc:Adonis/Core/Hash' -import Employee from 'App/Models/Employee' - -export default class User extends BaseModel { - @column({ isPrimary: true }) - public id: number - - @column.dateTime({ autoCreate: true }) - public createdAt: DateTime - - @column.dateTime({ autoCreate: true, autoUpdate: true }) - public updatedAt: DateTime - - @column() - public username: string - - @column() - public email: string - - @column({serializeAs: null}) - public password : string - - @hasOne(() => Employee) - public employeeProfile : HasOne - - @column() - public role : string - - @column() - public isActive : boolean - - @beforeSave() - public static async hashPassword(user: User) { - if(user.$dirty.password){ - user.password = await Hash.make(user.password) - } - } - -} diff --git a/app/Validators/CreateEmployeeValidator.ts b/app/Validators/CreateEmployeeValidator.ts index 0b3a02a..1065980 100644 --- a/app/Validators/CreateEmployeeValidator.ts +++ b/app/Validators/CreateEmployeeValidator.ts @@ -38,15 +38,17 @@ export default class CreateEmployeeValidator { trim: true }), - shorthand: schema.string({ + shorthand: schema.string( + { trim: true - }, - [ + }, + [ rules.unique({ table: 'employees', column: 'shorthand', + caseInsensitive: true, }) - ]), + ]), email: schema.string.optional({ trim: true @@ -61,7 +63,40 @@ export default class CreateEmployeeValidator { mobile: schema.string.optional(), - contractHours: schema.number.optional() + contractHours: schema.number.optional( + [ + rules.unsigned(), + rules.range(0, 40) + ] + ), + + role: schema.string( + { + trim: true + } + ), + + isActive: schema.boolean.optional(), + + username: schema.string.optional( + { + trim: true + }, + [ + rules.unique({ + table: 'employees', + column: 'username', + caseInsensitive: true, + }) + ] + ), + + password: schema.string.optional( + {}, + [ + rules.requiredIfExists('username') + ] + ) }) diff --git a/app/Validators/UpdateEmployeeValidator.ts b/app/Validators/UpdateEmployeeValidator.ts index a007d52..02977e9 100644 --- a/app/Validators/UpdateEmployeeValidator.ts +++ b/app/Validators/UpdateEmployeeValidator.ts @@ -45,6 +45,7 @@ export default class UpdateEmployeeValidator { rules.unique({ table: 'employees', column: 'shorthand', + caseInsensitive: true, whereNot: { id: this.ctx.params.id } @@ -64,7 +65,36 @@ export default class UpdateEmployeeValidator { mobile: schema.string.optional(), - contractHours: schema.number.optional() + contractHours: schema.number.optional([ + rules.unsigned(), + rules.range(0, 40) + ]), + + role: schema.string({ + trim: true + }), + + isActive: schema.boolean.optional(), + + username: schema.string.optional( + { + trim: true + }, + [ + rules.unique({ + table: 'employees', + column: 'username', + caseInsensitive: true, + }) + ] + ), + + password: schema.string.optional( + {}, + [ + rules.requiredIfExists('username') + ] + ) }) diff --git a/config/auth.ts b/config/auth.ts index 6d2eb3a..720dcd0 100644 --- a/config/auth.ts +++ b/config/auth.ts @@ -100,7 +100,7 @@ const authConfig: AuthConfig = { | that time. | */ - model: () => import('App/Models/User'), + model: () => import('App/Models/Employee'), }, }, }, diff --git a/database/migrations/1634413335137_employees.ts b/database/migrations/1634413335137_employees.ts index c8a3772..2276a74 100644 --- a/database/migrations/1634413335137_employees.ts +++ b/database/migrations/1634413335137_employees.ts @@ -18,11 +18,15 @@ export default class Employees extends BaseSchema { table.string('email') table.string('phone') table.string('mobile') + table.string('username').unique() + table.string('password') + table.string('role') + .defaultTo('employee') + .notNullable() + table.boolean('is_active') + .defaultTo(false) + .notNullable() table.decimal('contract_hours', 2, 2) - table - .integer('user_id') - .unsigned() - .references('users.id') }) } diff --git a/database/migrations/1634414150546_users.ts b/database/migrations/1634414150546_users.ts deleted file mode 100644 index 6dc8d06..0000000 --- a/database/migrations/1634414150546_users.ts +++ /dev/null @@ -1,25 +0,0 @@ -import BaseSchema from '@ioc:Adonis/Lucid/Schema' - -export default class Users extends BaseSchema { - protected tableName = 'users' - - public async up () { - this.schema.createTable(this.tableName, (table) => { - table.increments('id') - - /** - * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL - */ - table.timestamp('created_at', { useTz: true }) - table.timestamp('updated_at', { useTz: true }) - table.string('username').notNullable().unique() - table.string('email').notNullable().unique() - table.string('password').notNullable() - - }) - } - - public async down () { - this.schema.dropTable(this.tableName) - } -} diff --git a/database/migrations/1634474414234_users.ts b/database/migrations/1634474414234_users.ts deleted file mode 100644 index 6ec5260..0000000 --- a/database/migrations/1634474414234_users.ts +++ /dev/null @@ -1,23 +0,0 @@ -import Hash from '@ioc:Adonis/Core/Hash' -import BaseSchema from '@ioc:Adonis/Lucid/Schema' -import User from 'App/Models/User' - -export default class Users extends BaseSchema { - protected tableName = 'users' - - public async up () { - this.schema.alterTable(this.tableName, (table) => { - table.boolean('is_active').defaultTo(false) - table.string('role') - .defaultTo('employee') - .notNullable() - }) - } - - public async down () { - this.schema.alterTable(this.tableName, (table) => { - table.dropColumn('is_active') - table.dropColumn('role') - }) - } -} diff --git a/database/migrations/1634871433879_api_tokens.ts b/database/migrations/1634871433879_api_tokens.ts index d26751e..73b1387 100644 --- a/database/migrations/1634871433879_api_tokens.ts +++ b/database/migrations/1634871433879_api_tokens.ts @@ -6,7 +6,7 @@ export default class ApiTokens extends BaseSchema { public async up() { this.schema.createTable(this.tableName, (table) => { table.increments('id').primary() - table.integer('user_id').unsigned().references('id').inTable('users').onDelete('CASCADE') + table.integer('user_id').unsigned().references('id').inTable('employee').onDelete('CASCADE') table.string('name').notNullable() table.string('type').notNullable() table.string('token', 64).notNullable().unique() diff --git a/database/seeders/Employee.ts b/database/seeders/Employee.ts index fbe207e..9e0bda0 100644 --- a/database/seeders/Employee.ts +++ b/database/seeders/Employee.ts @@ -11,18 +11,26 @@ export default class EmployeeSeeder extends BaseSeeder { firstName: 'Max', lastName: 'Mustermann', shorthand: 'MM', - userId: 1 + username: 'admin', + password: 'admin', + isActive: true, + role: 'admin' }, { firstName: 'Jane', lastName: 'Doe', shorthand: 'JD', - userId: 2 + username: 'user', + password: 'user', + isActive: true, + role: 'employee' }, { firstName: 'Bingo', lastName: 'Bongo', - shorthand: 'BB' + shorthand: 'BB', + isActive: false, + role: 'employee' } ]) } catch (error) { diff --git a/database/seeders/User.ts b/database/seeders/User.ts deleted file mode 100644 index 00bb716..0000000 --- a/database/seeders/User.ts +++ /dev/null @@ -1,28 +0,0 @@ -import BaseSeeder from '@ioc:Adonis/Lucid/Seeder' -import User from 'App/Models/User' -import Logger from '@ioc:Adonis/Core/Logger' - -export default class UserSeeder extends BaseSeeder { - public async run () { - try { - await User.create({ - username: 'admin', - password: 'admin', - role: 'admin', - email: 'test@test.de', - isActive: true - }) - await User.create({ - username: 'user', - password: 'user', - role: 'employee', - email: 'user@test.de', - isActive: true - }) - } - catch(error) { - Logger.error(error.message) - } - } -} - diff --git a/start/bouncer.ts b/start/bouncer.ts index 127ae9c..1af8fff 100644 --- a/start/bouncer.ts +++ b/start/bouncer.ts @@ -6,7 +6,6 @@ */ import Bouncer from '@ioc:Adonis/Addons/Bouncer' -import User from 'App/Models/User' import Employee from 'App/Models/Employee' /* @@ -33,30 +32,30 @@ import Employee from 'App/Models/Employee' */ export const { actions } = Bouncer - .define('employees.index', (user: User) => { + .define('employees.index', (user: Employee) => { if(user.role !== 'admin') return Bouncer.deny('You are not allowed to view all employees') return true }) - .define('employees.show', (user: User, employee : Employee) => { - if(user.role !== 'admin' && user.id !== employee.userId){ + .define('employees.show', (user: Employee, query: Employee) => { + if(user.role !== 'admin' && user.id !== query.id){ return Bouncer.deny('You are not allowd to view employees other than yourself') } return true }) - .define('employees.store', (user: User) => { + .define('employees.store', (user: Employee) => { if(user.role !== 'admin') return Bouncer.deny('You are not allowd to create any employees') return true }) - .define('employees.destroy', (user: User) => { + .define('employees.destroy', (user: Employee) => { if(user.role !== 'admin') return Bouncer.deny('You are not allowed to delete any employees') return true }) - .define('employees.update', (user: User, editContractHours : boolean, employee: Employee) => { - if(user.id !== employee.userId && user.role !== 'admin'){ + .define('employees.update', (user: Employee, editContractHours : boolean, query: Employee) => { + if(user.id !== query.id && user.role !== 'admin'){ return Bouncer.deny('You are not allowed to edit employees other than yourself.') } else if (editContractHours && user.role !== 'admin'){ return Bouncer.deny('You are not allowed to edit your contract hours.')