Skip to content

Commit

Permalink
chore: Projects migration (#1826)
Browse files Browse the repository at this point in the history
* chore: rename updates table

* chore: add project id column to updates table

* chore: projects migration

* requested changes
  • Loading branch information
ncomerci committed Jun 5, 2024
1 parent 78b89b0 commit fad9378
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 31 deletions.
10 changes: 10 additions & 0 deletions src/back/jobs/ProjectsMigration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ProjectService } from '../../services/ProjectService'

export async function migrateProjects() {
const result = await ProjectService.migrateProjects()
if (result.error || result.migrationErrors.length > 0) {
throw JSON.stringify(result)
}

return result
}
19 changes: 18 additions & 1 deletion src/back/models/Project.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import crypto from 'crypto'
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'
import { SQL, table } from 'decentraland-gatsby/dist/entities/Database/utils'
import { SQL, join, table } from 'decentraland-gatsby/dist/entities/Database/utils'
import isEthereumAddress from 'validator/lib/isEthereumAddress'
import isUUID from 'validator/lib/isUUID'

import CoauthorModel from '../../entities/Coauthor/model'
import { CoauthorStatus } from '../../entities/Coauthor/types'
import { ProjectStatus } from '../../entities/Grant/types'
import ProposalModel from '../../entities/Proposal/model'
import { ProposalProject } from '../../entities/Proposal/types'

import PersonnelModel, { PersonnelAttributes } from './Personnel'
import ProjectLinkModel, { ProjectLink } from './ProjectLink'
Expand Down Expand Up @@ -70,6 +72,21 @@ export default class ProjectModel extends Model<ProjectAttributes> {
return result[0]
}

static async migrate(proposals: ProposalProject[]) {
const values = proposals.map(
({ id, title, about, status, created_at }) =>
SQL`(${crypto.randomUUID()}, ${id}, ${title}, ${about}, ${status}, ${new Date(created_at)})`
)

const query = SQL`
INSERT INTO ${table(ProjectModel)} (id, proposal_id, title, about, status, created_at)
VALUES ${join(values, SQL`, `)}
RETURNING *;
`

return this.namedQuery<ProjectAttributes>(`create_multiple_projects`, query)
}

static async isAuthorOrCoauthor(user: string, projectId: string): Promise<boolean> {
if (!isUUID(projectId || '')) {
throw new Error(`Invalid project id: "${projectId}"`)
Expand Down
2 changes: 2 additions & 0 deletions src/back/routes/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { DEBUG_ADDRESSES } from '../../constants'
import CacheService from '../../services/CacheService'
import { ErrorService } from '../../services/ErrorService'
import { giveAndRevokeLandOwnerBadges, giveTopVoterBadges, runQueuedAirdropJobs } from '../jobs/BadgeAirdrop'
import { migrateProjects } from '../jobs/ProjectsMigration'
import { validateDebugAddress } from '../utils/validations'

const FUNCTIONS_MAP: { [key: string]: () => Promise<unknown> } = {
runQueuedAirdropJobs,
giveAndRevokeLandOwnerBadges,
giveTopVoterBadges,
migrateProjects,
}

export default routes((router) => {
Expand Down
22 changes: 13 additions & 9 deletions src/entities/Proposal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,34 +810,38 @@ export type ProposalCommentsInDiscourse = {
}

export type VestingContractData = {
vestedAmount: number
vested_amount: number
releasable: number
released: number
start_at: number
finish_at: number
vesting_total_amount: number
}

export type ProjectVestingData = {
contract?: VestingContractData
enacting_tx?: string
token?: string
enacted_at?: number
tx_amount?: number
tx_date?: number
}

export type ProposalProject = {
id: string
project_id?: string | null
status: ProjectStatus
title: string
user: string
size: number
type: ProposalType
about: string
created_at: number
configuration: {
category: ProposalGrantCategory
tier: string
}
status?: ProjectStatus
contract?: VestingContractData
enacting_tx?: string
token?: string
enacted_at?: number
tx_amount?: number
tx_date?: number
}
} & ProjectVestingData

export type ProposalProjectWithUpdate = ProposalProject & {
update?: IndexedUpdate | null
Expand Down
13 changes: 13 additions & 0 deletions src/entities/Updates/model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import crypto from 'crypto'
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'
import { SQL, table } from 'decentraland-gatsby/dist/entities/Database/utils'

import ProjectModel from '../../back/models/Project'
import { type VestingInfo } from '../../clients/VestingData'
import Time from '../../utils/date/Time'
import { getMonthsBetweenDates } from '../../utils/date/getMonthsBetweenDates'
Expand Down Expand Up @@ -63,4 +65,15 @@ export default class UpdateModel extends Model<UpdateAttributes> {
...update,
})
}

static async setProjectIds() {
const query = SQL`
UPDATE ${table(this)} pu
SET project_id = p.id
FROM ${table(ProjectModel)} p
WHERE pu.proposal_id = p.proposal_id
`

return await this.namedQuery('set_project_ids', query)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { MigrationBuilder } from "node-pg-migrate"
import ProjectModel from "../back/models/Project"

const constraintName = 'unique_proposal_id'

export async function up(pgm: MigrationBuilder): Promise<void> {
pgm.addConstraint(ProjectModel.tableName, constraintName, {
unique: ['proposal_id'],
})
}

export async function down(pgm: MigrationBuilder): Promise<void> {
pgm.dropConstraint(ProjectModel.tableName, constraintName)
}
35 changes: 32 additions & 3 deletions src/services/ProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ProposalModel from '../entities/Proposal/model'
import { ProposalWithOutcome } from '../entities/Proposal/outcome'
import {
GrantProposalConfiguration,
ProposalAttributes,
ProposalProjectWithUpdate,
ProposalStatus,
ProposalType,
Expand All @@ -30,7 +31,7 @@ import logger from '../utils/logger'
import { createProposalProject } from '../utils/projects'

import { BudgetService } from './BudgetService'
import { ProposalInCreation } from './ProposalService'
import { ProposalInCreation, ProposalService } from './ProposalService'
import { VestingService } from './VestingService'

function newestVestingFirst(a: TransparencyVesting, b: TransparencyVesting): number {
Expand Down Expand Up @@ -167,6 +168,34 @@ export class ProjectService {
})
}

static async migrateProjects() {
const migrationResult = { migratedProjects: 0, migrationsFinished: 0, migrationErrors: [] as string[], error: '' }
try {
const { data: projects } = await this.getProjects()
const migratedProjects = await ProjectModel.migrate(projects)
migrationResult.migratedProjects = migratedProjects.length

for (const project of migratedProjects) {
try {
const proposal = await ProposalService.getProposal(project.proposal_id)
await ProjectService.createMilestones(proposal, project, new Date(project.created_at))
await ProjectService.createPersonnel(proposal, project, new Date(project.created_at))
migrationResult.migrationsFinished++
} catch (e) {
migrationResult.migrationErrors.push(`Project ${project.id} failed with: ${e}`)
}
}

await UpdateModel.setProjectIds()
return migrationResult
} catch (e) {
const message = `Migration failed: ${e}`
console.error(message)
migrationResult.error = message
return migrationResult
}
}

private static async createProject(proposal: ProposalWithOutcome) {
const creationDate = new Date()
const newProject = await ProjectModel.create({
Expand All @@ -184,7 +213,7 @@ export class ProjectService {
return newProject
}

private static async createPersonnel(proposal: ProposalWithOutcome, project: ProjectAttributes, creationDate: Date) {
private static async createPersonnel(proposal: ProposalAttributes, project: ProjectAttributes, creationDate: Date) {
const newPersonnel: PersonnelAttributes[] = []
const config =
proposal.type === ProposalType.Grant
Expand All @@ -204,7 +233,7 @@ export class ProjectService {
await PersonnelModel.createMany(newPersonnel)
}

private static async createMilestones(proposal: ProposalWithOutcome, project: ProjectAttributes, creationDate: Date) {
private static async createMilestones(proposal: ProposalAttributes, project: ProjectAttributes, creationDate: Date) {
const newMilestones: ProjectMilestone[] = []
const config =
proposal.type === ProposalType.Grant
Expand Down
47 changes: 29 additions & 18 deletions src/utils/projects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { TransparencyVesting } from '../clients/Transparency'
import { ProjectStatus, TransparencyProjectStatus } from '../entities/Grant/types'
import { ProposalAttributes, ProposalProject, ProposalWithProject } from '../entities/Proposal/types'
import {
ProjectVestingData,
ProposalAttributes,
ProposalProject,
ProposalWithProject,
} from '../entities/Proposal/types'

import Time from './date/Time'

Expand All @@ -23,38 +28,27 @@ function toGovernanceProjectStatus(status: TransparencyProjectStatus) {
}
}

function getProjectVestingData(proposal: ProposalAttributes, vesting: TransparencyVesting) {
function getProjectVestingData(proposal: ProposalAttributes, vesting?: TransparencyVesting): ProjectVestingData {
if (proposal.enacting_tx) {
return {
status: ProjectStatus.Finished,
enacting_tx: proposal.enacting_tx,
enacted_at: Time(proposal.updated_at).unix(),
}
}

if (!vesting) {
return {
status: ProjectStatus.Pending,
}
return {}
}

const {
vesting_status: status,
token,
vesting_start_at,
vesting_finish_at,
vesting_total_amount,
vesting_released,
vesting_releasable,
} = vesting
const { token, vesting_start_at, vesting_finish_at, vesting_total_amount, vesting_released, vesting_releasable } =
vesting

return {
status: toGovernanceProjectStatus(status),
token,
enacted_at: Time(vesting_start_at).unix(),
contract: {
vesting_total_amount: Math.round(vesting_total_amount),
vestedAmount: Math.round(vesting_released + vesting_releasable),
vested_amount: Math.round(vesting_released + vesting_releasable),
releasable: Math.round(vesting_releasable),
released: Math.round(vesting_released),
start_at: Time(vesting_start_at).unix(),
Expand All @@ -63,14 +57,31 @@ function getProjectVestingData(proposal: ProposalAttributes, vesting: Transparen
}
}

function getProjectStatus(proposal: ProposalAttributes, vesting?: TransparencyVesting) {
if (proposal.enacting_tx) {
return ProjectStatus.Finished
}

if (!vesting) {
return ProjectStatus.Pending
}

const { vesting_status } = vesting

return toGovernanceProjectStatus(vesting_status)
}

export function createProposalProject(proposal: ProposalWithProject, vesting?: TransparencyVesting): ProposalProject {
const vestingData = vesting ? getProjectVestingData(proposal, vesting) : {}
const vestingData = getProjectVestingData(proposal, vesting)
const status = getProjectStatus(proposal, vesting)

return {
id: proposal.id,
project_id: proposal.project_id,
status,
title: proposal.title,
user: proposal.user,
about: proposal.configuration.abstract,
type: proposal.type,
size: proposal.configuration.size || proposal.configuration.funding,
created_at: proposal.created_at.getTime(),
Expand Down

0 comments on commit fad9378

Please sign in to comment.