Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: create project personnel #1796

Merged
merged 9 commits into from
May 21, 2024
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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we validating this is an id?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are in the route handler, but I'll add a check in the model for xtra safu

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