Skip to content

Commit

Permalink
chore: create project personnel (#1796)
Browse files Browse the repository at this point in the history
* chore: project personnel creation and retrieval

* chore: use TeamMember in Personnel

* chore: use an empty array when projects have no personnel

* fix: query with relevantLink

* fix: replace personnel status for deleted check

* fix: add project status type deletion on down migration

* chore: address pr comments
  • Loading branch information
1emu committed May 21, 2024
1 parent 3c69ab4 commit b229e76
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 61 deletions.
17 changes: 4 additions & 13 deletions src/back/models/Personnel.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'

export type PersonnelAttributes = {
import { TeamMember } from '../../entities/Grant/types'

export type PersonnelAttributes = TeamMember & {
id: string
project_id: string
address?: string
name: string
role: string
description: string
link?: string
status: PersonnelStatus
deleted: boolean
updated_by?: string
updated_at?: Date
created_at: Date
}

export enum PersonnelStatus {
Deleted = 'deleted',
Unassigned = 'unassigned',
Assigned = 'assigned',
}

export default class PersonnelModel extends Model<PersonnelAttributes> {
static tableName = 'personnel'
static withTimestamps = false
Expand Down
50 changes: 43 additions & 7 deletions src/back/models/Project.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'
import { SQL } from 'decentraland-gatsby/dist/entities/Database/utils/sql'
import { SQL, table } from 'decentraland-gatsby/dist/entities/Database/utils'
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 PersonnelModel, { PersonnelAttributes } from './Personnel'

export type ProjectAttributes = {
id: string
Expand All @@ -18,6 +24,7 @@ export type ProjectAttributes = {

// TODO: add here all data from other tables (updates, personnel, milestones, etc)
export type Project = ProjectAttributes & {
personnel: PersonnelAttributes[]
author: string
coauthors: string[] | null
}
Expand All @@ -28,15 +35,44 @@ export default class ProjectModel extends Model<ProjectAttributes> {
static primaryKey = 'id'

static async getProject(id: string) {
if (!isUUID(id || '')) {
throw new Error(`Invalid project id: "${id}"`)
}

const query = SQL`
SELECT pr.*, p.user as author, array_agg(co.address) as coauthors
FROM projects pr
JOIN proposals p on pr.proposal_id = p.id
LEFT JOIN coauthors co on pr.proposal_id = co.proposal_id AND co.status = 'APPROVED'
WHERE pr.id = ${id}
GROUP BY pr.id, p.user;
SELECT
pr.*,
p.user as author,
COALESCE(json_agg(
json_build_object(
'id', pe.id,
'project_id', pe.project_id,
'address', pe.address,
'name', pe.name,
'role', pe.role,
'about', pe.about,
'relevantLink', pe."relevantLink",
'status', pe.status,
'updated_by', pe.updated_by,
'updated_at', pe.updated_at,
'created_at', pe.created_at
) ORDER BY pe.id) FILTER (WHERE pe.id IS NOT NULL), '[]') AS personnel,
COALESCE(array_agg(co.address) FILTER (WHERE co.address IS NOT NULL), '{}') AS coauthors
FROM ${table(ProjectModel)} pr
JOIN ${table(ProposalModel)} p ON pr.proposal_id = p.id
LEFT JOIN ${table(PersonnelModel)} pe ON pr.id = pe.project_id AND pe.deleted = false
LEFT JOIN ${table(CoauthorModel)} co ON pr.proposal_id = co.proposal_id AND co.status = ${
CoauthorStatus.APPROVED
}
WHERE pr.id = ${id}
GROUP BY pr.id, p.user;
`

const result = await this.namedQuery<Project>(`get_project`, query)
if (!result || result.length === 0) {
throw new Error(`Project not found: "${id}"`)
}

return result[0]
}
}
1 change: 1 addition & 0 deletions src/back/routes/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ async function getProject(req: Request<{ project: string }>) {
try {
return await ProjectService.getProject(id)
} catch (e) {
console.log(`Error getting project: ${e}`) //TODO: remove before merging projects to main
throw new RequestError(`Project "${id}" not found`, RequestError.NotFound)
}
}
Expand Down
25 changes: 11 additions & 14 deletions src/entities/Bid/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { BudgetBreakdownConcept, GrantRequestDueDiligenceSchema, GrantRequestTeamSchema } from '../Grant/types'
import {
BudgetBreakdownConcept,
GrantRequestDueDiligenceSchema,
GrantRequestTeamSchema,
ProposalRequestTeam,
} from '../Grant/types'

import { BID_MIN_PROJECT_DURATION } from './constants'

Expand Down Expand Up @@ -39,30 +44,22 @@ export type BidRequestGeneralInfo = {
coAuthors?: string[]
}

export type TeamMember = {
name: string
role: string
about: string
address?: string
relevantLink?: string
}

export type BidRequestTeam = {
members: TeamMember[]
}

export type BidRequestDueDiligence = {
budgetBreakdown: BudgetBreakdownConcept[]
}

export type BidRequest = BidRequestFunding &
BidRequestGeneralInfo &
BidRequestTeam &
ProposalRequestTeam &
BidRequestDueDiligence & {
linked_proposal_id: string
coAuthors?: string[]
}

export type BidProposalConfiguration = BidRequest & { bid_number: number } & { created_at: string } & {
choices: string[]
}

export const BidRequestFundingSchema = {
funding: {
type: 'integer',
Expand Down
6 changes: 3 additions & 3 deletions src/entities/Grant/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export type GrantRequest = {
category: NewGrantCategory | null
} & GrantRequestFunding &
GrantRequestGeneralInfo &
GrantRequestTeam &
ProposalRequestTeam &
GrantRequestCategoryAssessment &
GrantRequestDueDiligence

Expand Down Expand Up @@ -435,9 +435,9 @@ export type GrantRequestDueDiligence = {

export type TeamMember = {
name: string
address?: string
role: string
about: string
address?: string
relevantLink?: string
}

Expand All @@ -447,7 +447,7 @@ type Milestone = {
delivery_date: string
}

export type GrantRequestTeam = {
export type ProposalRequestTeam = {
members: TeamMember[]
}

Expand Down
4 changes: 2 additions & 2 deletions src/entities/Proposal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import {
CategoryAssessmentQuestions,
GrantRequestDueDiligence,
GrantRequestGeneralInfo,
GrantRequestTeam,
GrantTierType,
PaymentToken,
ProjectStatus,
ProposalGrantCategory,
ProposalRequestTeam,
SubtypeOptions,
VestingStartDate,
} from '../Grant/types'
Expand Down Expand Up @@ -680,7 +680,7 @@ export const ProposalRequiredVP = {

export type GrantProposalConfiguration = GrantRequestGeneralInfo &
GrantRequestDueDiligence &
GrantRequestTeam & {
ProposalRequestTeam & {
category: ProposalGrantCategory | null
size: number
paymentToken?: PaymentToken
Expand Down
16 changes: 7 additions & 9 deletions src/migrations/1715019261618_create-personnel-table.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { MigrationBuilder } from "node-pg-migrate"

import Model, { PersonnelStatus } from "../back/models/Personnel"
import Model from "../back/models/Personnel"
import ProjectModel from "../back/models/Project"

const STATUS_TYPE = 'personnel_status_type'

export async function up(pgm: MigrationBuilder): Promise<void> {
pgm.createType(STATUS_TYPE, Object.values(PersonnelStatus))
pgm.createTable(Model.tableName, {
id: {
type: 'TEXT',
Expand All @@ -28,16 +25,17 @@ export async function up(pgm: MigrationBuilder): Promise<void> {
type: 'TEXT',
notNull: true
},
description: {
about: {
type: 'TEXT',
notNull: true
},
link: {
relevantLink: {
type: 'TEXT',
},
status: {
type: STATUS_TYPE,
deleted: {
type: 'BOOLEAN',
notNull: true,
default: false
},
updated_at: {
type: 'TIMESTAMPTZ',
Expand All @@ -59,5 +57,5 @@ export async function up(pgm: MigrationBuilder): Promise<void> {

export async function down(pgm: MigrationBuilder): Promise<void> {
pgm.dropTable(Model.tableName)
pgm.dropType(STATUS_TYPE, { cascade: true })
pgm.dropType('personnel_status_type', { cascade: true })
}
19 changes: 11 additions & 8 deletions src/services/BidService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import JobContext from 'decentraland-gatsby/dist/entities/Job/context'

import UnpublishedBidModel from '../entities/Bid/model'
import { UnpublishedBidAttributes, UnpublishedBidStatus } from '../entities/Bid/types'
import { BidProposalConfiguration, UnpublishedBidAttributes, UnpublishedBidStatus } from '../entities/Bid/types'
import { GATSBY_GRANT_VP_THRESHOLD } from '../entities/Grant/constants'
import ProposalModel from '../entities/Proposal/model'
import { ProposalType } from '../entities/Proposal/types'
Expand All @@ -15,6 +15,7 @@ import { ErrorService } from './ErrorService'
import { ProposalService } from './ProposalService'

const MINIMUM_BIDS_TO_PUBLISH = Number(process.env.MINIMUM_BIDS_TO_PUBLISH || 0)

export default class BidService {
static async createBid(
linked_proposal_id: string,
Expand Down Expand Up @@ -102,16 +103,18 @@ export default class BidService {
? Number(GATSBY_GRANT_VP_THRESHOLD)
: getHighBudgetVpThreshold(Number(bid_proposal_data.funding))

const bidProposalConfiguration: BidProposalConfiguration = {
bid_number,
linked_proposal_id,
created_at,
...bid_proposal_data,
choices: DEFAULT_CHOICES,
}

await ProposalService.createProposal({
type: ProposalType.Bid,
user: author_address,
configuration: {
bid_number,
linked_proposal_id,
created_at,
...bid_proposal_data,
choices: DEFAULT_CHOICES,
},
configuration: bidProposalConfiguration,
required_to_pass,
finish_at,
})
Expand Down
34 changes: 29 additions & 5 deletions src/services/ProjectService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import crypto from 'crypto'

import PersonnelModel, { PersonnelAttributes } from '../back/models/Personnel'
import ProjectModel, { ProjectAttributes } from '../back/models/Project'
import { TransparencyVesting } from '../clients/Transparency'
import UnpublishedBidModel from '../entities/Bid/model'
import { BidProposalConfiguration } from '../entities/Bid/types'
import { GrantTier } from '../entities/Grant/GrantTier'
import { GRANT_PROPOSAL_DURATION_IN_SECONDS } from '../entities/Grant/constants'
import { GrantRequest, ProjectStatus, TransparencyProjectStatus } from '../entities/Grant/types'
Expand Down Expand Up @@ -164,20 +166,42 @@ export class ProjectService {
}

private static async createProject(proposal: ProposalWithOutcome) {
const newProject: ProjectAttributes = {
const creationDate = new Date()
const newProject = await ProjectModel.create({
id: crypto.randomUUID(),
proposal_id: proposal.id,
title: proposal.title,
status: ProjectStatus.Pending,
links: [],
created_at: new Date(),
}
created_at: creationDate,
})

await ProjectService.createPersonnel(proposal, newProject, creationDate)

return await ProjectModel.create(newProject)
return newProject
}

private static async createPersonnel(proposal: ProposalWithOutcome, project: ProjectAttributes, creationDate: Date) {
const newPersonnel: PersonnelAttributes[] = []
const config =
proposal.type === ProposalType.Grant
? (proposal.configuration as GrantProposalConfiguration)
: (proposal.configuration as BidProposalConfiguration)
config.members?.forEach((member) => {
if (member) {
newPersonnel.push({
...member,
id: crypto.randomUUID(),
project_id: project.id,
created_at: creationDate,
deleted: false,
})
}
})
await PersonnelModel.createMany(newPersonnel)
}

static async getProject(id: string) {
//TODO: add all data to project from other tables (updates, personnel, milestones, etc) & return Project type, instead of ProjectAttributes
const project = await ProjectModel.getProject(id)
if (!project) {
throw new Error(`Project not found: "${id}"`)
Expand Down

0 comments on commit b229e76

Please sign in to comment.