From 11956be8c25d1474de8522c8fc6d5cc56a69a3aa Mon Sep 17 00:00:00 2001 From: Akintola Olamilekan Date: Sat, 18 Jan 2025 21:20:49 +0100 Subject: [PATCH 1/2] list an item --- modules/market/market.models.ts | 14 +++++- modules/market/market.router.ts | 39 +++++++++++++++- modules/market/market.schema.ts | 14 ++++++ modules/market/market.service.ts | 76 ++++++++++++++++++++++++++++++++ modules/market/market.types.ts | 3 ++ 5 files changed, 144 insertions(+), 2 deletions(-) diff --git a/modules/market/market.models.ts b/modules/market/market.models.ts index c86378f..535ee8a 100644 --- a/modules/market/market.models.ts +++ b/modules/market/market.models.ts @@ -1,5 +1,6 @@ +import { optional } from 'zod'; import * as mongo from '../../util/mongo'; -import type * as Types from './market.types'; +import * as Types from './market.types'; export const Market = mongo.createModel('Market', { value: { type: String, required: true }, @@ -111,3 +112,14 @@ export const MarketStockSentiment = mongo.createModel('MarketListing', { + sellerId: { type: mongo.Schema.Types.ObjectId, required: true, ref: 'MarketInvestor' }, + quantityAvailable: { type: Number, required: true }, + currency: { type: String, required: true }, + exchange: { type: String, required: true }, + marketId: { type: mongo.Schema.Types.ObjectId, required: true, ref: 'MarketStock' }, + category: { type: String, required: true }, + status: { type: String, required: true }, + expiryDate: { type: Date, required: false }, +}); diff --git a/modules/market/market.router.ts b/modules/market/market.router.ts index 94d1db8..cf86f45 100644 --- a/modules/market/market.router.ts +++ b/modules/market/market.router.ts @@ -4,7 +4,7 @@ import { z as zod } from 'zod'; import { initTRPC, inferRouterInputs, inferRouterOutputs } from '@trpc/server'; import { customErrorFormatter, hasRole } from '../../util/rpc'; import type { RouterContext } from '../../types'; -import { Market, MarketPair, MarketExchange } from './market.schema'; +import { Market, MarketPair, MarketExchange, MarketListing } from './market.schema'; export const z = zod; export const t = initTRPC.context().create(); @@ -66,6 +66,43 @@ export const createRouter = () => .use(customErrorFormatter(t)) .input(z.object({ exchangeId: z.string(), data: MarketExchange.partial() })) .mutation(({ input, ctx }) => (ctx.app.service.Market.updateMarketExchange as any)(input, ctx)), + + getMarketListing: procedure + .use(hasRole('guest', t)) + .use(customErrorFormatter(t)) + .input(z.object({ listingId: z.string() })) + .query(({ input, ctx }) => (ctx.app.service.Market.getMarketListing as any)(input, ctx)), + + createMarketListing: procedure + .use(hasRole('admin', t)) + .use(customErrorFormatter(t)) + .input(MarketListing) + .mutation(({ input, ctx }) => (ctx.app.service.Market.createMarketListing as any)(input, ctx)), + + updateMarketListing: procedure + .use(hasRole('admin', t)) + .use(customErrorFormatter(t)) + .input(z.object({ listingId: z.string(), data: MarketListing.partial() })) + .mutation(({ input, ctx }) => (ctx.app.service.Market.updateMarketListing as any)(input, ctx)), + + deleteMarketListing: procedure + .use(hasRole('admin', t)) + .use(customErrorFormatter(t)) + .input(z.object({ listingId: z.string() })) + .mutation(({ input, ctx }) => (ctx.app.service.Market.deleteMarketListing as any)(input, ctx)), + + getMarketListings: procedure + .use(hasRole('guest', t)) + .use(customErrorFormatter(t)) + .input( + z.object({ + category: z.string().optional(), + status: z.string().optional(), + exchange: z.string().optional(), + sellerId: z.string().optional(), + }) + ) + .query(({ input, ctx }) => (ctx.app.service.Market.getMarketListings as any)(input, ctx)), }); export type Router = ReturnType; diff --git a/modules/market/market.schema.ts b/modules/market/market.schema.ts index c506cfa..ecd9356 100644 --- a/modules/market/market.schema.ts +++ b/modules/market/market.schema.ts @@ -138,3 +138,17 @@ export const MarketStockSentiment = Entity.merge( confidence: z.number().min(0).max(1), // overall confidence level for the analysis }) ); + +export const MarketListing = Entity.merge( + z.object({ + price: z.number(), + currency: z.string(), + quantityAvailable: z.number(), + exchange: z.string(), + sellerId: ObjectId, + marketId: ObjectId, + category: z.enum(['Stock', 'ChainToken']), + status: z.enum(['active', 'closed', 'withdrawn', 'expired']), + expiryDate: z.date().optional(), + }) +); diff --git a/modules/market/market.service.ts b/modules/market/market.service.ts index 4f60662..9875e87 100644 --- a/modules/market/market.service.ts +++ b/modules/market/market.service.ts @@ -4,6 +4,7 @@ import type { Market, MarketPair, MarketExchange, + MarketListing, RouterContext, Router, RouterInput, @@ -119,4 +120,79 @@ export class Service { return updatedMarketExchange as MarketExchange; } + + async getMarketListing( + input: RouterInput['getMarketListing'], + ctx: RouterContext + ): Promise { + if (!input) throw new Error('Input should not be void'); + console.log('MarketListingService.getMarketListing', input.listingId); + + const listing = await ctx.app.model.MarketListing.findById(input.listingId).lean().exec(); + if (!listing) throw new Error('MarketListing not found'); + + return listing as MarketListing; + } + + async createMarketListing( + input: RouterInput['createMarketListing'], + ctx: RouterContext + ): Promise { + if (!input) throw new Error('Input should not be void'); + console.log('Market.Service.createMarketListing', input); + + const marketListing = await ctx.app.model.MarketListing.create(input); + return marketListing as MarketListing; + } + + async updateMarketListing( + input: RouterInput['updateMarketListing'], + ctx: RouterContext + ): Promise { + if (!input) throw new Error('Input should not be void'); + console.log('MarketListingService.updateMarketListing', input.listingId, input.data); + + const updatedListing = await ctx.app.model.MarketListing.findByIdAndUpdate(input.listingId, input.data, { + new: true, + }) + .lean() + .exec(); + if (!updatedListing) throw new Error('MarketListing update failed'); + + return updatedListing as MarketListing; + } + + async deleteMarketListing( + input: RouterInput['deleteMarketListing'], + ctx: RouterContext + ): Promise { + if (!input) throw new Error('Input should not be void'); + console.log('MarketListingService.deleteMarketListing', input.listingId); + + const deletedListing = await ctx.app.model.MarketListing.findByIdAndDelete(input.listingId).lean().exec(); + if (!deletedListing) throw new Error('MarketListing deletion failed'); + + return deletedListing as MarketListing; + } + + async getMarketListings( + input: { + category?: string; + status?: string; + exchange?: string; + sellerId?: string; + }, + ctx: RouterContext + ): Promise { + console.log('MarketListingService.getMarketListings', input); + + const query: Record = {}; + if (input.category) query.category = input.category; + if (input.status) query.status = input.status; + if (input.exchange) query.exchange = input.exchange; + if (input.sellerId) query.sellerId = input.sellerId; + + const listings = await ctx.app.model.MarketListing.find(query).lean().exec(); + return listings as MarketListing[]; + } } diff --git a/modules/market/market.types.ts b/modules/market/market.types.ts index d79c5dd..b11ca73 100644 --- a/modules/market/market.types.ts +++ b/modules/market/market.types.ts @@ -23,6 +23,7 @@ export type MarketToken = z.infer; export type MarketCompany = z.infer; export type MarketETF = z.infer; export type MarketStockSentiment = z.infer; +export type MarketListing = z.infer; export type MarketDocument = Market & Document; export type MarketPairDocument = MarketPair & Document; @@ -36,6 +37,7 @@ export type MarketTokenDocument = MarketToken & Document; export type MarketCompanyDocument = MarketCompany & Document; export type MarketETFDocument = MarketETF & Document; export type MarketStockSentimentDocument = MarketStockSentiment & Document; +export type MarketListingDocument = MarketListing & Document; export type Mappings = { Market: Model; @@ -50,6 +52,7 @@ export type Mappings = { MarketCompany: Model; MarketETF: Model; MarketStockSentiment: Model; + MarketListing: Model; }; export type RouterInput = inferRouterInputs; From ec92881e6112cd73334d07f384efa2d370bc5eb9 Mon Sep 17 00:00:00 2001 From: Akintola Olamilekan Date: Mon, 20 Jan 2025 14:43:30 +0100 Subject: [PATCH 2/2] updated market listing --- modules/market/market.models.ts | 4 ++-- modules/market/market.router.ts | 31 ++++++++++++++++--------------- modules/market/market.schema.ts | 4 ++-- modules/market/market.service.ts | 24 +++++++++++++----------- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/modules/market/market.models.ts b/modules/market/market.models.ts index 535ee8a..143db19 100644 --- a/modules/market/market.models.ts +++ b/modules/market/market.models.ts @@ -114,8 +114,8 @@ export const MarketStockSentiment = mongo.createModel('MarketListing', { - sellerId: { type: mongo.Schema.Types.ObjectId, required: true, ref: 'MarketInvestor' }, - quantityAvailable: { type: Number, required: true }, + sellerId: { type: mongo.Schema.Types.ObjectId, required: true, ref: 'Profile' }, + quantity: { type: Number, required: true }, currency: { type: String, required: true }, exchange: { type: String, required: true }, marketId: { type: mongo.Schema.Types.ObjectId, required: true, ref: 'MarketStock' }, diff --git a/modules/market/market.router.ts b/modules/market/market.router.ts index cf86f45..3e0cca9 100644 --- a/modules/market/market.router.ts +++ b/modules/market/market.router.ts @@ -5,12 +5,20 @@ import { initTRPC, inferRouterInputs, inferRouterOutputs } from '@trpc/server'; import { customErrorFormatter, hasRole } from '../../util/rpc'; import type { RouterContext } from '../../types'; import { Market, MarketPair, MarketExchange, MarketListing } from './market.schema'; +import { Query, getQueryInput, getQueryOutput } from '../../schema'; export const z = zod; export const t = initTRPC.context().create(); export const router = t.router; export const procedure = t.procedure; +const MarketListingsInput = z.object({ + category: z.string().optional(), + status: z.string().optional(), + exchange: z.string().optional(), + sellerId: z.string().optional(), +}); + export const createRouter = () => router({ getMarket: procedure @@ -70,38 +78,31 @@ export const createRouter = () => getMarketListing: procedure .use(hasRole('guest', t)) .use(customErrorFormatter(t)) - .input(z.object({ listingId: z.string() })) + .input(getQueryInput(z.object({ listingId: z.string() }))) .query(({ input, ctx }) => (ctx.app.service.Market.getMarketListing as any)(input, ctx)), createMarketListing: procedure - .use(hasRole('admin', t)) + .use(hasRole('user', t)) .use(customErrorFormatter(t)) - .input(MarketListing) + .input(getQueryInput(MarketListing)) .mutation(({ input, ctx }) => (ctx.app.service.Market.createMarketListing as any)(input, ctx)), updateMarketListing: procedure - .use(hasRole('admin', t)) + .use(hasRole('user', t)) .use(customErrorFormatter(t)) - .input(z.object({ listingId: z.string(), data: MarketListing.partial() })) + .input(getQueryInput(MarketListing.partial())) .mutation(({ input, ctx }) => (ctx.app.service.Market.updateMarketListing as any)(input, ctx)), deleteMarketListing: procedure - .use(hasRole('admin', t)) + .use(hasRole('user', t)) .use(customErrorFormatter(t)) - .input(z.object({ listingId: z.string() })) + .input(getQueryInput(z.object({ listingId: z.string() }))) .mutation(({ input, ctx }) => (ctx.app.service.Market.deleteMarketListing as any)(input, ctx)), getMarketListings: procedure .use(hasRole('guest', t)) .use(customErrorFormatter(t)) - .input( - z.object({ - category: z.string().optional(), - status: z.string().optional(), - exchange: z.string().optional(), - sellerId: z.string().optional(), - }) - ) + .input(getQueryInput(MarketListingsInput)) .query(({ input, ctx }) => (ctx.app.service.Market.getMarketListings as any)(input, ctx)), }); diff --git a/modules/market/market.schema.ts b/modules/market/market.schema.ts index ecd9356..d5e014e 100644 --- a/modules/market/market.schema.ts +++ b/modules/market/market.schema.ts @@ -143,12 +143,12 @@ export const MarketListing = Entity.merge( z.object({ price: z.number(), currency: z.string(), - quantityAvailable: z.number(), + quantity: z.number(), exchange: z.string(), sellerId: ObjectId, marketId: ObjectId, category: z.enum(['Stock', 'ChainToken']), - status: z.enum(['active', 'closed', 'withdrawn', 'expired']), + status: z.enum(['Active', 'Closed', 'Withdrawn', 'Expired']), expiryDate: z.date().optional(), }) ); diff --git a/modules/market/market.service.ts b/modules/market/market.service.ts index 9875e87..ddbd672 100644 --- a/modules/market/market.service.ts +++ b/modules/market/market.service.ts @@ -10,6 +10,8 @@ import type { RouterInput, RouterOutput, } from './market.types'; +import { getFilter } from '../../util/api'; +import { ARXError } from '../../util/rpc'; export class Service { async getMarket(input: RouterInput['getMarket'], ctx: RouterContext): Promise { @@ -125,10 +127,10 @@ export class Service { input: RouterInput['getMarketListing'], ctx: RouterContext ): Promise { - if (!input) throw new Error('Input should not be void'); - console.log('MarketListingService.getMarketListing', input.listingId); + if (!input) throw new ARXError('NO_INPUT'); + console.log('Market.Service.getMarketListing', input); - const listing = await ctx.app.model.MarketListing.findById(input.listingId).lean().exec(); + const listing = await ctx.app.model.MarketListing.findOne(getFilter(input)).exec(); if (!listing) throw new Error('MarketListing not found'); return listing as MarketListing; @@ -138,7 +140,7 @@ export class Service { input: RouterInput['createMarketListing'], ctx: RouterContext ): Promise { - if (!input) throw new Error('Input should not be void'); + if (!input) throw new ARXError('NO_INPUT'); console.log('Market.Service.createMarketListing', input); const marketListing = await ctx.app.model.MarketListing.create(input); @@ -149,10 +151,10 @@ export class Service { input: RouterInput['updateMarketListing'], ctx: RouterContext ): Promise { - if (!input) throw new Error('Input should not be void'); - console.log('MarketListingService.updateMarketListing', input.listingId, input.data); + if (!input) throw new ARXError('NO_INPUT'); + console.log('Market.Service.updateMarketListing', input); - const updatedListing = await ctx.app.model.MarketListing.findByIdAndUpdate(input.listingId, input.data, { + const updatedListing = await ctx.app.model.MarketListing.findByIdAndUpdate(input.where.id.equals, input.data, { new: true, }) .lean() @@ -166,10 +168,10 @@ export class Service { input: RouterInput['deleteMarketListing'], ctx: RouterContext ): Promise { - if (!input) throw new Error('Input should not be void'); - console.log('MarketListingService.deleteMarketListing', input.listingId); + if (!input) throw new ARXError('NO_INPUT'); + console.log('Market.Service.deleteMarketListing', input); - const deletedListing = await ctx.app.model.MarketListing.findByIdAndDelete(input.listingId).lean().exec(); + const deletedListing = await ctx.app.model.MarketListing.findByIdAndDelete(input.where.id.quals).lean().exec(); if (!deletedListing) throw new Error('MarketListing deletion failed'); return deletedListing as MarketListing; @@ -184,7 +186,7 @@ export class Service { }, ctx: RouterContext ): Promise { - console.log('MarketListingService.getMarketListings', input); + console.log('Market.Service.getMarketListings', input); const query: Record = {}; if (input.category) query.category = input.category;