Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:Vizzuality/marxan-cloud into fix…
Browse files Browse the repository at this point in the history
…/custom-grid-texts-layer
  • Loading branch information
anamontiaga committed Nov 2, 2021
2 parents 88d8f01 + c49c117 commit 980855d
Show file tree
Hide file tree
Showing 55 changed files with 1,311 additions and 307 deletions.
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,23 @@ dump-api-data:
upload-dump-data:
az storage blob upload-batch --account-name marxancloudtest --auth-mode login -d data-ingestion-test-00/dbs-dumps -s data/data/processed/db_dumps

upload-volumes-data:
az storage blob upload-batch --account-name marxancloudtest --auth-mode login -d data-ingestion-test-00/dbs-volumes -s data/data/processed/db_volumes

upload-data-for-demo:
az storage blob upload-batch --account-name marxancloudtest --auth-mode login -d data-ingestion-test-00/data-demo -s data/data/data_demo/organized

restore-dumps:
docker-compose --project-name ${COMPOSE_PROJECT_NAME} -f ./data/docker-compose-data_management.yml up --build marxan-restore-data

## To generate volumes instances must be stop
create-volumes-data:
docker run --rm --volumes-from marxan-postgresql-api -v $$(pwd)/data/data/processed/db_volumes:/backup ubuntu tar cvf /backup/psql-api-data.tar /var/lib/postgresql/data && \
docker run --rm --volumes-from marxan-postgresql-geo-api -v $$(pwd)/data/data/processed/db_volumes:/backup ubuntu tar cvzf /backup/psql-geo-data.tar.gz /var/lib/postgresql/data

restore-volumes-data:
docker run --rm --volumes-from marxan-postgresql-api -v $$(pwd)/data/data/processed/db_volumes:/backup ubuntu bash -c "rm -rf /var/lib/postgresql/data/* && cd / && tar xvf /backup/psql-api-data.tar" && \
docker run --rm --volumes-from marxan-postgresql-geo-api -v $$(pwd)/data/data/processed/db_volumes:/backup ubuntu bash -c "rm -rf /var/lib/postgresql/data/* && cd / && tar xvf /backup/psql-geo-data.tar"
extract-geo-test-data:
#This location correspond with the Okavango delta touching partially Botswana, Angola Zambia and Namibia
TEST_GEOMETRY=$(shell cat api/apps/api/test/fixtures/test-geometry.json | jq 'tostring'); \
Expand Down
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ details.
* `API_AUTH_X_API_KEY` (string, required): a secret used as API key for
requests from the Geoprocessing service to the API; can be generated
similarly to `API_AUTH_JWT_SECRET`
* `API_SERVICE_PORT` (number, required): the port on which the API service should
listen on the local machine
* `API_SERVICE_PORT` (number, required): the port on which the API service
should listen on the local machine
* `API_SERVICE_URL` (URL, optional, default is http://api:3000): the internal
(docker-compose or k8s cluster) where the API service can be reached by
other services running in the cluster
Expand All @@ -57,8 +57,8 @@ details.
mount point for shared storage (via Docker volumes in development
environments and via Persistent Volumes in Kubernetes environments) should
be set accordingly
* `APP_SERVICE_PORT` (number, required): the port on which the App service should
listen on the local machine
* `APP_SERVICE_PORT` (number, required): the port on which the App service
should listen on the local machine
* `POSTGRES_API_SERVICE_PORT` (number, required): the port on which the
PostgreSQL service should listen on the local machine
* `API_POSTGRES_USER` (string, required): username to be used for the
Expand Down Expand Up @@ -90,12 +90,21 @@ details.
Redis Commander service should listen on the local machine
* `SPARKPOST_APIKEY` (string, required): an API key to be used for Sparkpost,
an email service
* `SPARKPOST_ORIGIN` (string, required): an address of Sparkpost server, please check
Sparkpost's documentation for details
* `PASSWORD_RESET_TOKEN_PREFIX` (string, required): a prefix of an address to reset
a password should point to a reset password screen; the token for reset is appended at the end
* `PASSWORD_RESET_EXPIRATION` (string, optional, default is 30 minutes): a time that a token
for a password reset is valid
* `SPARKPOST_ORIGIN` (string, required): the URL of a SparkPost API service:
this would normally be either `https://api.sparkpost.com` or
`https://api.eu.sparkpost.com` (note: **no trailing `/` character** or the
SparkPost API [client library](https://github.com/SparkPost/node-sparkpost)
will not work correctly); please check [SparkPost's
documentation](https://developers.sparkpost.com/api/#header-sparkpost-eu)
and the client library's own documentation for details
* `PASSWORD_RESET_TOKEN_PREFIX` (string, required): the public URL of the
**frontend** page on the running instance where users are redirected from
password reset emails to complete the process of resetting their
password; the reset token is appended at the end of this URL to compose
the actual link that is included in password reset emails
* `PASSWORD_RESET_EXPIRATION` (string, optional, default is 1800000
milliseconds: 30 minutes): a time (in milliseconds) that a token for a
password reset is valid for

The PostgreSQL credentials are used to create a database user when the
PostgreSQL container is started for the first time. PostgreSQL data is persisted
Expand Down
2 changes: 2 additions & 0 deletions api/apps/api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Unreleased
- The JSON:API serialization settings of IUCN categories did not map the `id`
property as appropriate, leading to undefined `id` properties being included
in JSON:API response payloads; this has now been fixed [MARXAN-335].
- Deleting projects that use custom (project-specific) features was not possible
due to a missing db cascade; this has now been fixed ([MARXAN-897]).

## [0.3.0]

Expand Down
2 changes: 2 additions & 0 deletions api/apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { AnalysisModule } from '@marxan-api/modules/analysis/analysis.module';
import { PlanningUnitsModule } from '@marxan-api/modules/planning-units/planning-units.module';
import { SpecificationModule } from '@marxan-api/modules/specification';
import { ScenarioSpecificationModule } from './modules/scenario-specification';
import { PublishedProjectModule } from '@marxan-api/modules/published-project/published-project.module';

@Module({
imports: [
Expand Down Expand Up @@ -61,6 +62,7 @@ import { ScenarioSpecificationModule } from './modules/scenario-specification';
PlanningUnitsModule,
SpecificationModule,
ScenarioSpecificationModule,
PublishedProjectModule,
],
controllers: [AppController, PingController],
providers: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddCascadeToFeaturesFKs1634209362000
implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE public.features DROP CONSTRAINT features_project_id_fkey;
ALTER TABLE public.features DROP CONSTRAINT features_created_by_fkey;
ALTER TABLE public.features ADD CONSTRAINT features_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE public.features ADD CONSTRAINT features_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL;
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE public.features DROP CONSTRAINT features_project_id_fkey;
ALTER TABLE public.features DROP CONSTRAINT features_created_by_fkey;
ALTER TABLE public.features ADD CONSTRAINT features_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id);
ALTER TABLE public.features ADD CONSTRAINT features_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id);
`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddPublishedProjectsTable1634807093754
implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "published_projects"
(
"id" uuid NOT NULL,
"name" character varying NOT NULL,
"description" text,
CONSTRAINT "REL_50b117f602b62155ceacac2fde" UNIQUE ("id"),
CONSTRAINT "PK_50b117f602b62155ceacac2fde3" PRIMARY KEY ("id")
)`);
await queryRunner.query(`ALTER TABLE "published_projects"
ADD CONSTRAINT "published_projects_id_fkey" FOREIGN KEY ("id") REFERENCES "projects" ("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "projects" DROP COLUMN "is_public"`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "published_projects" DROP CONSTRAINT "published_projects_id_fkey"`,
);
await queryRunner.query(`DROP TABLE "published_projects"`);
await queryRunner.query(`ALTER TABLE "projects"
ADD "is_public" boolean NOT NULL DEFAULT false`);
}
}
4 changes: 2 additions & 2 deletions api/apps/api/src/modules/countries/countries.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
AppBaseService,
JSONAPISerializerConfig,
} from '@marxan-api/utils/app-base.service';
import { apiConnections } from '../../ormconfig';
import { AppConfig } from '@marxan-api/utils/config.utils';
import { DbConnections } from '@marxan-api/ormconfig.connections';

@Injectable()
export class CountriesService extends AppBaseService<
Expand All @@ -22,7 +22,7 @@ export class CountriesService extends AppBaseService<
AppInfoDTO
> {
constructor(
@InjectRepository(Country, apiConnections.geoprocessingDB.name)
@InjectRepository(Country, DbConnections.geoprocessingDB)
private readonly countriesRepository: Repository<Country>,
) {
super(countriesRepository, 'country', 'countries', {
Expand Down
23 changes: 22 additions & 1 deletion api/apps/api/src/modules/geo-features/geo-feature.api.entity.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm';
import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
import { BaseServiceResource } from '@marxan-api/types/resource.interface';
import { FeatureTag } from '@marxan/features/domain';
import { JobStatus } from '../scenarios/scenario.api.entity';
import { Project } from '../projects/project.api.entity';
import { User } from '../users/user.api.entity';

export const geoFeatureResource: BaseServiceResource = {
className: 'GeoFeature',
Expand Down Expand Up @@ -66,6 +68,25 @@ export class GeoFeature extends BaseEntity {
@ApiPropertyOptional()
@Column('uuid', { name: 'project_id' })
projectId?: string;

@ApiProperty({ type: () => Project })
@ManyToOne((_type) => Project, {
onDelete: 'CASCADE',
})
@JoinColumn({
name: 'project_id',
referencedColumnName: 'id',
})
project?: Project;

@ManyToOne((_type) => User, {
onDelete: 'SET NULL',
})
@JoinColumn({
name: 'created_by',
referencedColumnName: 'id',
})
createdBy?: User;
}

export class JSONAPIGeoFeaturesData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class AdminPlanningAreasService implements AbstractPlanningAreasService {
constructor(
private readonly adminAreasRepository: AdminPlanningAreasRepository,
) {}

async getPlanningAreaIdAndName(ids: {
adminAreaLevel1Id?: string;
adminAreaLevel2Id?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ beforeEach(async () => {
uploader = fixtures.getUploader();
});

afterEach(async () => {
await fixtures.cleanup();
});

describe(`when file validated successfully`, () => {
let result: Either<typeof validationFailed, any>;
beforeEach(async () => {
Expand Down Expand Up @@ -111,6 +115,9 @@ async function getFixtures() {
)
.reply(500, 'failed');
},
cleanup() {
nock.enableNetConnect();
},
};
return fixtures;
}
10 changes: 0 additions & 10 deletions api/apps/api/src/modules/projects/project-details.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,6 @@ export class ProjectDetailsController {
}),
);
}

@SingleProject()
@Get('published/:id')
async getPublishedOne(
@Param('id') id: string,
): Promise<ProjectResultSingular> {
return await this.projectSerializer.serialize(
await this.projectsService.findOne(id),
);
}
}

function SingleProject() {
Expand Down
5 changes: 5 additions & 0 deletions api/apps/api/src/modules/projects/project-requests-info.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AppInfoDTO } from '@marxan-api/dto/info.dto';
import { BBox } from 'geojson';
import { User } from '@marxan-api/modules/users/user.api.entity';

export interface ProjectsRequest extends AppInfoDTO {
params?: {
Expand All @@ -9,3 +10,7 @@ export interface ProjectsRequest extends AppInfoDTO {
nameSearch?: string;
} & AppInfoDTO['params'];
}

export interface ProjectsServiceRequest extends ProjectsRequest {
authenticatedUser: User;
}
7 changes: 0 additions & 7 deletions api/apps/api/src/modules/projects/project.api.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,6 @@ export class Project extends TimeUserEntityMetadata {
@Column('character varying')
description?: string;

@Column({
type: `boolean`,
name: `is_public`,
default: false,
})
isPublic!: boolean;

/**
* The organization to which this scenario belongs.
*/
Expand Down
4 changes: 0 additions & 4 deletions api/apps/api/src/modules/projects/projects-crud.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,6 @@ export class ProjectsCrudService extends AppBaseService<
.andWhere(`acl.role_id = :roleId`, {
roleId: Roles.project_owner,
});
} else {
query.andWhere(`${this.alias}.is_public = true`);
}

return query;
Expand Down Expand Up @@ -384,8 +382,6 @@ export class ProjectsCrudService extends AppBaseService<
.andWhere(`acl.role_id = :roleId`, {
roleId: Roles.project_owner,
});
} else {
query.andWhere(`${this.alias}.is_public = true`);
}

return query;
Expand Down
14 changes: 0 additions & 14 deletions api/apps/api/src/modules/projects/projects-listing.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,6 @@ export class ProjectsListingController {
});
return this.projectSerializer.serializeAll(results.data, results.metadata);
}

@ProjectsListing()
@Get(`published`)
async findAllPublic(
@ProcessFetchSpecification() fetchSpecification: FetchSpecification,
@Query('q') namesSearch?: string,
): Promise<ProjectResultPlural> {
const results = await this.projectsService.findAll(fetchSpecification, {
params: {
namesSearch,
},
});
return this.projectSerializer.serializeAll(results.data, results.metadata);
}
}

function ProjectsListing() {
Expand Down
7 changes: 2 additions & 5 deletions api/apps/api/src/modules/projects/projects.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,12 @@ import { GetProjectHandler } from './get-project.handler';
JobStatusSerializer,
GetProjectHandler,
],
/**
* Order is important due to `GET projects/published` clash with
* `GET projects/:id`
*/
controllers: [
ProjectsListingController,
ProjectDetailsController,
ProjectsController,
],
exports: [ProjectsCrudService],
// @ToDo Remove TypeOrmModule after project publish will stop use the ProjectRepository
exports: [ProjectsCrudService, TypeOrmModule],
})
export class ProjectsModule {}
14 changes: 6 additions & 8 deletions api/apps/api/src/modules/projects/projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { UpdateProjectDTO } from './dto/update.project.dto';
import { PlanningAreasService } from './planning-areas';
import { assertDefined } from '@marxan/utils';

import { ProjectsRequest } from './project-requests-info';
import {
ProjectsRequest,
ProjectsServiceRequest,
} from './project-requests-info';
import { GetProjectErrors, GetProjectQuery } from '@marxan/projects';

export { validationFailed } from './planning-areas';
Expand Down Expand Up @@ -57,18 +60,13 @@ export class ProjectsService {
);
}

async findAll(fetchSpec: FetchSpecification, info?: ProjectsRequest) {
return this.projectsCrud.findAllPaginated(fetchSpec, info);
}

async findAllPublic(fetchSpec: FetchSpecification, info?: ProjectsRequest) {
// /ACL slot/
async findAll(fetchSpec: FetchSpecification, info: ProjectsServiceRequest) {
return this.projectsCrud.findAllPaginated(fetchSpec, info);
}

async findOne(
id: string,
info?: ProjectsRequest,
info: ProjectsServiceRequest,
): Promise<Project | undefined> {
// /ACL slot/
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Body, Controller, Param, Post, UseGuards } from '@nestjs/common';
import { PublishedProjectService } from '../published-project.service';
import { PublishProjectDto } from '../dto/publish-project.dto';
import { JwtAuthGuard } from '@marxan-api/guards/jwt-auth.guard';
import { ApiBearerAuth, ApiNoContentResponse, ApiTags } from '@nestjs/swagger';
import { projectResource } from '@marxan-api/modules/projects/project.api.entity';
import { apiGlobalPrefixes } from '@marxan-api/api.config';

@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiTags(projectResource.className)
@Controller(`${apiGlobalPrefixes.v1}/projects`)
export class PublishProjectController {
constructor(
private readonly publishedProjectService: PublishedProjectService,
) {}

@Post(':id/publish')
@ApiNoContentResponse()
async publish(
@Param('id') id: string,
@Body() createPublishedProjectDto: PublishProjectDto,
): Promise<void> {
await this.publishedProjectService.publish(id);
}
}
Loading

0 comments on commit 980855d

Please sign in to comment.