Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add virtual third parties #772

Merged
merged 10 commits into from
Sep 20, 2024
110 changes: 110 additions & 0 deletions migrations/1725996635075_add-virtual-third-parties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { MigrationBuilder } from 'node-pg-migrate'
import { VirtualThirdParty } from '../src/ThirdParty/VirtualThirdParty.model'
import { Collection } from '../src/Collection/Collection.model'

const update_updated_at_column_function_name = 'update_updated_at_column'
const update_updated_at_trigger_name = 'update_updated_at'
const delete_virtual_third_party_function_name =
'delete_virtual_third_party_function'
const delete_virtual_third_party_trigger_name =
'delete_virtual_third_party_trigger'

export async function up(pgm: MigrationBuilder): Promise<void> {
pgm.createTable(VirtualThirdParty.tableName, {
id: {
type: 'TEXT',
primaryKey: true,
notNull: true,
},
managers: {
type: 'TEXT',
notNull: true,
},
raw_metadata: {
type: 'TEXT',
notNull: true,
},
created_at: {
type: 'TIMESTAMP',
notNull: true,
default: pgm.func('current_timestamp'),
},
updated_at: {
type: 'TIMESTAMP',
notNull: true,
default: pgm.func('current_timestamp'),
},
})

// Create the trigger function to automatically update the updated_at column
pgm.createFunction(
update_updated_at_column_function_name,
[],
{
returns: 'TRIGGER',
language: 'plpgsql',
},
`
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
`
)

// Create the trigger to update the updated_at column
pgm.createTrigger(
VirtualThirdParty.tableName,
update_updated_at_trigger_name,
{
when: 'BEFORE',
operation: 'UPDATE',
level: 'ROW',
function: update_updated_at_column_function_name,
condition: 'OLD.* IS DISTINCT FROM NEW.*',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the updated_at field be updated with each update? or should it meet a specific condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated_at column will get updated only if any column in the row gets updated to something new. If it doesn't, we'll be saving a write operation by not updating the updated_at.

}
)

// Create the trigger function to delete a virtual third party if no collections are assigned
pgm.createFunction(
delete_virtual_third_party_function_name,
[],
{
returns: 'TRIGGER',
language: 'plpgsql',
},
`
BEGIN
IF NOT EXISTS (SELECT 1 FROM ${Collection.tableName} WHERE third_party_id = OLD.third_party_id) THEN
DELETE FROM ${VirtualThirdParty.tableName} WHERE id = OLD.third_party_id;
END IF;
RETURN OLD;
END;
`
)

// Create the trigger to delete a virtual third party if no collections are assigned
pgm.createTrigger(
Collection.tableName,
delete_virtual_third_party_trigger_name,
{
when: 'AFTER',
operation: 'DELETE',
level: 'ROW',
function: delete_virtual_third_party_function_name,
}
)
}

export async function down(pgm: MigrationBuilder): Promise<void> {
// Drop the triggers
pgm.dropTrigger(VirtualThirdParty.tableName, update_updated_at_trigger_name)
pgm.dropTrigger(Collection.tableName, delete_virtual_third_party_trigger_name)

// Drop the trigger functions
pgm.dropFunction(update_updated_at_column_function_name, [])
pgm.dropFunction(delete_virtual_third_party_function_name, [])

pgm.dropTable(VirtualThirdParty.tableName)
}
13 changes: 13 additions & 0 deletions spec/mocks/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ThirdPartyFragment,
ThirdPartyMetadataType,
} from '../../src/ethereum/api/fragments'
import { ThirdParty } from '../../src/ThirdParty/ThirdParty.types'
import { toUnixTimestamp } from '../../src/utils/parse'
import { wallet } from './wallet'

Expand Down Expand Up @@ -75,6 +76,18 @@ export const thirdPartyFragmentMock: ThirdPartyFragment = {
},
}

export const thirdPartyMock: ThirdParty = {
id: thirdPartyFragmentMock.id,
root: thirdPartyFragmentMock.root,
managers: thirdPartyFragmentMock.managers,
isApproved: thirdPartyFragmentMock.isApproved,
maxItems: thirdPartyFragmentMock.maxItems,
name: '',
description: '',
contracts: [],
published: false,
}

export type ResultCollection = Omit<
FullCollection,
'reviewed_at' | 'created_at' | 'updated_at' | 'urn_suffix'
Expand Down
14 changes: 7 additions & 7 deletions spec/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { CollectionFragment, ItemFragment } from '../src/ethereum/api/fragments'
import { Collection } from '../src/Collection'
import { ItemCuration } from '../src/Curation/ItemCuration'
import { Ownable } from '../src/Ownable/Ownable'
import { ThirdPartyService } from '../src/ThirdParty/ThirdParty.service'
import { Item } from '../src/Item/Item.model'
import { ItemAttributes } from '../src/Item/Item.types'
import { thirdPartyAPI } from '../src/ethereum/api/thirdParty'
import { wallet } from './mocks/wallet'
import { dbCollectionMock } from './mocks/collections'
import { tpWearableMock } from './mocks/peer'
Expand Down Expand Up @@ -146,8 +146,8 @@ export function mockCollectionAuthorizationMiddleware(
}

/**
* Mocks the "isManager" method of the thirdPartyAPI module.
* This mock requires the thirdPartyAPI's isManager method to be mocked first.
* Mocks the "isManager" method of the ThirdPartyService module.
* This mock requires the ThirdPartyService's isManager method to be mocked first.
*
* @param ethAddress - The ethAddress of the user that will be requesting authorization to the collection.
* @param isManager - If the user is a manager or not.
Expand All @@ -156,14 +156,14 @@ export function mockIsThirdPartyManager(
ethAddress: string,
isManager: boolean
) {
if (!(thirdPartyAPI.isManager as jest.Mock).mock) {
if (!(ThirdPartyService.isManager as jest.Mock).mock) {
throw new Error(
"isManager should be mocked to mock the withModelExists middleware but it isn't"
)
}

;(thirdPartyAPI.isManager as jest.MockedFunction<
typeof thirdPartyAPI.isManager
;(ThirdPartyService.isManager as jest.MockedFunction<
typeof ThirdPartyService.isManager
>).mockImplementationOnce((_, manager) =>
Promise.resolve(manager === ethAddress && isManager)
)
Expand Down Expand Up @@ -386,7 +386,7 @@ export function mockThirdPartyCollectionURNExists(
): void {
if (!(Collection.isURNRepeated as jest.Mock).mock) {
throw new Error(
"thirdPartyAPI.isPublished should be mocked to mock the isPublished method but it isn't"
"Collection.isURNRepeated should be mocked to mock the thirdPartyCollectionExists method but it isn't"
)
}
;(Collection.isURNRepeated as jest.MockedFunction<
Expand Down
Loading
Loading