Skip to content

Commit

Permalink
feat: projects view (#1785)
Browse files Browse the repository at this point in the history
* chore: project entities (#1783)

* chore: create projects entity and table creation migration

* chore: remove unused functions

* chore: personnel entity and table creation migration, and some improvements to projects migration

* chore: project milestone entity and table creation migration

* chore: rename old proposal Project to ProposalProject

* chore: ProjectMilestoneUpdate entity and migrations

* chore: add id to personnel attributes

* chore: create projects per passed grants and bids proposals (#1784)

* chore: rename old proposal Project to ProposalProject

* chore: ProjectMilestoneUpdate entity and migrations

* chore: create projects por passed grants and bids proposals

* chore: add basic project info to proposal data on proposals endpoint (#1786)

* chore: rename enums (#1788)

* chore: rename ProjectStatus enum values to be consisten with system

* chore: rename PersonnelStatus, and MilestoneStatus enum values

* chore: add project endpoint (#1789)

* feat: get projects query (#1795)

* feat: get projects query

* requested changes

* feat: add address to team member schema (#1799)

* feat: validate milestones and add them to snapshot message (#1797)

* feat: validate milestones and add them to snapshot message

* chore: update date name field

* refactor: rename description field to tasks in milestone (#1802)

* chore: removed console log

* chore: create project personnel (#1796)

* 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

* fix: remove personnel status from projects query, (#1804)

* feat: use abstract as about in project (#1805)

* feat: validate milestones on bid request (#1803)

* fix: team address not required in schema (#1807)

* feat: create project milestones from proposal data (#1808)

* feat: add milestones to project query (#1810)

* chore: add personnel (#1814)

* chore: add personnel to project

* refactor: rename personnel in creation schema

* chore: delete personnel (#1815)

* chore: add personnel to project

* refactor: rename personnel in creation schema

* chore: order milestones and personnel by creation date

* chore: delete personnel endpoint

* refactor: move types

* chore: create project links (#1818)

* feat: project id in response (#1816)

* fix: migration error (#1820)

* fix: project query improvements (#1822)

* chore: rename updates table (#1824)

* chore: rename updates table

* chore: add project id column to updates table

* chore: project links (#1823)

* chore: create project links

* fix: add/delete link endpoints

* refactor: project editor validation

* refactor: remove unused method

* feat: add and delete milestone endpoints (#1829)

* feat: add and delete milestone endpoints

* refactor: use validate can edit project

* feat: milestones submit limit in proposals request (#1831)

* chore: Projects migration (#1826)

* chore: rename updates table

* chore: add project id column to updates table

* chore: projects migration

* requested changes

* chore: update project status on proposal enactment (#1828)

* chore: create project links

* fix: add/delete link endpoints

* refactor: project editor validation

* refactor: remove unused method

* refactor: proposal status update

* chore: set project to in progress on proposal enactment

* refactor: disable logging finish proposal job run locally

* chore: start project on proposal enactment

* refactor: rename fn

* feat: add roadmap field validation (#1833)

* feat: add roadmap field validation

* feat: add roadmap to snapshot message

* chore: update project status with vesting status on fetch/enactment (#1836)

* chore: update project status on project fetch

* chore: update project status on project fetch / enactment using the vesting contract

* refactor: remove unnecessary assignment

* refactor: Updates (#1835)

* refactor: update creation

* requested changes

* fix: test

* fix: type

* fix: use updated_at as created_at for projects (#1841)

* feat: add proposal link as project link (#1839)

* Chore: add vesting to project (#1838)

* refactor: unify vesting types

* chore: add funding info to projects

* chore: add tx_amount to one time payments

* refactor: renames and remove unused code

* refactor: rename updates method (#1843)

* chore: merge master into projects view (#1847)

* feat: b&t notifications (#1774)

* fix: removed duplicated notification on finished proposal

* feat: B&T notifications

* notification types

* requested changes

* fix: variables naming (#1819)

* chore: add endpoints for snapshot subgraph and update api url (#1845)

* feat: update decentraland-gatsby v6.2.0 (#1846)

---------

Co-authored-by: Nicolás Comerci <45410089+ncomerci@users.noreply.github.com>
Co-authored-by: lemu <github@lemu.sh>
Co-authored-by: Braian Mellor <braianj@gmail.com>

* feat: Not editable project status (#1848)

* feat: added not editable validation

* requested change

* fix: migrations and remove links creation on migration/project create (#1852)

* fix: migration error on table rename

* fix: check legacy condition migrating projects, remove proposal links

* fix: revert text replace (#1856)

---------

Signed-off-by: Andy Espagnolo <andyespagnolo@gmail.com>
Co-authored-by: Nicolás Comerci <45410089+ncomerci@users.noreply.github.com>
Co-authored-by: Andy Espagnolo <andyespagnolo@gmail.com>
Co-authored-by: ncomerci <ncomerci@itba.edu.ar>
Co-authored-by: Braian Mellor <braianj@gmail.com>
  • Loading branch information
5 people committed Jun 13, 2024
1 parent 3b4231b commit 9f8efb9
Show file tree
Hide file tree
Showing 58 changed files with 1,816 additions and 1,341 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"build": "npm run build:server",
"build:server": "tsc -p .",
"format": "prettier --write \"**/*.{js,jsx,json,md,ts,tsx}\"",
"start": "concurrently -c blue,green -n SERVER,FRONT 'npm run serve' 'npm run develop'",
"start": "concurrently -c blue -n SERVER 'npm run serve'",
"serve": "DOTENV_CONFIG_PATH=.env.development nodemon --watch src/entities --watch src/back --watch src/clients --watch src/services --watch src/server.ts --watch static/api.yaml -e ts,json --exec 'ts-node -r dotenv/config' src/server",
"version": "tsc -p .",
"migrate": "DOTENV_CONFIG_PATH=.env.development ts-node -r dotenv/config.js ./node_modules/node-pg-migrate/bin/node-pg-migrate -j ts -m src/migrations -d CONNECTION_STRING",
Expand Down
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
}
18 changes: 18 additions & 0 deletions src/back/models/Personnel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'

import { TeamMember } from '../../entities/Grant/types'

export type PersonnelAttributes = TeamMember & {
id: string
project_id: string
deleted: boolean
updated_by?: string
updated_at?: Date
created_at: Date
}

export default class PersonnelModel extends Model<PersonnelAttributes> {
static tableName = 'personnel'
static withTimestamps = false
static primaryKey = 'id'
}
115 changes: 115 additions & 0 deletions src/back/models/Project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import crypto from 'crypto'
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'
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 { ProjectFunding, ProposalProject } from '../../entities/Proposal/types'

import PersonnelModel, { PersonnelAttributes } from './Personnel'
import ProjectLinkModel, { ProjectLink } from './ProjectLink'
import ProjectMilestoneModel, { ProjectMilestone } from './ProjectMilestone'

export type ProjectAttributes = {
id: string
proposal_id: string
title: string
status: ProjectStatus
about?: string
about_updated_by?: string
about_updated_at?: Date
updated_at?: Date
created_at: Date
}

export type Project = ProjectAttributes & {
personnel: PersonnelAttributes[]
links: ProjectLink[]
milestones: ProjectMilestone[]
author: string
coauthors: string[] | null
vesting_addresses: string[]
funding?: ProjectFunding
}

export default class ProjectModel extends Model<ProjectAttributes> {
static tableName = 'projects'
static withTimestamps = false
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,
p.vesting_addresses as vesting_addresses,
COALESCE(json_agg(DISTINCT to_jsonb(pe.*)) FILTER (WHERE pe.id IS NOT NULL), '[]') as personnel,
COALESCE(json_agg(DISTINCT to_jsonb(mi.*)) FILTER (WHERE mi.id IS NOT NULL), '[]') as milestones,
COALESCE(json_agg(DISTINCT to_jsonb(li.*)) FILTER (WHERE li.id IS NOT NULL), '[]') as links,
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(ProjectMilestoneModel)} mi ON pr.id = mi.project_id
LEFT JOIN ${table(ProjectLinkModel)} li ON pr.id = li.project_id
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, p.vesting_addresses;
`

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

return result[0]
}

static async migrate(proposals: ProposalProject[]) {
const values = proposals.map(
({ id, title, about, status, updated_at }) =>
SQL`(${crypto.randomUUID()}, ${id}, ${title}, ${about}, ${status}, ${new Date(updated_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}"`)
}
if (!isEthereumAddress(user)) {
throw new Error(`Invalid user: "${user}"`)
}

const query = SQL`
SELECT EXISTS (
SELECT 1
FROM ${table(ProjectModel)} pr
JOIN ${table(ProposalModel)} p ON pr.proposal_id = p.id
LEFT JOIN ${table(CoauthorModel)} co ON pr.proposal_id = co.proposal_id
AND co.status = ${CoauthorStatus.APPROVED}
WHERE pr.id = ${projectId}
AND (p.user = ${user} OR co.address = ${user})
) AS "exists"
`

const result = await this.namedQuery<{ exists: boolean }>(`is_author_or_coauthor`, query)
return result[0]?.exists || false
}
}
18 changes: 18 additions & 0 deletions src/back/models/ProjectLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'

export type ProjectLink = {
id: string
project_id: string
label: string
url: string
updated_by?: string
updated_at?: Date
created_by: string
created_at: Date
}

export default class ProjectLinkModel extends Model<ProjectLink> {
static tableName = 'project_links'
static withTimestamps = false
static primaryKey = 'id'
}
26 changes: 26 additions & 0 deletions src/back/models/ProjectMilestone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'

export type ProjectMilestone = {
id: string
project_id: string
title: string
description: string
delivery_date: Date
status: ProjectMilestoneStatus
updated_by?: string
updated_at?: Date
created_by: string
created_at: Date
}

export enum ProjectMilestoneStatus {
Pending = 'pending',
InProgress = 'in_progress',
Done = 'done',
}

export default class ProjectMilestoneModel extends Model<ProjectMilestone> {
static tableName = 'project_milestones'
static withTimestamps = false
static primaryKey = 'id'
}
16 changes: 16 additions & 0 deletions src/back/models/ProjectMilestoneUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'

export type ProjectMilestoneUpdate = {
id: string
update_id: string
milestone_id: string
description: string
tasks: string[]
created_at: Date
}

export default class ProjectMilestoneUpdateModel extends Model<ProjectMilestoneUpdate> {
static tableName = 'project_milestone_updates'
static withTimestamps = false
static primaryKey = 'id'
}
4 changes: 2 additions & 2 deletions src/back/routes/budget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Budget, BudgetWithContestants, CategoryBudget } from '../../entities/Bu
import { QuarterBudgetAttributes } from '../../entities/QuarterBudget/types'
import { toNewGrantCategory } from '../../entities/QuarterCategoryBudget/utils'
import { BudgetService } from '../../services/BudgetService'
import { validateProposalId } from '../utils/validations'
import { validateId } from '../utils/validations'

export default routes((route) => {
const withAuth = auth()
Expand Down Expand Up @@ -48,6 +48,6 @@ async function getCurrentContestedBudget(): Promise<BudgetWithContestants> {
}

async function getBudgetWithContestants(req: Request<{ proposal: string }>): Promise<BudgetWithContestants> {
const id = validateProposalId(req.params.proposal)
const id = validateId(req.params.proposal)
return await BudgetService.getBudgetWithContestants(id)
}
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
4 changes: 2 additions & 2 deletions src/back/routes/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { EventsService } from '../services/events'
import {
validateDebugAddress,
validateEventTypesFilters,
validateProposalId,
validateId,
validateRequiredString,
} from '../utils/validations'

Expand All @@ -26,7 +26,7 @@ async function getLatestEvents(req: Request) {
async function voted(req: WithAuth) {
const user = req.auth!

validateProposalId(req.body.proposalId)
validateId(req.body.proposalId)
validateRequiredString('proposalTitle', req.body.proposalTitle)
validateRequiredString('choice', req.body.choice)
return await EventsService.voted(req.body.proposalId, req.body.proposalTitle, req.body.choice, user)
Expand Down
Loading

0 comments on commit 9f8efb9

Please sign in to comment.