Implement functionality to release funds after deployment #7
@ -3,7 +3,9 @@ import {
|
|||||||
DeepPartial,
|
DeepPartial,
|
||||||
FindManyOptions,
|
FindManyOptions,
|
||||||
FindOneOptions,
|
FindOneOptions,
|
||||||
FindOptionsWhere
|
FindOptionsWhere,
|
||||||
|
IsNull,
|
||||||
|
Not
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
@ -151,14 +153,19 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async allProjectsWithoutDeployments(): Promise<Project[]> {
|
async allProjectsWithoutDeployments(): Promise<Project[]> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const allProjects = await this.getProjects({
|
||||||
|
where: {
|
||||||
|
auctionId: Not(IsNull()),
|
||||||
|
},
|
||||||
|
relations: ['deployments'],
|
||||||
|
withDeleted: true,
|
||||||
|
});
|
||||||
|
|
||||||
const projects = await projectRepository
|
const projects = allProjects.filter(project => {
|
||||||
.createQueryBuilder('project')
|
if (project.deletedAt !== null) return false;
|
||||||
.leftJoinAndSelect('project.deployments', 'deployment', 'deployment.deletedAt IS NULL') // Join only non-soft-deleted deployments
|
|
||||||
.where('deployment.id IS NULL') // Get projects where no deployments are present
|
return project.deployments.length === 0;
|
||||||
.andWhere('project.auctionId IS NOT NULL') // Ensure auctionId is not null
|
});
|
||||||
.getMany();
|
|
||||||
|
|
||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,9 @@ export class Project {
|
|||||||
@Column({ type: 'simple-array', nullable: true })
|
@Column({ type: 'simple-array', nullable: true })
|
||||||
deployerLrns!: string[] | null;
|
deployerLrns!: string[] | null;
|
||||||
|
|
||||||
|
@Column('boolean', { default: false, nullable: true })
|
||||||
|
fundsReleased!: boolean;
|
||||||
|
|
||||||
// TODO: Compute template & framework in import repository
|
// TODO: Compute template & framework in import repository
|
||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
template!: string | null;
|
template!: string | null;
|
||||||
|
@ -319,6 +319,21 @@ export class Registry {
|
|||||||
return deployerLrns;
|
return deployerLrns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async releaseDeployerFunds(
|
||||||
|
auctionId: string
|
||||||
|
): Promise<any> {
|
||||||
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
|
const auction = await this.registry.releaseFunds(
|
||||||
|
{
|
||||||
|
auctionId
|
||||||
|
},
|
||||||
|
this.registryConfig.privateKey,
|
||||||
|
fee
|
||||||
|
);
|
||||||
|
|
||||||
|
return auction;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch ApplicationDeploymentRecords for deployments
|
* Fetch ApplicationDeploymentRecords for deployments
|
||||||
*/
|
*/
|
||||||
@ -417,6 +432,10 @@ export class Registry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCompletedAuctionIds(auctionIds: string[]): Promise<string[]> {
|
async getCompletedAuctionIds(auctionIds: string[]): Promise<string[]> {
|
||||||
|
if (auctionIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const auctions = await this.registry.getAuctionsByIds(auctionIds);
|
const auctions = await this.registry.getAuctionsByIds(auctionIds);
|
||||||
|
|
||||||
const completedAuctions = auctions
|
const completedAuctions = auctions
|
||||||
|
@ -74,6 +74,7 @@ type Project {
|
|||||||
description: String
|
description: String
|
||||||
deployerLrns: [String]
|
deployerLrns: [String]
|
||||||
auctionId: String
|
auctionId: String
|
||||||
|
fundsReleased: Boolean
|
||||||
template: String
|
template: String
|
||||||
framework: String
|
framework: String
|
||||||
webhooks: [String!]
|
webhooks: [String!]
|
||||||
|
@ -173,6 +173,9 @@ export class Service {
|
|||||||
where: records.map((record) => ({
|
where: records.map((record) => ({
|
||||||
applicationDeploymentRequestId: record.attributes.request,
|
applicationDeploymentRequestId: record.attributes.request,
|
||||||
})),
|
})),
|
||||||
|
relations: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
order: {
|
order: {
|
||||||
createdAt: 'DESC',
|
createdAt: 'DESC',
|
||||||
},
|
},
|
||||||
@ -189,8 +192,6 @@ export class Service {
|
|||||||
// Update deployment data for ApplicationDeploymentRecords
|
// Update deployment data for ApplicationDeploymentRecords
|
||||||
const deploymentUpdatePromises = records.map(async (record) => {
|
const deploymentUpdatePromises = records.map(async (record) => {
|
||||||
const deployment = recordToDeploymentsMap[record.attributes.request];
|
const deployment = recordToDeploymentsMap[record.attributes.request];
|
||||||
const project = await this.getProjectById(deployment.projectId)
|
|
||||||
assert(project)
|
|
||||||
|
|
||||||
const parts = record.attributes.url.replace('https://', '').split('.');
|
const parts = record.attributes.url.replace('https://', '').split('.');
|
||||||
const baseDomain = parts.slice(1).join('.');
|
const baseDomain = parts.slice(1).join('.');
|
||||||
@ -204,15 +205,21 @@ export class Service {
|
|||||||
|
|
||||||
await this.db.updateDeploymentById(deployment.id, deployment);
|
await this.db.updateDeploymentById(deployment.id, deployment);
|
||||||
|
|
||||||
const baseDomains = project.baseDomains || [];
|
const baseDomains = deployment.project.baseDomains || [];
|
||||||
|
|
||||||
if (!baseDomains.includes(baseDomain)) {
|
if (!baseDomains.includes(baseDomain)) {
|
||||||
baseDomains.push(baseDomain);
|
baseDomains.push(baseDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.db.updateProjectById(project.id, {
|
// Release deployer funds on successful deployment
|
||||||
baseDomains
|
if (!deployment.project.fundsReleased) {
|
||||||
})
|
const fundsReleased = await this.releaseDeployerFundsByProjectId(deployment.projectId);
|
||||||
|
|
||||||
|
await this.db.updateProjectById(deployment.projectId, {
|
||||||
|
baseDomains,
|
||||||
|
fundsReleased,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
log(
|
log(
|
||||||
`Updated deployment ${deployment.id} with URL ${record.attributes.url}`,
|
`Updated deployment ${deployment.id} with URL ${record.attributes.url}`,
|
||||||
@ -292,7 +299,7 @@ export class Service {
|
|||||||
async checkAuctionStatus(): Promise<void> {
|
async checkAuctionStatus(): Promise<void> {
|
||||||
const projects = await this.db.allProjectsWithoutDeployments();
|
const projects = await this.db.allProjectsWithoutDeployments();
|
||||||
|
|
||||||
const validAuctionIds = projects.map((project) => project.auctionId!)
|
const validAuctionIds = projects.map((project) => project.auctionId)
|
||||||
.filter((id): id is string => Boolean(id));
|
.filter((id): id is string => Boolean(id));
|
||||||
const completedAuctionIds = await this.laconicRegistry.getCompletedAuctionIds(validAuctionIds);
|
const completedAuctionIds = await this.laconicRegistry.getCompletedAuctionIds(validAuctionIds);
|
||||||
|
|
||||||
@ -301,7 +308,7 @@ export class Service {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const project of projectsToBedeployed) {
|
for (const project of projectsToBedeployed) {
|
||||||
const deployerLrns = await this.laconicRegistry.getAuctionWinningDeployers(project!.auctionId!);
|
const deployerLrns = await this.laconicRegistry.getAuctionWinningDeployers(project.auctionId!);
|
||||||
|
|
||||||
if (!deployerLrns) {
|
if (!deployerLrns) {
|
||||||
log(`No winning deployer for auction ${project!.auctionId}`);
|
log(`No winning deployer for auction ${project!.auctionId}`);
|
||||||
@ -1261,4 +1268,27 @@ export class Service {
|
|||||||
const auctions = await this.laconicRegistry.getAuctionData(auctionId);
|
const auctions = await this.laconicRegistry.getAuctionData(auctionId);
|
||||||
return auctions[0];
|
return auctions[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async releaseDeployerFundsByProjectId(projectId: string): Promise<boolean> {
|
||||||
|
const project = await this.db.getProjectById(projectId);
|
||||||
|
|
||||||
|
if (!project || !project.auctionId) {
|
||||||
|
log(`Project ${projectId} ${!project ? 'not found' : 'does not have an auction'}`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auction = await this.laconicRegistry.releaseDeployerFunds(project.auctionId);
|
||||||
|
|
||||||
|
if (auction.auction.fundsReleased) {
|
||||||
|
log(`Funds released for auction ${project.auctionId}`);
|
||||||
|
await this.db.updateProjectById(projectId, { fundsReleased: true });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Error releasing funds for auction ${project.auctionId}`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ const WAIT_DURATION = 5000;
|
|||||||
export const AuctionCard = ({ project }: { project: Project }) => {
|
export const AuctionCard = ({ project }: { project: Project }) => {
|
||||||
const [auctionStatus, setAuctionStatus] = useState<string>('');
|
const [auctionStatus, setAuctionStatus] = useState<string>('');
|
||||||
const [deployerLrns, setDeployerLrns] = useState<string[]>([]);
|
const [deployerLrns, setDeployerLrns] = useState<string[]>([]);
|
||||||
|
const [fundsStatus, setFundsStatus] = useState<boolean>(false);
|
||||||
const [auctionDetails, setAuctionDetails] = useState<Auction | null>(null);
|
const [auctionDetails, setAuctionDetails] = useState<Auction | null>(null);
|
||||||
const [openDialog, setOpenDialog] = useState<boolean>(false);
|
const [openDialog, setOpenDialog] = useState<boolean>(false);
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
@ -29,18 +30,16 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
setAuctionStatus(result.status);
|
setAuctionStatus(result.status);
|
||||||
setAuctionDetails(result);
|
setAuctionDetails(result);
|
||||||
setDeployerLrns(project.deployerLrns);
|
setDeployerLrns(project.deployerLrns);
|
||||||
}, [client, project.auctionId, project.deployerLrns]);
|
setFundsStatus(project.fundsReleased);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (auctionStatus !== 'completed') {
|
if (auctionStatus !== 'completed') {
|
||||||
checkAuctionStatus();
|
checkAuctionStatus();
|
||||||
const intervalId = setInterval(checkAuctionStatus, WAIT_DURATION);
|
const intervalId = setInterval(checkAuctionStatus, WAIT_DURATION);
|
||||||
|
|
||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}
|
}
|
||||||
}, [auctionStatus, checkAuctionStatus]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (auctionStatus === 'completed') {
|
if (auctionStatus === 'completed') {
|
||||||
const fetchUpdatedProject = async () => {
|
const fetchUpdatedProject = async () => {
|
||||||
// Wait for 5 secs since the project is not immediately updated with deployer LRNs
|
// Wait for 5 secs since the project is not immediately updated with deployer LRNs
|
||||||
@ -48,10 +47,9 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
const updatedProject = await client.getProject(project.id);
|
const updatedProject = await client.getProject(project.id);
|
||||||
setDeployerLrns(updatedProject.project?.deployerLrns || []);
|
setDeployerLrns(updatedProject.project?.deployerLrns || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchUpdatedProject();
|
fetchUpdatedProject();
|
||||||
}
|
}
|
||||||
}, [auctionStatus, client, project.id]);
|
}, [auctionStatus, client]);
|
||||||
|
|
||||||
const renderAuctionStatus = useCallback(
|
const renderAuctionStatus = useCallback(
|
||||||
() => (
|
() => (
|
||||||
@ -101,6 +99,18 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mt-1">
|
||||||
|
<span className="text-elements-high-em text-sm font-medium tracking-tight">Deployer Funds Status</span>
|
||||||
|
<div className="ml-2">
|
||||||
|
<Tag
|
||||||
|
size="xs"
|
||||||
|
type={fundsStatus ? 'positive' : 'emphasized'}
|
||||||
|
>
|
||||||
|
{fundsStatus ? 'RELEASED' : 'LOCKED'}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="md">
|
<Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="md">
|
||||||
|
@ -23,12 +23,8 @@ const Id = () => {
|
|||||||
|
|
||||||
const handleSetupDomain = async () => {
|
const handleSetupDomain = async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
// console.log('id', id);
|
|
||||||
// console.log('getting project for id', id);
|
|
||||||
const project = await client.getProject(id);
|
const project = await client.getProject(id);
|
||||||
// console.log('project found:', project);
|
|
||||||
if (project && project.project) {
|
if (project && project.project) {
|
||||||
// console.log('project:', project.project);
|
|
||||||
setProject(project.project);
|
setProject(project.project);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -38,7 +34,7 @@ const Id = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleSetupDomain();
|
handleSetupDomain();
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -124,5 +124,6 @@ export const project: Project = {
|
|||||||
deployerLrns: ['lrn://deployer.apps.snowballtools.com '],
|
deployerLrns: ['lrn://deployer.apps.snowballtools.com '],
|
||||||
webhooks: ['beepboop'],
|
webhooks: ['beepboop'],
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
|
fundsReleased: true,
|
||||||
baseDomains: ['baseDomain'],
|
baseDomains: ['baseDomain'],
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,7 @@ query ($projectId: String!) {
|
|||||||
prodBranch
|
prodBranch
|
||||||
auctionId
|
auctionId
|
||||||
deployerLrns
|
deployerLrns
|
||||||
|
fundsReleased
|
||||||
framework
|
framework
|
||||||
repository
|
repository
|
||||||
webhooks
|
webhooks
|
||||||
@ -76,6 +77,7 @@ query ($organizationSlug: String!) {
|
|||||||
framework
|
framework
|
||||||
auctionId
|
auctionId
|
||||||
deployerLrns
|
deployerLrns
|
||||||
|
fundsReleased
|
||||||
prodBranch
|
prodBranch
|
||||||
webhooks
|
webhooks
|
||||||
repository
|
repository
|
||||||
@ -193,6 +195,7 @@ query ($searchText: String!) {
|
|||||||
framework
|
framework
|
||||||
auctionId
|
auctionId
|
||||||
deployerLrns
|
deployerLrns
|
||||||
|
fundsReleased
|
||||||
prodBranch
|
prodBranch
|
||||||
webhooks
|
webhooks
|
||||||
updatedAt
|
updatedAt
|
||||||
|
@ -171,6 +171,7 @@ export type Project = {
|
|||||||
framework: string;
|
framework: string;
|
||||||
deployerLrns: string[];
|
deployerLrns: string[];
|
||||||
auctionId: string;
|
auctionId: string;
|
||||||
|
fundsReleased: boolean;
|
||||||
webhooks: string[];
|
webhooks: string[];
|
||||||
members: ProjectMember[];
|
members: ProjectMember[];
|
||||||
environmentVariables: EnvironmentVariable[];
|
environmentVariables: EnvironmentVariable[];
|
||||||
|
Loading…
Reference in New Issue
Block a user