diff --git a/.eslintrc.yml b/.eslintrc.yml index fbca9a66b..3062c8a42 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -11,3 +11,7 @@ rules: - error - except-parens no-plusplus: off + new-cap: + - error + - capIsNewExceptions: ['Model', 'Attr', 'HasMany', 'HasOne', 'BelongsTo', 'Link', 'Stream'] + properties: false diff --git a/package.json b/package.json index c7e2c5cbe..abad1cc94 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,10 @@ "@maicol07/material-web-additions": "^1.4.4", "@material/mwc-snackbar": "^0.27.0", "@material/mwc-top-app-bar": "^0.27.0", - "@material/web": "1.0.1", + "@material/web": "^1.0.1", "@mdi/js": "^7.3.67", "classnames": "^2.3.2", "collect.js": "^4.36.1", - "coloquent": "npm:@maicol07/coloquent@3.0.1-beta", "dayjs": "^1.11.10", "include-media": "^2.0.0", "lit": "^3.1.0", @@ -32,6 +31,7 @@ "postcss-scss": "^4.0.9", "prntr": "^2.0.18", "readable-http-codes": "^1.1.1", + "spraypaint": "^0.10.24", "ts-pattern": "^5.0.6", "tslib": "^2.6.2", "typescript-cookie": "^1.0.6" diff --git a/resources/ts/Components/DataTable/RecordsTable.tsx b/resources/ts/Components/DataTable/RecordsTable.tsx index bdc4a1bdf..7565f4345 100644 --- a/resources/ts/Components/DataTable/RecordsTable.tsx +++ b/resources/ts/Components/DataTable/RecordsTable.tsx @@ -14,7 +14,7 @@ import DataTable, {DataTableAttributes} from '@osm/Components/DataTable/DataTabl import DataTableColumn, {DataTableColumnAttributes} from '@osm/Components/DataTable/DataTableColumn'; import RecordsTableColumn from '@osm/Components/DataTable/RecordsTableColumn'; import MdIcon from '@osm/Components/MdIcon'; -import Model from '@osm/Models/Model'; +import Model from '@osm/Models/Record'; import {isVnode} from '@osm/utils/misc'; import collect, {Collection} from 'collect.js'; import { @@ -28,7 +28,7 @@ export interface RecordsTableColumnAttributes extends DataTableColumnAttributes label?: string; } -export interface RecordsTableAttributes> extends DataTableAttributes { +export interface RecordsTableAttributes extends DataTableAttributes { cols: Collection | Collection | Collection; records: Map; readonly?: boolean; @@ -42,7 +42,7 @@ export interface RecordsTableAttributes> extends DataT valueModifier?(value: any, attribute: string, record: M): any; } -export default class RecordsTable, A extends RecordsTableAttributes = RecordsTableAttributes> extends DataTable { +export default class RecordsTable = RecordsTableAttributes> extends DataTable { element!: MdDataTable; selectedRecordsIds: string[] = []; @@ -162,7 +162,7 @@ export default class RecordsTable, A extends RecordsTa cells.put('actions', this.tableRowActions(vnode, record).values().all()); } - rows.set(record.getId()!, cells); + rows.set(record.id!, cells); } return rows; @@ -238,7 +238,7 @@ export default class RecordsTable, A extends RecordsTa protected onDeleteRecordButtonClicked(vnode: Vnode, record: M, event: MouseEvent) { event.stopPropagation(); - vnode.attrs.onDeleteRecordButtonClick?.(record.getId()!, event); + vnode.attrs.onDeleteRecordButtonClick?.(record.id!, event); } protected onDeleteSelectedRecordsButtonClicked(vnode: Vnode, event: MouseEvent) { @@ -247,9 +247,10 @@ export default class RecordsTable, A extends RecordsTa protected getModelValue(record: M, attribute: string, vnode: Vnode): unknown { // Check if is a relation - let value: unknown = record.getAttribute(attribute); + // @ts-expect-error + let value: unknown = record[attribute]; if (attribute === 'id') { - value = record.getId(); + value = record.id; } return vnode.attrs.valueModifier?.(value, attribute, record) ?? value; diff --git a/resources/ts/Components/Dialogs/AddEditRecordDialog.tsx b/resources/ts/Components/Dialogs/AddEditRecordDialog.tsx index b1c6ee72a..e7fa4b5c2 100644 --- a/resources/ts/Components/Dialogs/AddEditRecordDialog.tsx +++ b/resources/ts/Components/Dialogs/AddEditRecordDialog.tsx @@ -1,7 +1,7 @@ import {mdiFloppy} from '@mdi/js'; import RecordDialog, {RecordDialogAttributes} from '@osm/Components/Dialogs/RecordDialog'; import MdIcon from '@osm/Components/MdIcon'; -import Model from '@osm/Models/Model'; +import Model from '@osm/Models/Record'; import { VnodeCollection, VnodeCollectionItem @@ -13,7 +13,6 @@ import { showSnackbar } from '@osm/utils/misc'; import collect, {Collection} from 'collect.js'; -import {SaveResponse} from 'coloquent'; import { Children, Vnode, @@ -23,7 +22,7 @@ import Stream from 'mithril/stream'; import {Form} from 'mithril-utilities'; import {Class} from 'type-fest'; -export default abstract class AddEditRecordDialog> extends RecordDialog { +export default abstract class AddEditRecordDialog extends RecordDialog { // eslint-disable-next-line unicorn/no-null protected formElement: HTMLFormElement | null = null; protected abstract formState: Map>; @@ -47,12 +46,13 @@ export default abstract class AddEditRecordDialog> ext super.oncreate(vnode); this.formElement = this.element.querySelector('form'); - this.element.querySelector(`#saveBtn${this.formId}`)?.setAttribute('form', this.formId!); + this.element.querySelector(`#saveBtn${this.formId!}`)?.setAttribute('form', this.formId!); } fillForm() { for (const [key, value] of this.formState) { - value(this.record.getAttribute(key) ?? value()); + // @ts-ignore + value(this.record[key] ?? value()); } } @@ -126,27 +126,25 @@ export default abstract class AddEditRecordDialog> ext } async save(): Promise { - this.record.setAttributes(this.modelAttributesFromFormState); + this.record.assignAttributes(this.modelAttributesFromFormState); try { - const response = await this.record.save(); - this.afterSave(response); - return response.getModelId() !== undefined; + const result = await this.record.save(); + this.afterSave(result); + return result; } catch (error) { this.onSaveError(error as JSONAPI.RequestError); return false; } } - afterSave(response: SaveResponse): void { - const responseModel = response.getModel() as M; - if (responseModel !== undefined) { - this.record = responseModel; + afterSave(result: boolean): void { + if (result) { void showSnackbar(__('Record salvato con successo')); } } onSaveError(error: JSONAPI.RequestError): void { - const message = error.response.data.message; + const {message} = error.response.data; void showSnackbar(message, false); } diff --git a/resources/ts/Components/Dialogs/DeleteRecordDialog.tsx b/resources/ts/Components/Dialogs/DeleteRecordDialog.tsx index 4475ab6eb..1a0a103b0 100644 --- a/resources/ts/Components/Dialogs/DeleteRecordDialog.tsx +++ b/resources/ts/Components/Dialogs/DeleteRecordDialog.tsx @@ -1,6 +1,6 @@ import {mdiDelete} from '@mdi/js'; import MdIcon from '@osm/Components/MdIcon'; -import Model from '@osm/Models/Model'; +import Model from '@osm/Models/Record'; import {VnodeCollection} from '@osm/typings/jsx'; import {showSnackbar} from '@osm/utils/misc'; import collect from 'collect.js'; @@ -12,11 +12,11 @@ import {RequestError} from 'mithril-utilities'; import RecordDialog, {RecordDialogAttributes} from './RecordDialog'; -export interface DeleteRecordDialogAttributes> extends RecordDialogAttributes { +export interface DeleteRecordDialogAttributes extends RecordDialogAttributes { records: M | M[]; } -export default class DeleteRecordDialog, A extends DeleteRecordDialogAttributes = DeleteRecordDialogAttributes> extends RecordDialog { +export default class DeleteRecordDialog = DeleteRecordDialogAttributes> extends RecordDialog { records!: M[]; oninit(vnode: Vnode) { @@ -32,7 +32,7 @@ export default class DeleteRecordDialog, A extends Del return ( <>

{text}

-
    {this.records.map((record) =>
  • {this.recordSummary(record, vnode)}
  • )}
+
    {this.records.map((record) =>
  • {this.recordSummary(record, vnode)}
  • )}
); } @@ -61,7 +61,7 @@ export default class DeleteRecordDialog, A extends Del } recordSummary(record: M, vnode: Vnode): Children { - return __('ID: :recordId', {recordId: record.getId()!}); + return __('ID: :recordId', {recordId: record.id!}); } async onConfirmButtonClicked() { @@ -74,12 +74,12 @@ export default class DeleteRecordDialog, A extends Del async deleteRecord() { try { - const promises = this.records.map((record) => record.delete()); + const promises = this.records.map((record) => record.destroy()); await Promise.all(promises); // TODO: Better way for pluralization in i18n void showSnackbar(this.records.length > 1 ? __('Record eliminati!') : __('Record eliminato!')); - this.close('deleted'); + void this.close('deleted'); } catch (error) { void showSnackbar(__('Errore durante l\'eliminazione del record! :error', {error: (error as RequestError<{message: string}>).response.message}), false); } diff --git a/resources/ts/Components/Dialogs/RecordDialog.tsx b/resources/ts/Components/Dialogs/RecordDialog.tsx index 9f869297e..4b5862c6b 100644 --- a/resources/ts/Components/Dialogs/RecordDialog.tsx +++ b/resources/ts/Components/Dialogs/RecordDialog.tsx @@ -2,7 +2,7 @@ import '@maicol07/material-web-additions/layout-grid/layout-grid.js'; import '@material/web/button/text-button.js'; import Dialog, {DialogAttributes} from '@osm/Components/Dialogs/Dialog'; -import Model from '@osm/Models/Model'; +import Model from '@osm/Models/Record'; import {Vnode} from 'mithril'; export interface RecordDialogAttributes> extends DialogAttributes { diff --git a/resources/ts/Components/Pages/RecordPage.tsx b/resources/ts/Components/Pages/RecordPage.tsx index 667efc20b..255a6cdb1 100644 --- a/resources/ts/Components/Pages/RecordPage.tsx +++ b/resources/ts/Components/Pages/RecordPage.tsx @@ -3,27 +3,27 @@ import '@material/web/button/outlined-button.js'; import {mdiChevronLeft} from '@mdi/js'; import MdIcon from '@osm/Components/MdIcon'; import Page, {PageAttributes} from '@osm/Components/Page'; -import Model from '@osm/Models/Model'; +import Model from '@osm/Models/Record'; import {showSnackbar} from '@osm/utils/misc'; -import {Builder} from 'coloquent'; import { Children, Vnode } from 'mithril'; +import {Scope} from 'spraypaint'; import {Class} from 'type-fest'; -export interface RecordPageAttributes> extends PageAttributes { +export interface RecordPageAttributes extends PageAttributes { record: M; } -export default abstract class RecordPage, A extends RecordPageAttributes = RecordPageAttributes> extends Page
{ - abstract recordType: Class & typeof Model; +export default abstract class RecordPage = RecordPageAttributes> extends Page { + abstract recordType: Class & typeof Model; record?: M; async oninit(vnode: Vnode) { super.oninit(vnode); const {id: recordId} = route().params as {id: number | string}; - if (recordId !== this.record?.getId()) { + if (recordId !== this.record?.id) { await this.loadRecord(recordId); } } @@ -32,24 +32,23 @@ export default abstract class RecordPage, A extends Re if (recordId && recordId !== 'new' && !this.record) { try { const response = await this.modelQuery().find(recordId); - this.record = response.getData() || undefined; - } catch (e) { - console.error(e); + this.record = response.data || undefined; + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); void showSnackbar(__('Errore durante il caricamento del record')); // Do nothing } } if (!this.record) { - // @ts-expect-error — This won't be abstract when implemented - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.record = new this.recordType(); } m.redraw(); } - modelQuery(): Builder { - return this.recordType.query(); + modelQuery(): Scope { + return this.recordType as unknown as Scope; } contents(vnode: Vnode): Children { diff --git a/resources/ts/Components/Pages/RecordsPage.tsx b/resources/ts/Components/Pages/RecordsPage.tsx index be8eee336..261169e84 100644 --- a/resources/ts/Components/Pages/RecordsPage.tsx +++ b/resources/ts/Components/Pages/RecordsPage.tsx @@ -21,9 +21,8 @@ import DeleteRecordDialog, {DeleteRecordDialogAttributes} from '@osm/Components/ import RecordDialog, {RecordDialogAttributes} from '@osm/Components/Dialogs/RecordDialog'; import MdIcon from '@osm/Components/MdIcon'; import Page, {PageAttributes} from '@osm/Components/Page'; -import Model from '@osm/Models/Model'; +import Record from '@osm/Models/Record'; import collect, {type Collection} from 'collect.js'; -import {SortDirection} from 'coloquent'; import dayjs from 'dayjs'; import type { Children, @@ -31,38 +30,25 @@ import type { VnodeDOM } from 'mithril'; import Stream from 'mithril/stream'; +import {Scope} from 'spraypaint'; +import {SortDir} from 'spraypaint/lib-esm/scope'; import {match} from 'ts-pattern'; import {Match} from 'ts-pattern/dist/types/Match'; import type {Class} from 'type-fest'; -export interface Meta { - current_page: number; - from: number; - last_page: number; - path: string; - per_page: number; - to: number; - total: number; -} - -export interface JSONAPIResponse { - meta: Meta; -} - - -type RecordDialogVnode, D extends RecordDialog> = Vnode, D>; -type DeleteRecordDialogVnode, D extends DeleteRecordDialog> = Vnode, D>; +type RecordDialogVnode> = Vnode, D>; +type DeleteRecordDialogVnode> = Vnode, D>; // noinspection JSUnusedLocalSymbols /** * @abstract */ export default abstract class RecordsPage< - M extends Model, + M extends Record, D extends AddEditRecordDialog = AddEditRecordDialog, DRD extends DeleteRecordDialog = DeleteRecordDialog > extends Page { - abstract modelType: Class & typeof Model; + abstract modelType: Class & typeof Record; recordDialogType?: Class; deleteRecordDialogType?: Class; @@ -86,15 +72,13 @@ export default abstract class RecordsPage< protected lastRowOfPage = this.totalRecords; protected filters: Map = new Map(); - protected sort: Map = new Map([['id', SortDirection.ASC]]); + protected sort: Map = new Map(); protected relatedFilters: Map = new Map(); private listenedFilterColumns: string[] = []; private listenedSortedColumns: string[] = []; oninit(vnode: Vnode) { super.oninit(vnode); - // @ts-ignore - this.modelType.pageSize = this.currentPageSize; // Redraw on a first load to call onbeforeupdate m.redraw(); } @@ -111,24 +95,21 @@ export default abstract class RecordsPage< async loadRecords() { this.isTableLoading = true; - let query = this.modelQuery(); + const query = this.modelQuery(); - // Fix Restify when filtering relations - query = query.option('related', query.getQuery().getInclude().join(',')); - - const response = await query.get(this.currentPage); - const rawResponse = response.getHttpClientResponse().getData() as JSONAPIResponse; - this.lastPage = rawResponse.meta.last_page; - this.firstRowOfPage = rawResponse.meta.from; - this.lastRowOfPage = rawResponse.meta.to; - this.currentPageSize = rawResponse.meta.per_page; - this.totalRecords = rawResponse.meta.total; - const data = response.getData(); + const response = await query.page(this.currentPage).all(); + const rawResponse = response.raw; + this.lastPage = rawResponse.meta?.last_page as number; + this.firstRowOfPage = rawResponse.meta?.from as number; + this.lastRowOfPage = rawResponse.meta?.to as number; + this.currentPageSize = rawResponse.meta?.per_page as number; + this.totalRecords = rawResponse.meta?.total as number; + const {data} = response; this.records.clear(); if (data.length > 0) { for (const record of data) { - this.records.set(record.getId()!, record); + this.records.set(record.id, record); } } @@ -144,31 +125,29 @@ export default abstract class RecordsPage< */ private static convertToSnakeCase(string_: string, trim = false, removeSpecials = false, underscoredNumbers = false) { return string_.replace(removeSpecials ? /[^\w ]/g : '', '') - .replace(/([ _]+)/g, '_') + .replace(/[ _]+/g, '_') .replace(trim ? /(^_|_$)/gm : '', '') - .replace(underscoredNumbers ? /([^\dA-Z_])([^_a-z])/g : /([^\dA-Z_])([^\d_a-z])/g, (m, preUpper, upper) => `${preUpper}_${upper}`) - .replace(underscoredNumbers ? /([^\d_]\d|\d[^\d_])/g : '', (m, index) => (index ? index.split('').join('_') : '')) - .replace(/([A-Z])([A-Z])([^\dA-Z_])/g, (m, previousUpper, upper, lower) => `${previousUpper}_${upper}${lower}`) - .replaceAll('_.', '.') // remove redundant underscores + .replace(underscoredNumbers ? /([^\dA-Z_])([^_a-z])/g : /([^\dA-Z_])([^\d_a-z])/g, (m, preUpper: string, upper: string) => `${preUpper}_${upper}`) + .replace(underscoredNumbers ? /([^\d_]\d|\d[^\d_])/g : '', (m, index: string) => (index ? [...index].join('_') : '')) + .replace(/([A-Z])([A-Z])([^\dA-Z_])/g, (m, previousUpper: string, upper: string, lower: string) => `${previousUpper}_${upper}${lower}`) + .replaceAll('_.', '.') // Remove redundant underscores .toLowerCase(); } modelQuery() { - // @ts-ignore - let query = this.modelType.query(); + let query: Scope = this.modelType.per(this.currentPageSize); for (const [attribute, value] of this.filters) { - // Query = query.where(attribute, value); TODO: Revert when Restify uses JSONAPI syntax - query = query.option(RecordsPage.convertToSnakeCase(attribute), value); + query = query.where({[RecordsPage.convertToSnakeCase(attribute)]: value}); } for (const [relation, value] of this.relatedFilters) { - query = query.option('related', relation) - .option('search', value); + query = query.where({related: relation, search: value}); // TODO: Check + // .where('search', value); } for (const [attribute, value] of this.sort) { - query = query.orderBy(RecordsPage.convertToSnakeCase(attribute), value); + query = query.order({[RecordsPage.convertToSnakeCase(attribute)]: value}); } return query; @@ -244,7 +223,7 @@ export default abstract class RecordsPage< updateRecord(model: M) { if (this.recordPageRouteName) { - router.visit(route(this.recordPageRouteName, {id: model.getId()!})); + router.visit(route(this.recordPageRouteName, {id: model.id})); return; } @@ -258,8 +237,8 @@ export default abstract class RecordsPage< for (const [key, state] of this.recordDialogsStates) { // noinspection LocalVariableNamingConventionJS const RD = this.recordDialogType!; - const record = key instanceof Model ? key : this.records.get(key); - const vnodeKey = record?.getId() ?? (key as string); + const record = key instanceof Record ? key : this.records.get(key); + const vnodeKey = record?.id ?? (key as string); collection.put(vnodeKey, ); } @@ -303,14 +282,16 @@ export default abstract class RecordsPage< onTablePageChange(event: CustomEvent) { const {pageSize, action} = event.detail; this.currentPageSize = pageSize; - const {currentPage} = this; + const {currentPage, lastPage} = this; match(action) .with('first', () => (this.currentPage = 1)) .with('previous', () => (this.currentPage--)) .with('next', () => (this.currentPage++)) - .with('last', () => (this.currentPage = this.lastPage)) + .with('last', () => (this.currentPage = lastPage)) .with('current', () => {}) .run(); + // We need to check if the page has changed + // eslint-disable-next-line unicorn/consistent-destructuring if (currentPage !== this.currentPage) { this.refreshRecords = true; m.redraw(); @@ -356,13 +337,13 @@ export default abstract class RecordsPage< const {column, isDescending} = (event as CustomEvent).detail; const modelAttribute = column.dataset.sortAttribute ?? column.dataset.modelAttribute!; this.sort.clear(); - this.sort.set(modelAttribute, isDescending ? SortDirection.DESC : SortDirection.ASC); + this.sort.set(modelAttribute, isDescending ? 'desc' : 'asc'); this.refreshRecords = true; m.redraw(); } openDeleteRecordsDialog(records: M | M[]) { - const key = records instanceof Model ? records.getId()! : records.map((r) => r.getId()).join(','); + const key = records instanceof Record ? records.id! : records.map((r) => r.id).join(','); let state = this.deleteRecordsDialogStates.get(key); if (!state) { @@ -386,7 +367,7 @@ export default abstract class RecordsPage< } protected getRecordDialogState(record?: M, slug?: string) { - const key: string = slug ?? record?.getId() ?? ''; + const key: string = slug ?? record?.id ?? ''; if (!this.recordDialogsStates.has(key)) { const state = Stream(false); diff --git a/resources/ts/Models/Http/RequestHttpClient.ts b/resources/ts/Models/Http/RequestHttpClient.ts deleted file mode 100644 index 5f4564514..000000000 --- a/resources/ts/Models/Http/RequestHttpClient.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { - HttpClient, - HttpClientPromise -} from 'coloquent'; -import {Request} from 'mithril-utilities'; - -import RequestHttpClientPromise from './RequestHttpClientPromise'; - -/** - * @class RequestHttpClient - * - * NOTE: This class is not meant to be used directly, but only for Models. - * You should use the {@link Request} class instead. - */ -export default class RequestHttpClient implements HttpClient { - request: Request; - - constructor(requestInstance?: Request) { - this.request = requestInstance ?? new Request({ - background: true, - headers: { - Accept: 'application/vnd.api+json', - 'Content-Type': 'application/vnd.api+json' - } - }); - } - - delete(url: string): HttpClientPromise { - this.request.options.method = 'delete'; - this.request.options.url = url; - return new RequestHttpClientPromise(this.request.send()); - } - - get(url: string): HttpClientPromise { - this.request.options.method = 'get'; - this.request.options.url = url; - return new RequestHttpClientPromise(this.request.send()); - } - - getImplementingClient() { - return this.request; - } - - head(url: string): HttpClientPromise { - this.request.options.method = 'head'; - this.request.options.url = url; - return new RequestHttpClientPromise(this.request.send()); - } - - patch(url: string, data?: Record): HttpClientPromise { - this.request.options.method = 'patch'; - this.request.options.url = url; - this.request.options.body = data; - return new RequestHttpClientPromise(this.request.send()); - } - - post(url: string, data?: Record): HttpClientPromise { - this.request.options.method = 'post'; - this.request.options.url = url; - this.request.options.body = data; - return new RequestHttpClientPromise(this.request.send()); - } - - put(url: string, data?: Record): HttpClientPromise { - this.request.options.method = 'put'; - this.request.options.url = url; - this.request.options.body = data; - return new RequestHttpClientPromise(this.request.send()); - } - - setWithCredentials(withCredientials: boolean): void { - this.request.options.withCredentials = withCredientials; - } -} diff --git a/resources/ts/Models/Http/RequestHttpClientPromise.ts b/resources/ts/Models/Http/RequestHttpClientPromise.ts deleted file mode 100644 index 8c8551900..000000000 --- a/resources/ts/Models/Http/RequestHttpClientPromise.ts +++ /dev/null @@ -1,29 +0,0 @@ -import RequestHttpClientResponse from '@osm/Models/Http/RequestHttpClientResponse'; -import type { - HttpClientPromise, - HttpClientResponse -} from 'coloquent'; -import type {Thenable} from 'coloquent/dist/httpclient/Types'; - -export default class RequestHttpClientPromise implements HttpClientPromise { - constructor(private response: Promise) {} - - catch(onRejected?: (error: any) => (Thenable | U)): Promise { - return this.response.catch(onRejected) as Promise; - } - - // eslint-disable-next-line unicorn/no-thenable - then( - onFulfilled?: (value: HttpClientResponse) => (Thenable | U), - onRejected?: (error: any) => void | (Thenable | U) - ): Promise { - const wrappedOnFulfilled = onFulfilled === undefined - ? undefined - : ((responsePromise: any) => onFulfilled(new RequestHttpClientResponse(responsePromise))); - return this.response.then( - wrappedOnFulfilled, - // @ts-ignore - onRejected - ); - } -} diff --git a/resources/ts/Models/Http/RequestHttpClientResponse.ts b/resources/ts/Models/Http/RequestHttpClientResponse.ts deleted file mode 100644 index eb0493ced..000000000 --- a/resources/ts/Models/Http/RequestHttpClientResponse.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type {HttpClientResponse} from 'coloquent'; - -export default class RequestHttpClientResponse implements HttpClientResponse { - constructor(private response: any) {} - - getData(): any { - return this.response; - } - - getUnderlying(): any { - return this.response; - } -} diff --git a/resources/ts/Models/Model.ts b/resources/ts/Models/Model.ts deleted file mode 100644 index 2fec38e7b..000000000 --- a/resources/ts/Models/Model.ts +++ /dev/null @@ -1,113 +0,0 @@ -import RequestHttpClient from '@osm/Models/Http/RequestHttpClient'; -import { - Model as BaseModel, - PaginationStrategy, - PluralResponse -} from 'coloquent'; -import dayjs from 'dayjs'; -import type {ValueOf} from 'type-fest'; - -export interface ModelAttributes { - id: number; - createdAt: Date; - updatedAt: Date; - - [key: string]: unknown; -} - -export interface ModelRelations { - [key: string]: unknown; -} - -/** - * The base model for all models. - */ -export default abstract class Model extends BaseModel { - protected static paginationStrategy = PaginationStrategy.PageBased; - protected static jsonApiBaseUrl = '/api/restify'; - getHttpClient() { return new RequestHttpClient(); } - - static dates: Record = { - createdAt: 'YYYY-MM-DDTHH:mm:ss.ssssssZ', - updatedAt: 'YYYY-MM-DDTHH:mm:ss.ssssssZ' - }; - - protected static get jsonApiType() { - return `${this.name.toLowerCase()}s`; - } - - /** - * Returns all the instances of the model. (Alias of {@link Model.get}). - */ - static all & { - new (): InstanceType; - // @ts-ignore - }>(this: M): Promise>> { - // @ts-expect-error - return this.get(); - } - - /** - * Set multiple attributes on the model. - */ - setAttributes(attributes: Partial | Map>) { - // Record to map - if (!(attributes instanceof Map)) { - // eslint-disable-next-line no-param-reassign - attributes = new Map(Object.entries(attributes) as [keyof A, ValueOf][]); - } - - for (const [attribute, value] of attributes) { - this.setAttribute(attribute, value); - } - } - - getAttribute(attributeName: AN) { - return super.getAttribute(attributeName as string) as ValueOf; - } - - getAttributes() { - return super.getAttributes() as ModelAttributes; - } - - protected getAttributeAsDate(attributeName: string) { - // @ts-ignore - let attribute: string | Date = (this.attributes as Map).get(attributeName); - if (attribute && dayjs(attribute).isValid()) { - attribute = super.getAttributeAsDate(attributeName) as Date; - } - return attribute; - } - - setAttribute(attributeName: AN, value: ValueOf) { - const date = dayjs(value as string | Date | undefined); - // @ts-expect-error - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - if (this.isDateAttribute(attributeName) && date.isValid()) { - const format = this.constructor.dates[attributeName as string]; - // eslint-disable-next-line no-param-reassign - value = (format === 'YYYY-MM-DDTHH:mm:ss.ssssssZ' ? date.toISOString() : date.format(format)) as ValueOf; - } - // @ts-expect-error — This is needed to parse the dates correctly. - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - this.attributes.set(attributeName as string, value); - } - - getRelation(relationName: RN) { - return super.getRelation(relationName as string) as ValueOf; - } - - /** - * Get model ID. - */ - getId() { - return this.getApiId(); - } - - /** - * Check if the model is new (not already saved). - */ - isNew() { - return this.getId() === undefined; - } -} diff --git a/resources/ts/Models/Record.ts b/resources/ts/Models/Record.ts new file mode 100644 index 000000000..136c8f581 --- /dev/null +++ b/resources/ts/Models/Record.ts @@ -0,0 +1,15 @@ +import {Attr, Model, SpraypaintBase} from 'spraypaint'; + +@Model() +export default class Record extends SpraypaintBase { + static baseUrl = ''; + static apiNamespace = '/api/restify'; + static clientApplication = 'OpenSTAManager'; + + @Attr({persist: false}) createdAt!: string; + @Attr({persist: false}) updatedAt!: string; + + isNew(): boolean { + return this.id === undefined; + } +} diff --git a/resources/ts/Models/User.ts b/resources/ts/Models/User.ts index d712f7c73..4b4b7b0c8 100644 --- a/resources/ts/Models/User.ts +++ b/resources/ts/Models/User.ts @@ -1,15 +1,10 @@ -import Model, { - ModelAttributes, - ModelRelations -} from '@osm/Models/Model'; +import Record from '@osm/Models/Record'; +import {Attr, Model} from 'spraypaint'; -export interface UserAttributes extends ModelAttributes { - username: string; - email: string; +@Model() +export default class User extends Record { + static jsonapiType = 'users'; + + @Attr() username!: string; + @Attr() email!: string; } - -export interface UserRelations extends ModelRelations { - // Notifications: DatabaseNotifications -} - -export default class User extends Model {} diff --git a/resources/ts/Views/Users/UserRecord.tsx b/resources/ts/Views/Users/UserRecord.tsx index e9b18f936..eb3e57eff 100644 --- a/resources/ts/Views/Users/UserRecord.tsx +++ b/resources/ts/Views/Users/UserRecord.tsx @@ -12,9 +12,9 @@ export default class UserRecord extends RecordPage { return ( <> {this.backButton(vnode)} -

{this.record?.getAttribute('username')}

+

{this.record?.username}

- {JSON.stringify(this.record?.getAttributes())} + {JSON.stringify(this.record?.attributes)} ); diff --git a/resources/ts/Views/Users/UsersRecordDialog.tsx b/resources/ts/Views/Users/UsersRecordDialog.tsx index 517fc2b55..2fa1a930c 100644 --- a/resources/ts/Views/Users/UsersRecordDialog.tsx +++ b/resources/ts/Views/Users/UsersRecordDialog.tsx @@ -35,9 +35,9 @@ export default class UsersRecordDialog extends AddEditRecordDialog { } async save() { - if (this.record.isNew()) { - this.record.setAttribute('password', 'default'); - } + // if (this.record.isNew()) { + // this.record.password = 'default'; + // } return super.save(); } }