Skip to content

Commit

Permalink
feat: add claim page
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Apr 5, 2024
1 parent a3be194 commit b37c4e7
Show file tree
Hide file tree
Showing 33 changed files with 1,284 additions and 170 deletions.
17 changes: 11 additions & 6 deletions api-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,14 @@ type AppConfig {
}

type Claim {
account: String!
amount: String!
community: Community
communityId: String!
createdAt: DateTime
id: String!
minter: String!
identity: Identity
minter: TokenGatorMinter
name: String!
provider: IdentityProvider!
providerId: String!
Expand All @@ -96,9 +99,9 @@ type Claim {
}

input ClaimAdminCreateInput {
account: String!
amount: String
communityId: String!
minter: String!
provider: IdentityProvider!
providerId: String!
}
Expand Down Expand Up @@ -127,17 +130,17 @@ enum ClaimStatus {
}

input ClaimUserCreateInput {
account: String!
amount: String
communityId: String!
minter: String!
provider: IdentityProvider!
providerId: String!
}

input ClaimUserFindManyInput {
account: String
communityId: String!
limit: Int = 10
minter: String
page: Int = 1
provider: IdentityProvider
providerId: String
Expand Down Expand Up @@ -204,15 +207,15 @@ scalar DateTime

type Identity {
challenges: [IdentityChallenge!]
createdAt: DateTime!
createdAt: DateTime
expired: Boolean
id: String!
name: String
owner: User
profile: JSON
provider: IdentityProvider!
providerId: String!
updatedAt: DateTime!
updatedAt: DateTime
url: String
verified: Boolean
}
Expand Down Expand Up @@ -430,6 +433,8 @@ type Query {
userFindOnePreset(presetId: String!): Preset
userFindOneUser(username: String!): User
userFindOneWallet(publicKey: String!): Wallet
userGetClaim(claimId: String!): Claim!
userGetClaims: [Claim!]!
userGetMinter(account: String!): TokenGatorMinter!
userGetMinterAssets(account: String!): JSON!
userGetMinters: TokenGatorMinter!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,12 @@ export class ApiClaimDataUserService {
async updateClaim(claimId: string, input: ClaimUserUpdateInput) {
return this.data.update(claimId, input)
}

async userGetClaims(userId: string) {
return this.data.getClaims(userId)
}

async userGetClaim(userId: string, claimId: string) {
return this.data.getClaim(userId, claimId)
}
}
65 changes: 62 additions & 3 deletions libs/api/claim/data-access/src/lib/api-claim-data.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { Injectable } from '@nestjs/common'
import { ClaimStatus, Prisma } from '@prisma/client'
import { Injectable, Logger } from '@nestjs/common'
import { ClaimStatus, Identity, IdentityProvider, Prisma } from '@prisma/client'
import { ApiCoreService, PagingInputFields } from '@tokengator-mint/api-core-data-access'
import { ApiPresetService, TokenGatorMinter } from '@tokengator-mint/api-preset-data-access'
import { LRUCache } from 'lru-cache'
import { ClaimPaging } from './entity/claim.entity'

@Injectable()
export class ApiClaimDataService {
constructor(private readonly core: ApiCoreService) {}
private readonly logger = new Logger(ApiClaimDataService.name)
private readonly minterCache = new LRUCache<string, TokenGatorMinter>({
max: 1000,
ttl: 1000 * 60 * 5, // 5 minutes
fetchMethod: async (account) => {
this.logger.verbose(`minterCache: Cache miss for ${account}`)
return this.preset.minter.getMinter(account)
},
})

constructor(private readonly core: ApiCoreService, private readonly preset: ApiPresetService) {}

async create(input: Omit<Prisma.ClaimUncheckedCreateInput, 'amount' | 'status'> & { amount?: string }) {
return this.core.data.claim.create({
Expand Down Expand Up @@ -41,4 +53,51 @@ export class ApiClaimDataService {
async update(claimId: string, input: Prisma.ClaimUpdateInput) {
return this.core.data.claim.update({ where: { id: claimId }, data: input })
}

async getClaims(userId: string) {
const ids: Map<IdentityProvider, Identity[]> = await this.core.getUserIdentityMap({ userId })
const OR: Prisma.ClaimWhereInput[] = []

for (const provider of ids.keys()) {
const identities = ids.get(provider)
for (const { profile, providerId } of identities || []) {
OR.push({ provider, providerId })
// This allows for adding claims to a user by their username instead of providerId
const username = (profile as { username?: string })?.username
if (username) {
OR.push({ provider, providerId: username })
}
}
}

return this.core.data.claim.findMany({ where: { OR }, include: { community: true } }).then(async (claims) => {
const uniqueMinters = Array.from(new Set(claims.map((claim) => claim.account)))
const minters = await Promise.all([...uniqueMinters.map((minter) => this.minterCache.fetch(minter))])

return claims.map((claim) => {
const identity = ids
.get(claim.provider)
?.find(
({ profile, providerId }) =>
providerId === claim.providerId || (profile as { username?: string })?.username === claim.providerId,
)
const minter = minters.find((m) => m?.publicKey.toString() === claim.account)

return {
...claim,
minter,
identity,
}
})
})
}

async getClaim(userId: string, claimId: string) {
const claims = await this.getClaims(userId)
const found = claims.find((claim) => claim.id === claimId)
if (!found) {
throw new Error('Claim not found')
}
return found
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Module } from '@nestjs/common'
import { ApiCoreDataAccessModule } from '@tokengator-mint/api-core-data-access'
import { ApiClaimService } from './api-claim.service'
import { ApiClaimDataService } from './api-claim-data.service'
import { ApiPresetDataAccessModule } from '@tokengator-mint/api-preset-data-access'
import { ApiClaimDataAdminService } from './api-claim-data-admin.service'
import { ApiClaimDataUserService } from './api-claim-data-user.service'
import { ApiClaimDataService } from './api-claim-data.service'
import { ApiClaimService } from './api-claim.service'

@Module({
imports: [ApiCoreDataAccessModule],
imports: [ApiCoreDataAccessModule, ApiPresetDataAccessModule],
providers: [ApiClaimService, ApiClaimDataService, ApiClaimDataAdminService, ApiClaimDataUserService],
exports: [ApiClaimService],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class ClaimAdminCreateInput {
@Field()
communityId!: string
@Field()
minter!: string
account!: string
@Field(() => IdentityProvider)
provider!: IdentityProvider
@Field()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class ClaimUserCreateInput {
@Field({ nullable: true })
amount?: string
@Field()
minter!: string
account!: string
@Field(() => IdentityProvider)
provider!: IdentityProvider
@Field()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class ClaimUserFindManyInput extends PagingInput() {
@Field({ nullable: true })
search?: string
@Field({ nullable: true })
minter?: string
account?: string
@Field(() => IdentityProvider, { nullable: true })
provider?: IdentityProvider
@Field({ nullable: true })
Expand Down
12 changes: 10 additions & 2 deletions libs/api/claim/data-access/src/lib/entity/claim.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Field, ObjectType } from '@nestjs/graphql'
import { Community } from '@tokengator-mint/api-community-data-access'
import { PagingResponse } from '@tokengator-mint/api-core-data-access'
import { IdentityProvider } from '@tokengator-mint/api-identity-data-access'
import { Identity, IdentityProvider } from '@tokengator-mint/api-identity-data-access'
import { TokenGatorMinter } from '@tokengator-mint/api-preset-data-access'
import { ClaimStatus } from './claim-status.enum'

@ObjectType()
Expand All @@ -16,7 +18,7 @@ export class Claim {
@Field()
communityId!: string
@Field()
minter!: string
account!: string
@Field({ nullable: true })
signature?: string | null
@Field(() => IdentityProvider)
Expand All @@ -25,6 +27,12 @@ export class Claim {
providerId!: string
@Field(() => ClaimStatus)
status!: ClaimStatus
@Field(() => Community, { nullable: true })
community?: Community
@Field(() => TokenGatorMinter, { nullable: true })
minter?: TokenGatorMinter
@Field(() => Identity, { nullable: true })
identity?: Identity
}

@ObjectType()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ClaimUserFindManyInput } from '../dto/claim-user-find-many.input'
export function getClaimWhereUserInput(input: ClaimUserFindManyInput): Prisma.ClaimWhereInput {
const where: Prisma.ClaimWhereInput = {
communityId: input.communityId,
minter: input.minter ?? undefined,
account: input.account ?? undefined,
provider: input.provider ?? undefined,
providerId: input.providerId ?? undefined,
status: input.status ?? undefined,
Expand Down
21 changes: 15 additions & 6 deletions libs/api/claim/feature/src/lib/api-claim-user.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Resolver } from '@nestjs/graphql'
import { ApiClaimService } from '@tokengator-mint/api-claim-data-access'
import { ApiAuthGraphQLUserGuard } from '@tokengator-mint/api-auth-data-access'
import { Mutation, Query, Args } from '@nestjs/graphql'
import { UseGuards } from '@nestjs/common'
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'
import { ApiAuthGraphQLUserGuard, CtxUserId } from '@tokengator-mint/api-auth-data-access'
import {
ClaimUserCreateInput,
ClaimUserFindManyInput,
ApiClaimService,
Claim,
ClaimPaging,
ClaimUserCreateInput,
ClaimUserFindManyInput,
ClaimUserUpdateInput,
} from '@tokengator-mint/api-claim-data-access'

Expand All @@ -26,6 +25,16 @@ export class ApiClaimUserResolver {
return this.service.user.deleteClaim(claimId)
}

@Query(() => Claim)
userGetClaim(@CtxUserId() userId: string, @Args('claimId') claimId: string) {
return this.service.user.userGetClaim(userId, claimId)
}

@Query(() => [Claim])
userGetClaims(@CtxUserId() userId: string) {
return this.service.user.userGetClaims(userId)
}

@Query(() => ClaimPaging)
userFindManyClaim(@Args('input') input: ClaimUserFindManyInput) {
return this.service.user.findManyClaim(input)
Expand Down
5 changes: 5 additions & 0 deletions libs/api/claim/feature/src/lib/api-claim.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export class ApiClaimResolver {

@ResolveField(() => String)
name(@Parent() claim: Claim) {
const communityName = claim.community?.name
const minterName = claim.minter?.name
if (communityName && minterName) {
return `${communityName} - ${minterName}`
}
return `${claim.provider}: ${ellipsify(claim.providerId)} `
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const provisionCommunities: ProvisionCommunityInput[] = [
{ provider: IdentityProvider.Solana, providerId: '81sWMLg1EgYps3nMwyeSW1JfjKgFqkGYPP85vTnkFzRn' },
{ provider: IdentityProvider.Solana, providerId: 'BEEMANPx2jdmfR7jpn1hRdMuM2Vj4E3azBLb6RUBrCDY' },
{ provider: IdentityProvider.Twitter, providerId: 'beeman_nl' },
].map((i) => ({ ...i, minter: '9u6HpBdFd1yZzQ8JszBoSRtcgbvJF5uk5pv7zcvrL3Se' })),
].map((i) => ({ ...i, account: '9u6HpBdFd1yZzQ8JszBoSRtcgbvJF5uk5pv7zcvrL3Se' })),
},
},
{
Expand Down
22 changes: 21 additions & 1 deletion libs/api/core/data-access/src/lib/api-core.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'
import { EventEmitter2 } from '@nestjs/event-emitter'
import { CommunityMemberRole, IdentityProvider } from '@prisma/client'
import { CommunityMemberRole, Identity, IdentityProvider } from '@prisma/client'
import { ApiCorePrismaClient, prismaClient } from './api-core-prisma-client'
import { ApiCoreConfigService } from './config/api-core-config.service'
import { slugifyId } from './helpers/slugify-id'
Expand Down Expand Up @@ -33,6 +33,26 @@ export class ApiCoreService {
return found
}

async getUserIdentityMap({ userId }: { userId: string }): Promise<Map<IdentityProvider, Identity[]>> {
const identities = await this.data.identity.findMany({
where: { ownerId: userId },
select: {
id: true,
profile: true,
provider: true,
providerId: true,
name: true,
},
})

return identities.reduce((acc, identity) => {
const { provider } = identity
const existing = acc.get(provider) || []
acc.set(provider, existing.length ? [...existing, identity] : [identity])
return acc
}, new Map())
}

async findUserByIdentity({ provider, providerId }: { provider: IdentityProvider; providerId: string }) {
return this.data.identity.findUnique({
where: { provider_providerId: { provider, providerId } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { IdentityProvider } from './identity-provider.enum'
export class Identity {
@Field()
id!: string
@Field()
createdAt!: Date
@Field()
updatedAt!: Date
@Field({ nullable: true })
createdAt?: Date
@Field({ nullable: true })
updatedAt?: Date

@Field(() => IdentityProvider)
provider!: IdentityProvider
Expand Down
2 changes: 2 additions & 0 deletions libs/api/preset/data-access/src/lib/api-preset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { Injectable } from '@nestjs/common'
import { ApiPresetDataAdminService } from './api-preset-data-admin.service'
import { ApiPresetDataUserService } from './api-preset-data-user.service'
import { ApiPresetDataService } from './api-preset-data.service'
import { ApiPresetMinterService } from './api-preset-minter.service'

@Injectable()
export class ApiPresetService {
constructor(
readonly data: ApiPresetDataService,
readonly admin: ApiPresetDataAdminService,
readonly user: ApiPresetDataUserService,
readonly minter: ApiPresetMinterService,
) {}
}
Loading

0 comments on commit b37c4e7

Please sign in to comment.