Skip to content

Commit

Permalink
debt(sequelize): move attributes to declare
Browse files Browse the repository at this point in the history
  • Loading branch information
Betree committed Jun 13, 2022
1 parent 7e14716 commit 2b798c4
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 133 deletions.
25 changes: 19 additions & 6 deletions docs/adding-new-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,28 @@ module.exports = {
Example: `server/models/MyTable.ts`

```ts
import type { CreationOptional, InferAttributes, InferCreationAttributes } from 'sequelize';
import sequelize, { DataTypes, Model } from '../lib/sequelize';

class MyTable extends Model<InferAttributes<MyTable>, InferCreationAttributes<MyTable>> {
declare id: CreationOptional<number>;
// Define all attributes for the model
interface MyTableAttributes {
id: number;
MyCollectiveId: number;
createdAt: Date;
updatedAt: Date;
deletedAt: Date;
}

// Define attributes that can be used for model creation
interface MyTableCreateAttributes {
MyCollectiveId: number;
}

class MyTable extends Model<MyTableAttributes, MyTableCreateAttributes> implements MyTableAttributes {
declare id: number;
declare MyCollectiveId: number;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;
declare deletedAt: CreationOptional<Date>;
declare createdAt: Date;
declare updatedAt: Date;
declare deletedAt: Date;
}

MyTable.init(
Expand Down
31 changes: 18 additions & 13 deletions server/graphql/common/expenses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { canUseFeature } from '../../lib/user-permissions';
import { formatCurrency } from '../../lib/utils';
import models, { sequelize } from '../../models';
import { ExpenseAttachedFile } from '../../models/ExpenseAttachedFile';
import { ExpenseItem } from '../../models/ExpenseItem';
import { ExpenseItem, ItemsDiff } from '../../models/ExpenseItem';
import { PayoutMethodTypes } from '../../models/PayoutMethod';
import paymentProviders from '../../paymentProviders';
import {
Expand Down Expand Up @@ -955,15 +955,13 @@ export const changesRequireStatusUpdate = (
export const getItemsChanges = async (
existingItems: ExpenseItem[],
expenseData: ExpenseData,
): Promise<
[boolean, Record<string, unknown>[], [Record<string, unknown>[], ExpenseItem[], Record<string, unknown>[]]]
> => {
): Promise<[boolean, ItemsDiff]> => {
if (expenseData.items) {
const itemsDiff = models.ExpenseItem.diffDBEntries(existingItems, expenseData.items);
const hasItemChanges = flatten(<unknown[]>itemsDiff).length > 0;
return [hasItemChanges, expenseData.items, itemsDiff];
return [hasItemChanges, itemsDiff];
} else {
return [false, [], [[], [], []]];
return [false, [[], [], []]];
}
};

Expand Down Expand Up @@ -1021,7 +1019,7 @@ export async function editExpense(
],
});

const [hasItemChanges, itemsData, itemsDiff] = await getItemsChanges(expense.items, expenseData);
const [hasItemChanges, itemsDiff] = await getItemsChanges(expense.items, expenseData);
const taxes = expenseData.tax || expense.data?.taxes || [];
const expenseType = expenseData.type || expense.type;
checkTaxes(expense.collective, expense.collective.host, expenseType, taxes);
Expand Down Expand Up @@ -1119,11 +1117,13 @@ export async function editExpense(

// Update items
if (hasItemChanges) {
checkExpenseItems({ ...expense.dataValues, ...cleanExpenseData }, itemsData, taxes);
const [newItemsData, oldItems, itemsToUpdate] = itemsDiff;
const simulatedItems = ExpenseItem.simulateItemsDiff(expense.items, itemsDiff);
checkExpenseItems({ ...expense.dataValues, ...cleanExpenseData }, simulatedItems, taxes);
// TODO: Move the code below to ExpenseItem.applyItemsDiff
const [newItemsData, removedItems, itemsToUpdate] = itemsDiff;
await Promise.all(<Promise<void>[]>[
// Delete
...oldItems.map(item => {
...removedItems.map(item => {
return item.destroy({ transaction: t });
}),
// Create
Expand All @@ -1135,6 +1135,8 @@ export async function editExpense(
return models.ExpenseItem.updateFromData(itemData, t);
}),
]);

expense.items = await expense.getItems({ transaction: t });
}

// Update expense
Expand All @@ -1155,10 +1157,13 @@ export async function editExpense(
);

await createAttachedFiles(expense, newAttachedFiles, remoteUser, t);
await Promise.all(removedAttachedFiles.map((file: ExpenseAttachedFile) => file.destroy()));
await Promise.all(removedAttachedFiles.map((file: ExpenseAttachedFile) => file.destroy({ transaction: t })));
await Promise.all(
updatedAttachedFiles.map((file: Record<string, unknown>) =>
models.ExpenseAttachedFile.update({ url: file.url }, { where: { id: file.id, ExpenseId: expense.id } }),
models.ExpenseAttachedFile.update(
{ url: file.url },
{ where: { id: file.id, ExpenseId: expense.id }, transaction: t },
),
),
);
}
Expand All @@ -1173,7 +1178,7 @@ export async function editExpense(
const updatedExpenseProps = {
...cleanExpenseData,
data: !expense.data ? null : cloneDeep(expense.data),
amount: computeTotalAmountForExpense(expenseData.items || expense.items, taxes),
amount: computeTotalAmountForExpense(expense.items, taxes),
lastEditedById: remoteUser.id,
incurredAt: expenseData.incurredAt || new Date(),
status,
Expand Down
23 changes: 0 additions & 23 deletions server/lib/restore-sequelize-attributes-on-class.ts

This file was deleted.

9 changes: 2 additions & 7 deletions server/models/CurrencyExchangeRate.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import type { CreationOptional, InferAttributes, InferCreationAttributes } from 'sequelize';

import sequelize, { DataTypes, Model } from '../lib/sequelize';

/**
* Sequelize model to represent an CurrencyExchangeRate, linked to the `CurrencyExchangeRates` table.
*/
export class CurrencyExchangeRate extends Model<
InferAttributes<CurrencyExchangeRate>,
InferCreationAttributes<CurrencyExchangeRate>
> {
public declare readonly id: CreationOptional<number>;
export class CurrencyExchangeRate extends Model {
public declare readonly id: number;
public declare rate: number;
public declare from: string;
public declare to: string;
Expand Down
57 changes: 36 additions & 21 deletions server/models/ExpenseItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,61 @@ import { DataTypes, Model, Transaction } from 'sequelize';

import { diffDBEntries } from '../lib/data';
import { isValidUploadedImage } from '../lib/images';
import restoreSequelizeAttributesOnClass from '../lib/restore-sequelize-attributes-on-class';
import { buildSanitizerOptions, sanitizeHTML } from '../lib/sanitize-html';
import sequelize from '../lib/sequelize';

import models from '.';

// An array like [newItemsData, removedItems, updatedItemsData]
export type ItemsDiff = [Record<string, unknown>[], ExpenseItem[], Record<string, unknown>[]];

/**
* Sequelize model to represent an ExpenseItem, linked to the `ExpenseItems` table.
*/
export class ExpenseItem extends Model {
public readonly id!: number;
public ExpenseId!: number;
public CreatedByUserId!: number;
public amount!: number;
public url!: string;
public createdAt!: Date;
public updatedAt!: Date;
public deletedAt: Date;
public incurredAt!: Date;
public description: string;
public declare readonly id: number;
public declare ExpenseId: number;
public declare CreatedByUserId: number;
public declare amount: number;
public declare url: string;
public declare createdAt: Date;
public declare updatedAt: Date;
public declare deletedAt: Date;
public declare incurredAt: Date;
public declare description: string;

private static editableFields = ['amount', 'url', 'description', 'incurredAt'];

constructor(...args) {
super(...args);
restoreSequelizeAttributesOnClass(new.target, this);
}

/**
* Based on `diffDBEntries`, diff two items list to know which ones where
* added, removed or added.
* @returns [newEntries, removedEntries, updatedEntries]
*/
static diffDBEntries = (
baseItems: ExpenseItem[],
itemsData: Record<string, unknown>[],
): [Record<string, unknown>[], ExpenseItem[], Record<string, unknown>[]] => {
static diffDBEntries = (baseItems: ExpenseItem[], itemsData: Record<string, unknown>[]): ItemsDiff => {
return diffDBEntries(baseItems, itemsData, ExpenseItem.editableFields);
};

/**
* Simulate a diff on a list of existing items, returns a list of items data as we would expect to find it
* once recorded in the DB.
*/
static simulateItemsDiff = (items: ExpenseItem[], diff: ItemsDiff): Record<string, unknown>[] => {
const [newItems, removedItems, updatedItems] = diff;
return (
items
// Remove items that were removed
.filter(item => !removedItems.some(removedItem => removedItem.id === item.id))
// Update (or keep it the same if not in updatedItems)
.map(item => {
const existingValues = item['dataValues'];
const updatedItemData = updatedItems.find(updatedItem => updatedItem.id === item.id);
return updatedItemData ? { ...existingValues, ...updatedItemData } : existingValues;
})
// Add new
.concat(newItems)
);
};

/**
* Create an expense item from user-submitted data.
* @param itemData: The (potentially unsafe) user data. Fields will be whitelisted.
Expand All @@ -53,7 +68,7 @@ export class ExpenseItem extends Model {
itemData: Record<string, unknown>,
user: typeof models.User,
expense: typeof models.Expense,
dbTransaction: Transaction | null,
dbTransaction: Transaction | null = null,
): Promise<ExpenseItem> {
const cleanData = ExpenseItem.cleanData(itemData);
return ExpenseItem.create(
Expand Down
22 changes: 16 additions & 6 deletions server/models/HostApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,27 @@ export enum HostApplicationStatus {
EXPIRED = 'EXPIRED',
}

export class HostApplication extends Model<InferAttributes<HostApplication>, InferCreationAttributes<HostApplication>> {
public declare readonly id: CreationOptional<number>;
interface HostApplicationCreationAttributes {
CollectiveId: number;
HostCollectiveId: number;
status: HostApplicationStatus;
customData?: Record<string, unknown> | null;
message?: string;
createdAt?: Date;
updatedAt?: Date;
deletedAt?: Date | null;
}

export class HostApplication extends Model<HostApplication, HostApplicationCreationAttributes> {
public declare readonly id: number;
public declare CollectiveId: number;
public declare HostCollectiveId: number;
public CreatedByUserId: number;
public declare status: HostApplicationStatus;
public declare customData: Record<string, unknown> | null;
public declare message: string;
public declare createdAt: CreationOptional<Date>;
public declare updatedAt: CreationOptional<Date>;
public declare deletedAt: CreationOptional<Date>;
public declare createdAt: Date;
public declare updatedAt: Date;
public declare deletedAt: Date | null;

// ---- Static ----

Expand Down
11 changes: 6 additions & 5 deletions server/models/MigrationLog.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { CreationOptional, InferAttributes, InferCreationAttributes } from 'sequelize';

import sequelize, { DataTypes, Model } from '../lib/sequelize';

export enum MigrationLogType {
Expand All @@ -21,10 +19,13 @@ export type MigrationLogDataForMergeAccounts = {

type MigrationLogData = MigrationLogDataForMergeAccounts | Record<string, unknown>;

class MigrationLog extends Model<InferAttributes<MigrationLog>, InferCreationAttributes<MigrationLog>> {
public declare id: CreationOptional<number>;
class MigrationLog
extends Model<MigrationLogAttributes, MigrationLogCommonCreateAttributes>
implements MigrationLogAttributes
{
public declare id: number;
public declare type: MigrationLogType;
public declare createdAt: CreationOptional<Date>;
public declare createdAt: Date;
public declare description: string;
public declare data: MigrationLogData;
public declare CreatedByUserId: number;
Expand Down
25 changes: 21 additions & 4 deletions server/models/OAuthAuthorizationCode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { CreationOptional, InferAttributes, InferCreationAttributes, NonAttribute } from 'sequelize';

import sequelize, { DataTypes, Model } from '../lib/sequelize';

import models from '.';
Expand All @@ -19,8 +17,27 @@ class OAuthAuthorizationCode extends Model<
public declare ApplicationId: number;
public declare UserId: number;

public declare application?: NonAttribute<typeof models.Application>;
public declare user?: NonAttribute<typeof models.User>;
// Define all attributes for the model
interface OAuthAuthorizationCodeAttributes extends OAuthAuthorizationCodeCreateAttributes {
id: number;
}

class OAuthAuthorizationCode
extends Model<OAuthAuthorizationCodeAttributes, OAuthAuthorizationCodeCreateAttributes>
implements OAuthAuthorizationCodeAttributes
{
public declare id: number;
public declare code: string;
public declare redirectUri: string;
public declare expiresAt: Date;
public declare data: Record<string, unknown>;
public declare createdAt: Date;
public declare updatedAt: Date;
public declare deletedAt?: Date;
public declare ApplicationId: number;
public declare UserId: number;
public declare application: typeof models.Application;
public declare user: typeof models.User;
}

OAuthAuthorizationCode.init(
Expand Down
10 changes: 5 additions & 5 deletions server/models/PayoutMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ export type PayoutMethodDataType =
/**
* Sequelize model to represent an PayoutMethod, linked to the `PayoutMethods` table.
*/
export class PayoutMethod extends Model<InferAttributes<PayoutMethod>, InferCreationAttributes<PayoutMethod>> {
public declare readonly id: CreationOptional<number>;
export class PayoutMethod extends Model {
public declare readonly id: number;
public declare type: PayoutMethodTypes;
public declare createdAt: CreationOptional<Date>;
public declare updatedAt: CreationOptional<Date>;
public declare deletedAt: CreationOptional<Date>;
public declare createdAt: Date;
public declare updatedAt: Date;
public declare deletedAt: Date;
public declare name: string;
public declare isSaved: boolean;
public declare CollectiveId: number;
Expand Down
4 changes: 1 addition & 3 deletions server/models/PaypalPlan.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { InferAttributes } from 'sequelize';

import sequelize, { DataTypes, Model } from '../lib/sequelize';

import { PaypalProductCreateAttributes } from './PaypalProduct';
Expand All @@ -21,7 +19,7 @@ interface PaypalPlanCreateWithProductAttributes extends PaypalPlanCommonCreateAt

type PaypalPlanCreateAttributes = PaypalPlanCreateWithProductIdAttributes | PaypalPlanCreateWithProductAttributes;

class PaypalPlan extends Model<InferAttributes<PaypalPlan>, PaypalPlanCreateAttributes> {
class PaypalPlan extends Model<PaypalPlanAttributes, PaypalPlanCreateAttributes> implements PaypalPlanAttributes {
public declare id: string;
public declare ProductId: string;
public declare currency: string;
Expand Down
7 changes: 5 additions & 2 deletions server/models/PaypalProduct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InferAttributes } from 'sequelize';
import sequelize, { DataTypes, Model } from '../lib/sequelize';

import sequelize, { DataTypes, Model } from '../lib/sequelize';

Expand All @@ -8,7 +8,10 @@ export interface PaypalProductCreateAttributes {
CollectiveId: number;
}

class PaypalProduct extends Model<InferAttributes<PaypalProduct>, PaypalProductCreateAttributes> {
class PaypalProduct
extends Model<PaypalProductAttributes, PaypalProductCreateAttributes>
implements PaypalProductAttributes
{
public declare id: string;
public declare CollectiveId: number;
public declare TierId: number;
Expand Down

0 comments on commit 2b798c4

Please sign in to comment.