Skip to content

Commit

Permalink
Merge pull request #244 from kodadot/equip-mee
Browse files Browse the repository at this point in the history
Extend Base & Resource
  • Loading branch information
vikiival authored Apr 15, 2023
2 parents 11ad509 + 429f638 commit 89f9558
Show file tree
Hide file tree
Showing 21 changed files with 380 additions and 57 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"extends": ["eslint-config-unjs"],
"ignorePatterns": ["tests/**", "src/model"],
"rules": {
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-unused-vars": 0,
Expand Down
43 changes: 43 additions & 0 deletions db/migrations/1681503829848-Data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module.exports = class Data1681503829848 {
name = 'Data1681503829848'

async up(db) {
await db.query(`CREATE TABLE "part" ("id" character varying NOT NULL, "name" text NOT NULL, "equippable" text array, "metadata" text, "src" text, "thumb" text, "type" character varying(5) NOT NULL, "z" integer NOT NULL, "base_id" character varying, "meta_id" character varying, CONSTRAINT "PK_58888debdf048d2dfe459aa59da" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_eb9c6227aa57a31b1ae1aa562e" ON "part" ("base_id") `)
await db.query(`CREATE INDEX "IDX_105a0f423a9c3e75b58156e70e" ON "part" ("meta_id") `)
await db.query(`ALTER TABLE "resource" DROP COLUMN "slot"`)
await db.query(`ALTER TABLE "base" ADD "events" jsonb`)
await db.query(`ALTER TABLE "resource" ADD "parts" text array`)
await db.query(`ALTER TABLE "resource" ADD "base_id" character varying`)
await db.query(`ALTER TABLE "resource" ADD "slot_id" character varying`)
await db.query(`ALTER TABLE "nft_entity" ADD "equipped_id" character varying`)
await db.query(`CREATE INDEX "IDX_d642baad1c299522a6a2a42d32" ON "resource" ("base_id") `)
await db.query(`CREATE INDEX "IDX_170d8671cccd2c4597a75bd5b7" ON "resource" ("slot_id") `)
await db.query(`CREATE INDEX "IDX_730c0494be69b620692ab83143" ON "nft_entity" ("equipped_id") `)
await db.query(`ALTER TABLE "part" ADD CONSTRAINT "FK_eb9c6227aa57a31b1ae1aa562e3" FOREIGN KEY ("base_id") REFERENCES "base"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "part" ADD CONSTRAINT "FK_105a0f423a9c3e75b58156e70e6" FOREIGN KEY ("meta_id") REFERENCES "metadata_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "resource" ADD CONSTRAINT "FK_d642baad1c299522a6a2a42d32c" FOREIGN KEY ("base_id") REFERENCES "base"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "resource" ADD CONSTRAINT "FK_170d8671cccd2c4597a75bd5b7a" FOREIGN KEY ("slot_id") REFERENCES "part"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_730c0494be69b620692ab831438" FOREIGN KEY ("equipped_id") REFERENCES "part"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
}

async down(db) {
await db.query(`DROP TABLE "part"`)
await db.query(`DROP INDEX "public"."IDX_eb9c6227aa57a31b1ae1aa562e"`)
await db.query(`DROP INDEX "public"."IDX_105a0f423a9c3e75b58156e70e"`)
await db.query(`ALTER TABLE "resource" ADD "slot" text`)
await db.query(`ALTER TABLE "base" DROP COLUMN "events"`)
await db.query(`ALTER TABLE "resource" DROP COLUMN "parts"`)
await db.query(`ALTER TABLE "resource" DROP COLUMN "base_id"`)
await db.query(`ALTER TABLE "resource" DROP COLUMN "slot_id"`)
await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "equipped_id"`)
await db.query(`DROP INDEX "public"."IDX_d642baad1c299522a6a2a42d32"`)
await db.query(`DROP INDEX "public"."IDX_170d8671cccd2c4597a75bd5b7"`)
await db.query(`DROP INDEX "public"."IDX_730c0494be69b620692ab83143"`)
await db.query(`ALTER TABLE "part" DROP CONSTRAINT "FK_eb9c6227aa57a31b1ae1aa562e3"`)
await db.query(`ALTER TABLE "part" DROP CONSTRAINT "FK_105a0f423a9c3e75b58156e70e6"`)
await db.query(`ALTER TABLE "resource" DROP CONSTRAINT "FK_d642baad1c299522a6a2a42d32c"`)
await db.query(`ALTER TABLE "resource" DROP CONSTRAINT "FK_170d8671cccd2c4597a75bd5b7a"`)
await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_730c0494be69b620692ab831438"`)
}
}
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"dependencies": {
"@kodadot1/metasquid": "^0.1.5-rc.0",
"@kodadot1/minimark": "0.1.6-rc.2",
"@kodadot1/minimark": "0.1.7-rc.2",
"@kodadot1/minipfs": "^0.3.0-rc.0",
"@subsquid/archive-registry": "2.1.10",
"@subsquid/big-decimal": "^0.0.0",
Expand Down
40 changes: 35 additions & 5 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type NFTEntity @entity {
emoteCount: Int!
emotes: [Emote!] @derivedFrom(field: "nft")
events: [Event!] @derivedFrom(field: "nft")
equipped: Part
hash: String! @index @unique
id: ID!
instance: String
Expand Down Expand Up @@ -102,6 +103,14 @@ type CollectionEvent @jsonField {
meta: String!
}

type BaseEvent @jsonField {
blockNumber: String
timestamp: DateTime
caller: String!
interaction: String!
meta: String!
}

type Emote @entity {
id: ID!
nft: NFTEntity!
Expand Down Expand Up @@ -134,6 +143,7 @@ enum Interaction {
UNLIST
PAY_ROYALTY
ROYALTY
UNEQUIP
}

# Remarkably specific types
Expand All @@ -145,12 +155,31 @@ type Base @entity {
currentOwner: String!
meta: MetadataEntity
metadata: String
# parts: [BasePart!] @derivedFrom(field: "base")
parts: [Part!] @derivedFrom(field: "base")
themes: [Theme!] @derivedFrom(field: "base")
# attachedToResources: [Resource!] @derivedFrom (field: "base")
# logs: [Call!] @derivedFrom (field: "base") // Rename to event
events: [BaseEvent!]
}

type Part @entity {
id: ID! # baseId.partId base-12126274-SKBS.Eyebrows2white
name: String! # Eyebrows2white // RMRK calls it part_id
base: Base!
equippable: [String!]
metadata: String
meta: MetadataEntity
src: String
thumb: String # RMRK GraphQL does not have this
# resources: [ResourcePart!] @derivedFrom (field: "resource")
type: PartType!
z: Int!
}

# type ResourcePart @entity {
# resource: Resource!
# part: Part!
# }


type Theme @entity {
id: ID!
Expand All @@ -165,12 +194,13 @@ type Theme @entity {
# https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/entities/nft.md#resources-and-resource
type Resource @entity {
id: ID!
# base: Base
base: Base
src: String
meta: MetadataEntity
metadata: String
slot: String
slot: Part
thumb: String
parts: [String!]
# parts: [ResourcePart!] @derivedFrom (field: "resource")
priority: Int! # 0 is highest priority, need to check if this is correct
pending: Boolean!
Expand All @@ -185,7 +215,7 @@ enum BaseType {
png
}

enum BasePartType {
enum PartType {
fixed
slot
}
Expand Down
6 changes: 5 additions & 1 deletion src/mappings/utils/consolidator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { real, burned, plsBe, plsNotBe } from '@kodadot1/metasquid/consolidator'
import { isTransferable } from '@kodadot1/minimark/v2'
import { Base, CollectionEntity, NFTEntity } from '../../model/generated'
import { Base, CollectionEntity, NFTEntity, Resource } from '../../model/generated'
import { BatchArg, ExtraCall, RmrkInteraction, Transfer } from './types'
import { serializer } from './serializer'

Expand All @@ -12,6 +12,10 @@ export function transferable({ transferable }: NFTEntity) {
return !!transferable
}

export function pending({ pending }: NFTEntity | Resource) {
return pending
}

export function withMeta(interaction: RmrkInteraction): interaction is RmrkInteraction {
return !!interaction.value
}
Expand Down
13 changes: 11 additions & 2 deletions src/mappings/utils/entity.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { takeFirst } from '@kodadot1/metasquid'
import { findByRawQuery } from '@kodadot1/metasquid/entity'
import { FindOptionsWhere } from 'typeorm'
import { NFTEntity } from '../../model'
import { rootOwnerQuery } from '../../server-extension/query/nft'
import { NFTEntity, Resource } from '../../model'
import { parentBaseResouceQuery, rootOwnerQuery } from '../../server-extension/query/nft'
import { EntityConstructor, Store } from './types'

export type EntityWithId = {
Expand Down Expand Up @@ -74,3 +74,12 @@ export async function findRootItemById(store: Store, id: string): Promise<NFTEnt

return result
}

export async function findParentBaseResouce(store: Store, id: string, baseId: string): Promise<Resource> {
const result = await findByRawQuery(store, Resource, parentBaseResouceQuery, [id, baseId]).then(takeFirst)
if (!result) {
throw new Error(`Parent resource with id ${id} not found`)
}

return result
}
4 changes: 4 additions & 0 deletions src/mappings/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ export const debug = (action: Action, message: Record<any, any>, serialize?: boo
logger.debug(`[${action}] ${JSON.stringify(message, serialize ? serializer : undefined, 2)}`)
}

export const warn = (action: Action, message: string) => {
logger.warn(`⚠️ [${action}] ${message}`)
}

export { logger as default } from '@kodadot1/metasquid/logger'
18 changes: 14 additions & 4 deletions src/mappings/v2/addResource.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { burned, plsNotBe } from '@kodadot1/metasquid/consolidator'
import { getOrCreate, getOrFail as get } from '@kodadot1/metasquid/entity'
import { getOrCreate, getOrFail as get, getOptional } from '@kodadot1/metasquid/entity'
import { Optional } from '@kodadot1/metasquid/types'
import { Resadd } from '@kodadot1/minimark/v2'

import { NFTEntity, Resource } from '../../model'
import { Base, NFTEntity, Part, Resource } from '../../model'
import { handleMetadata } from '../shared'
import { createEvent } from '../shared/event'
import { unwrap } from '../utils'
Expand All @@ -26,7 +26,7 @@ export async function addResource(context: Context) {
const isPending = nft.currentOwner !== caller
nft.updatedAt = timestamp

const final = await getOrCreate(context.store, Resource, interaction.value.id, { ...interaction.value })
const final = await getOrCreate(context.store, Resource, interaction.value.id, {})

if (interaction.value.metadata) {
const metadata = await handleMetadata(interaction.value.metadata, '', context.store)
Expand All @@ -39,7 +39,17 @@ export async function addResource(context: Context) {
final.metadata = interaction.value.metadata
final.src = interaction.value.src
final.thumb = interaction.value.thumb
final.priority = 0
final.priority = 99

if (interaction.value.base) {
const base = await getOptional<Base>(context.store, Base, interaction.value.base)
final.base = base
}

if (interaction.value.slot) {
const part = await getOptional<Part>(context.store, Part, interaction.value.slot)
final.slot = part
}

success(OPERATION, `${nft.id} from ${caller}`)
await context.store.save(nft)
Expand Down
27 changes: 25 additions & 2 deletions src/mappings/v2/base.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Optional } from '@kodadot1/metasquid/types'
import { CreatedBase } from '@kodadot1/minimark/v2'
import { CreatedBase, toPartId } from '@kodadot1/minimark/v2'

import { Base, BaseType } from '../../model'
import { Base, BaseType, Part, PartType } from '../../model'
import { handleMetadata } from '../shared'
import { unwrap } from '../utils/extract'
import { baseId } from '../utils/helper'
Expand Down Expand Up @@ -32,6 +32,29 @@ export async function base(context: Context) {
}

await context.store.save(final)

// id of part is partId
if (base.parts.length > 0) {
for (const basePart of base.parts) {
const partId = toPartId(id, basePart.id)
const part = await createUnlessNotExist(partId, Part, context)
part.name = basePart.id
part.base = final
part.id = partId
part.metadata = basePart.metadata
part.type = basePart.type as PartType

if (basePart.metadata) {
const metadata = await handleMetadata(basePart.metadata, '', context.store)
part.meta = metadata
}

await context.store.save(part)
}
}

// TODO: themes
// if (base.themes) {}
} catch (e) {
error(e, OPERATION, JSON.stringify(base))
}
Expand Down
51 changes: 42 additions & 9 deletions src/mappings/v2/equip.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { burned, plsNotBe } from '@kodadot1/metasquid/consolidator'
import { getOrFail as get } from '@kodadot1/metasquid/entity'
import { burned, plsBe, plsNotBe, real } from '@kodadot1/metasquid/consolidator'
import { getOrFail as get, getWhere, getWith } from '@kodadot1/metasquid/entity'
import { Optional } from '@kodadot1/metasquid/types'
import { Equip } from '@kodadot1/minimark/v2'
import { Equip, baseIdFromPartId } from '@kodadot1/minimark/v2'

import { NFTEntity } from '../../model'
import { NFTEntity, Part } from '../../model'
import { createEvent } from '../shared/event'
import { unwrap } from '../utils'
import { isOwnerOrElseError } from '../utils/consolidator'
import { error, success } from '../utils/logger'
import { isOwnerOrElseError, pending } from '../utils/consolidator'
import { findParentBaseResouce } from '../utils/entity'
import { error, success, warn } from '../utils/logger'
import { Action, Context } from '../utils/types'
import { getEquip } from './getters'

Expand All @@ -19,20 +20,52 @@ export async function equip(context: Context) {
try {
const { value: equip, caller, timestamp, blockNumber, version } = unwrap(context, getEquip)
interaction = equip
const nft = await get<NFTEntity>(context.store, NFTEntity, interaction.id)
const nft = await getWith<NFTEntity>(context.store, NFTEntity, interaction.id, { parent: true, equipped: true, collection: true })
plsNotBe(burned, nft)
isOwnerOrElseError(nft, caller)
plsBe(real, nft.parent)
plsNotBe(pending, nft)
nft.updatedAt = timestamp

// TODO: add logic for EQUIPing resource
if (interaction.baseslot === '') {
const id = nft.equipped?.id
nft.equipped = null
await createEvent(nft, Action.UNEQUIP, { blockNumber, caller, timestamp, version }, `${id}`, context.store)

await context.store.save(nft)
return
}

if (nft.equipped?.id === interaction.baseslot) {
warn(OPERATION, `Already equipped ${nft.id} in slot ${interaction.baseslot} from ${caller}`)
return
}

const baseId = baseIdFromPartId(interaction.baseslot)

const resource = await findParentBaseResouce(context.store, nft.parent?.id as string, baseId)
plsNotBe(pending, resource)

const part = await get<Part>(context.store, Part, interaction.baseslot)
if (part.type !== 'slot') {
throw new Error(`Part ${part.id} is not a slot`)
}

const isEqquipable = part.equippable?.some((e) => e === nft.collection?.id || e === '*')

if (!isEqquipable) {
throw new Error(`Part ${part.id} is not equippable by ${nft.collection?.id}`)
}

nft.equipped = part

success(OPERATION, `${nft.id} from ${caller}`)
await context.store.save(nft)
await createEvent(
nft,
OPERATION,
{ blockNumber, caller, timestamp, version },
`${interaction.id}::${interaction.baseslot}`,
`${interaction.baseslot}`,
context.store
)
} catch (e) {
Expand Down
Loading

0 comments on commit 89f9558

Please sign in to comment.