diff --git a/api/src/db/deploymentProvider.ts b/api/src/db/deploymentProvider.ts index 19e2f7884..7fdb7f8bc 100644 --- a/api/src/db/deploymentProvider.ts +++ b/api/src/db/deploymentProvider.ts @@ -2,7 +2,7 @@ import * as v1 from "@src/proto/akash/v1beta1"; import * as v2 from "@src/proto/akash/v1beta2"; import { decodeMsg } from "@src/utils/protobuf"; import { Transaction } from "@shared/dbSchemas/base"; -import { Deployment } from "@shared/dbSchemas/akash"; +import { Deployment, Lease } from "@shared/dbSchemas/akash"; import { Op } from "sequelize"; import { Block, Message } from "@shared/dbSchemas"; @@ -69,3 +69,77 @@ export async function getDeploymentRelatedMessages(owner: string, dseq: string) type: msg.type })); } + +export async function getProviderDeployments(provider: string, skip: number, limit: number, status?: "active" | "closed") { + let leaseFilter = { providerAddress: provider }; + + if (status) { + leaseFilter["closedHeight"] = status === "active" ? null : { [Op.ne]: null }; + } + + const deploymentDseqs = await Deployment.findAll({ + attributes: ["dseq", "createdHeight"], + include: [{ model: Lease, attributes: [], required: true, where: leaseFilter }], + order: [["createdHeight", "DESC"]], + offset: skip, + limit: limit + }); + + const deployments = await Deployment.findAll({ + where: { + dseq: { [Op.in]: deploymentDseqs.map((d) => d.dseq) } + }, + include: [ + { + model: Lease, + required: true, + where: { providerAddress: provider }, + include: [ + { model: Block, required: true, as: "createdBlock" }, + { model: Block, required: false, as: "closedBlock" } + ] + }, + { model: Block, required: true, as: "createdBlock" }, + { model: Block, required: false, as: "closedBlock" } + ] + }); + + return deployments.map((d) => ({ + owner: d.owner, + dseq: d.dseq, + denom: d.denom, + createdHeight: d.createdHeight, + createdDate: d.createdBlock.datetime, + closedHeight: d.closedHeight, + closedDate: d.closedHeight ? d.closedBlock.datetime : null, + status: d.closedHeight ? "closed" : "active", + balance: d.balance, + transferred: d.withdrawnAmount, + settledAt: d.lastWithdrawHeight, + resources: { + cpu: d.leases.reduce((acc, l) => acc + l.cpuUnits, 0), + memory: d.leases.reduce((acc, l) => acc + l.memoryQuantity, 0), + gpu: d.leases.reduce((acc, l) => acc + l.gpuUnits, 0), + ephemeralStorage: d.leases.reduce((acc, l) => acc + l.ephemeralStorageQuantity, 0), + persistentStorage: d.leases.reduce((acc, l) => acc + l.persistentStorageQuantity, 0) + }, + leases: d.leases.map((l) => ({ + provider: l.providerAddress, + gseq: l.gseq, + oseq: l.oseq, + price: l.price, + createdHeight: l.createdHeight, + createdDate: l.createdBlock.datetime, + closedHeight: l.closedHeight, + closedDate: l.closedHeight ? l.closedBlock.datetime : null, + status: l.closedHeight ? "closed" : "active", + resources: { + cpu: l.cpuUnits, + memory: l.memoryQuantity, + gpu: l.gpuUnits, + ephemeralStorage: l.ephemeralStorageQuantity, + persistentStorage: l.persistentStorageQuantity + } + })) + })); +} diff --git a/api/src/routers/apiRouter.ts b/api/src/routers/apiRouter.ts index 2fe579dc1..8117ce5f3 100644 --- a/api/src/routers/apiRouter.ts +++ b/api/src/routers/apiRouter.ts @@ -24,6 +24,7 @@ import axios from "axios"; import { getMarketData } from "@src/providers/marketDataProvider"; import { getAuditors, getProviderAttributesSchema } from "@src/providers/githubProvider"; import { getProviderRegions } from "@src/db/providerDataProvider"; +import { getProviderDeployments } from "@src/db/deploymentProvider"; export const apiRouter = express.Router(); @@ -192,6 +193,24 @@ apiRouter.get( }) ); +apiRouter.get( + "/providers/:provider/deployments/:skip/:limit/:status?", + asyncHandler(async (req, res) => { + const skip = parseInt(req.params.skip); + const limit = Math.min(100, parseInt(req.params.limit)); + const statusParam = req.params.status as "active" | "closed" | undefined; + + if (statusParam && statusParam !== "active" && statusParam !== "closed") { + res.status(400).send(`Invalid status filter: "${statusParam}". Valid values are "active" and "closed".`); + return; + } + + const deployments = await getProviderDeployments(req.params.provider, skip, limit, statusParam); + + res.send(deployments); + }) +); + apiRouter.get( "/validators", asyncHandler(async (req, res) => { diff --git a/shared/dbSchemas/akash/deployment.ts b/shared/dbSchemas/akash/deployment.ts index a62d71161..89795c0b7 100644 --- a/shared/dbSchemas/akash/deployment.ts +++ b/shared/dbSchemas/akash/deployment.ts @@ -1,8 +1,8 @@ -import { Column, Default, HasMany, Model, PrimaryKey, Table } from "sequelize-typescript"; +import { BelongsTo, Column, Default, HasMany, Model, PrimaryKey, Table } from "sequelize-typescript"; import { DataTypes, UUIDV4 } from "sequelize"; import { DeploymentGroup } from "./deploymentGroup"; import { Lease } from "./lease"; -import { Message } from "../base/message"; +import { Message, Block } from "../base"; import { Required } from "../decorators/requiredDecorator"; @Table({ modelName: "deployment" }) @@ -18,6 +18,8 @@ export class Deployment extends Model { @Required @Column(DataTypes.DOUBLE) withdrawnAmount!: number; @Column closedHeight?: number; + @BelongsTo(() => Block, "createdHeight") createdBlock: Block; + @BelongsTo(() => Block, "closedHeight") closedBlock: Block; @HasMany(() => DeploymentGroup, "deploymentId") deploymentGroups: DeploymentGroup[]; @HasMany(() => Lease, "deploymentId") leases: Lease[]; @HasMany(() => Message, { foreignKey: "relatedDeploymentId", constraints: false }) relatedMessages: Message[]; diff --git a/shared/dbSchemas/akash/lease.ts b/shared/dbSchemas/akash/lease.ts index 2a00c4a21..b5c06887d 100644 --- a/shared/dbSchemas/akash/lease.ts +++ b/shared/dbSchemas/akash/lease.ts @@ -4,6 +4,7 @@ import { DeploymentGroup } from "./deploymentGroup"; import { Deployment } from "./deployment"; import { Provider } from "./provider"; import { Required } from "../decorators/requiredDecorator"; +import { Block } from "../base"; @Table({ modelName: "lease", @@ -37,6 +38,8 @@ export class Lease extends Model { @Required @Column(DataTypes.BIGINT) ephemeralStorageQuantity: number; @Required @Column(DataTypes.BIGINT) persistentStorageQuantity: number; + @BelongsTo(() => Block, "createdHeight") createdBlock: Block; + @BelongsTo(() => Block, "closedHeight") closedBlock: Block; @BelongsTo(() => DeploymentGroup, "deploymentGroupId") deploymentGroup: DeploymentGroup; @BelongsTo(() => Deployment, "deploymentId") deployment: Deployment; @BelongsTo(() => Provider, "providerAddress") provider: Provider;