diff --git a/app/Helpers/ModelEnum.ts b/app/Helpers/ModelEnum.ts new file mode 100644 index 0000000..6981ab3 --- /dev/null +++ b/app/Helpers/ModelEnum.ts @@ -0,0 +1,21 @@ +import { DecoratorFn, LucidModel } from '@ioc:Adonis/Lucid/Orm' +// import { column as originalColumn } from '@ioc:Adonis/Lucid/Orm' + +export type EnumDecorator = () => DecoratorFn + +export const enumColumn: EnumDecorator = () => { + return function decorateAsColumn(target, property) { + const Model = target.constuctor as LucidModel + Model.boot() + Model.$addColumn(property, { + prepare: (value: string[]): string => `{${value.join(',')}}`, + consume: (value: string): string[] => value.slice(1, -1).split(','), + }) + } +} + +// declare module '@ioc:Adonis/Lucid/Orm' { +// export const column: typeof originalColumn & { enum: EnumDecorator } +// } + +// column.enum = enumColumn diff --git a/app/Helpers/ModelJson.ts b/app/Helpers/ModelJson.ts new file mode 100644 index 0000000..b660a8f --- /dev/null +++ b/app/Helpers/ModelJson.ts @@ -0,0 +1,21 @@ +import { DecoratorFn, LucidModel } from '@ioc:Adonis/Lucid/Orm' +// import { column as originalColumn } from '@ioc:Adonis/Lucid/Orm' + +export type JsonDecorator = () => DecoratorFn + +export const jsonColumn: JsonDecorator = () => { + return function decorateAsColumn(target, property) { + const Model = target.constuctor as LucidModel + Model.boot() + Model.$addColumn(property, { + prepare: (value: {}): string => JSON.stringify(value), + consume: (value: string): {} => JSON.parse(value), + }) + } +} + +// declare module '@ioc:Adonis/Lucid/Orm' { +// export const column: typeof originalColumn & { json: JsonDecorator } +// } + +// column.json = jsonColumn diff --git a/app/Models/Member.ts b/app/Models/Member.ts new file mode 100644 index 0000000..24cfce4 --- /dev/null +++ b/app/Models/Member.ts @@ -0,0 +1,27 @@ +import { DateTime } from 'luxon' +import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm' +import User from './User' +import { enumColumn } from 'App/Helpers/ModelEnum' +import School from './School' + +export type MemberRole = 'student' | 'teacher' | 'parent' | 'staff' + +export default class Member extends BaseModel { + @column({ isPrimary: true }) + public id: number + + @belongsTo(() => User) + public user: BelongsTo + + @belongsTo(() => School) + public school: BelongsTo + + @enumColumn() + public roles: MemberRole[] + + @column.dateTime({ autoCreate: true }) + public createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + public updatedAt: DateTime +} diff --git a/app/Models/School.ts b/app/Models/School.ts new file mode 100644 index 0000000..46083c9 --- /dev/null +++ b/app/Models/School.ts @@ -0,0 +1,42 @@ +import { DateTime } from 'luxon' +import { BaseModel, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm' +import { jsonColumn } from 'App/Helpers/ModelJson' +import Member from './Member' + +export type SchoolContact = { + email: string + phone: string +} + +export type SchoolAddress = { + street: string + city: string + state: string + zip: string +} + +export default class School extends BaseModel { + @column({ isPrimary: true }) + public id: number + + @column() + public name: string + + @jsonColumn() + public contact: SchoolContact + + @jsonColumn() + public address: SchoolAddress + + @column() + public identifier: string + + @column.dateTime({ autoCreate: true }) + public createdAt: DateTime + + @hasMany(() => Member, { foreignKey: 'school_id' }) + public members: HasMany + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + public updatedAt: DateTime +} diff --git a/app/Models/User.ts b/app/Models/User.ts index 103e28d..4a4b92b 100644 --- a/app/Models/User.ts +++ b/app/Models/User.ts @@ -1,7 +1,18 @@ import { DateTime } from 'luxon' import Hash from '@ioc:Adonis/Core/Hash' -import { column, beforeSave, BaseModel, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm' +import { + column, + beforeSave, + BaseModel, + hasMany, + HasMany, + hasOne, + HasOne, +} from '@ioc:Adonis/Lucid/Orm' import Access from 'App/Models/Access' +import Member from './Member' +import { enumColumn } from 'App/Helpers/ModelEnum' +import UserDetail from './UserDetail' export type UserRole = 'admin' | 'user' | 'developer' @@ -24,15 +35,18 @@ export default class User extends BaseModel { @column.dateTime({ autoCreate: true, autoUpdate: true }) public updatedAt: DateTime - @column({ - prepare: (value: string[]): string => `{${value.join(',')}}`, - consume: (value: string): string[] => value.slice(1, -1).split(','), - }) + @enumColumn() public roles: UserRole[] @hasMany(() => Access, { foreignKey: 'user_id' }) public accesses: HasMany + @hasMany(() => Member, { foreignKey: 'user_id' }) + public members: HasMany + + @hasOne(() => UserDetail, { foreignKey: 'user_id' }) + public detail: HasOne + @beforeSave() public static async hashPassword(user: User) { if (user.$dirty.password) { diff --git a/app/Models/UserDetail.ts b/app/Models/UserDetail.ts new file mode 100644 index 0000000..d5515bd --- /dev/null +++ b/app/Models/UserDetail.ts @@ -0,0 +1,28 @@ +import { DateTime } from 'luxon' +import { BaseModel, belongsTo, BelongsTo, column, computed } from '@ioc:Adonis/Lucid/Orm' +import User from './User' + +export default class UserDetail extends BaseModel { + @column({ isPrimary: true }) + public id: number + + @column() + public firstName: string + + @column() + public lastName: string + + @computed() + public get fullName() { + return `${this.firstName} ${this.lastName}` + } + + @belongsTo(() => User) + public member: BelongsTo + + @column.dateTime({ autoCreate: true }) + public createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + public updatedAt: DateTime +} diff --git a/database/migrations/1666341836371_members.ts b/database/migrations/1666341836371_members.ts new file mode 100644 index 0000000..fdb6d6a --- /dev/null +++ b/database/migrations/1666341836371_members.ts @@ -0,0 +1,27 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema' + +export default class extends BaseSchema { + protected tableName = 'members' + + public async up() { + this.schema.raw("CREATE TYPE member_role AS ENUM('student', 'teacher', 'parent', 'staff')") + + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + table.integer('user_id').unsigned().references('users.id').onDelete('CASCADE') + table.integer('school_id').unsigned().references('schools.id').onDelete('CASCADE') + table.specificType('roles', 'member_role[]').notNullable() + + /** + * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL + */ + table.timestamp('created_at', { useTz: true }) + table.timestamp('updated_at', { useTz: true }) + }) + } + + public async down() { + this.schema.dropTable(this.tableName) + this.schema.raw('DROP TYPE member_role') + } +} diff --git a/database/migrations/1666342630554_user_details.ts b/database/migrations/1666342630554_user_details.ts new file mode 100644 index 0000000..14fca54 --- /dev/null +++ b/database/migrations/1666342630554_user_details.ts @@ -0,0 +1,24 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema' + +export default class extends BaseSchema { + protected tableName = 'user_details' + + public async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + table.integer('user_id').unsigned().references('users.id').onDelete('CASCADE') + table.string('first_name', 32).notNullable() + table.string('last_name', 64).notNullable() + + /** + * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL + */ + table.timestamp('created_at', { useTz: true }) + table.timestamp('updated_at', { useTz: true }) + }) + } + + public async down() { + this.schema.dropTable(this.tableName) + } +} diff --git a/database/migrations/1666347063115_schools.ts b/database/migrations/1666347063115_schools.ts new file mode 100644 index 0000000..c67baaf --- /dev/null +++ b/database/migrations/1666347063115_schools.ts @@ -0,0 +1,25 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema' + +export default class extends BaseSchema { + protected tableName = 'schools' + + public async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + table.string('name', 255).notNullable() + table.json('contact').notNullable() + table.json('address').notNullable() + table.string('identifier').notNullable() + + /** + * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL + */ + table.timestamp('created_at', { useTz: true }) + table.timestamp('updated_at', { useTz: true }) + }) + } + + public async down() { + this.schema.dropTable(this.tableName) + } +}