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

[TM-1398] Merge tree species epic #27

Merged
merged 20 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ed64464
[TM-1401] Set up new entity service.
roguenet Dec 4, 2024
db6956d
[TM-1401] Set up the tree species research table.
roguenet Dec 4, 2024
7d898ac
[TM-1401] Create the scientific names search endpoint.
roguenet Dec 4, 2024
5ea4ea2
[TM-1401] Regenerate package-lock.json
roguenet Dec 4, 2024
b7bd679
[TM-1401] Small cleanup pass
roguenet Dec 5, 2024
acd04e8
[TM-1401] Don't pretend this is returning the full tree species model.
roguenet Dec 5, 2024
e4ae654
[TM-1401] Specs for the entity service.
roguenet Dec 5, 2024
fe93eed
[TM-1401] Ensure the data object in this endpoint is always an array.
roguenet Dec 5, 2024
5a2404d
[TM-1401] Move this factory to the index.
roguenet Dec 5, 2024
aa75793
Merge pull request #24 from wri/feat/TM-1401-cleaned-tree-species
roguenet Dec 6, 2024
b2a2f95
[TM-1402] Update the tree_species_research table.
roguenet Dec 10, 2024
e68e41f
[TM-1402] Initial POC of getting entity establishment trees.
roguenet Dec 11, 2024
7d817c3
[TM-1402] Support nursery reports in establishment tree species.
roguenet Dec 12, 2024
0b5c03c
[TM-1402] Full support for establishment species.
roguenet Dec 12, 2024
8d59c53
[TM-1402] Implemented previous planting counts.
roguenet Dec 12, 2024
a5ba2ee
[TM-1402] Avoid constraints on polymorphic association.
roguenet Dec 12, 2024
a694b17
[TM-1402] tree controller coverage complete.
roguenet Dec 12, 2024
715ab5f
[TM-1402] tree service coverage complete.
roguenet Dec 13, 2024
49123e2
Merge pull request #26 from wri/feat/TM-1402-tree-species-design-inte…
roguenet Dec 13, 2024
d16ce65
[TM-1398] Merge remote-tracking branch 'origin/staging' into epic/TM-…
roguenet Dec 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.local.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ USER_SERVICE_PORT=4010
JOB_SERVICE_PORT=4020
RESEARCH_SERVICE_PORT=4030
UNIFIED_DATABASE_SERVICE_PORT=4040
ENTITY_SERVICE_PORT=4050

DB_HOST=localhost
DB_PORT=3360
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/deploy-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ on:
required: true
options:
- job-service
- research-service
- user-service
- entity-service
- research-service
- unified-database-service
env:
description: 'Deployment target environment'
Expand Down Expand Up @@ -70,7 +71,7 @@ jobs:
: # Don't build the base image with NODE_ENV because it'll limit the packages that are installed
docker build -t terramatch-microservices-base:nx-base .
SERVICE_IMAGE=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker build --build-arg NODE_ENV=production --build-arg BUILD_FLAG=--prod -f apps/${{ inputs.service }}/Dockerfile -t $SERVICE_IMAGE .
docker build --build-arg NODE_ENV=production --build-arg BUILD_FLAG='--prod --verbose' -f apps/${{ inputs.service }}/Dockerfile -t $SERVICE_IMAGE .
docker push $SERVICE_IMAGE
echo "image=$SERVICE_IMAGE"

Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/deploy-services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ jobs:
outputs:
job-service-enabled: ${{ steps.check-services.outputs.job }}
user-service-enabled: ${{ steps.check-services.outputs.user }}
entity-service-enabled: ${{ steps.check-services.outputs.entity }}
research-service-enabled: ${{ steps.check-services.outputs.research }}
unified-database-service-enabled: ${{ steps.check-services.outputs.unified-database }}
steps:
- id: check-services
run: |
echo "job=${{ vars.ENABLED_SERVICES == '' || contains(vars.ENABLED_SERVICES, 'job-service') }}" >> "$GITHUB_OUTPUT"
echo "user=${{ vars.ENABLED_SERVICES == '' || contains(vars.ENABLED_SERVICES, 'user-service') }}" >> "$GITHUB_OUTPUT"
echo "entity=${{ vars.ENABLED_SERVICES == '' || contains(vars.ENABLED_SERVICES, 'entity-service') }}" >> "$GITHUB_OUTPUT"
echo "research=${{ vars.ENABLED_SERVICES == '' || contains(vars.ENABLED_SERVICES, 'research-service') }}" >> "$GITHUB_OUTPUT"
echo "unified-database=${{ vars.ENABLED_SERVICES == '' || contains(vars.ENABLED_SERVICES, 'unified-database-service') }}" >> "$GITHUB_OUTPUT"

Expand All @@ -44,7 +46,6 @@ jobs:
service: job-service
secrets: inherit


user-service:
needs: check-services
if: needs.check-services.outputs.user-service-enabled == 'true'
Expand All @@ -54,6 +55,15 @@ jobs:
service: user-service
secrets: inherit

entity-service:
needs: check-services
if: needs.check-services.outputs.entity-service-enabled == 'true'
uses: ./.github/workflows/deploy-service.yml
with:
env: ${{ inputs.env }}
service: entity-service
secrets: inherit

research-service:
needs: check-services
if: needs.check-services.outputs.research-service-enabled == 'true'
Expand Down
18 changes: 18 additions & 0 deletions apps/entity-service-e2e/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
19 changes: 19 additions & 0 deletions apps/entity-service-e2e/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable */
export default {
displayName: "entity-service-e2e",
preset: "../../jest.preset.js",
globalSetup: "<rootDir>/src/support/global-setup.ts",
globalTeardown: "<rootDir>/src/support/global-teardown.ts",
setupFiles: ["<rootDir>/src/support/test-setup.ts"],
testEnvironment: "node",
transform: {
"^.+\\.[tj]s$": [
"ts-jest",
{
tsconfig: "<rootDir>/tsconfig.spec.json"
}
]
},
moduleFileExtensions: ["ts", "js", "html"],
coverageDirectory: "../../coverage/entity-service-e2e"
};
17 changes: 17 additions & 0 deletions apps/entity-service-e2e/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "entity-service-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"implicitDependencies": ["entity-service"],
"targets": {
"e2e": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"],
"options": {
"jestConfig": "apps/entity-service-e2e/jest.config.ts",
"passWithNoTests": true
},
"dependsOn": ["entity-service:build"]
}
}
}
10 changes: 10 additions & 0 deletions apps/entity-service-e2e/src/entity-service/entity-service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import axios from "axios";

describe("GET /api", () => {
it("should return a message", async () => {
const res = await axios.get(`/api`);

expect(res.status).toBe(200);
expect(res.data).toEqual({ message: "Hello API" });
});
});
10 changes: 10 additions & 0 deletions apps/entity-service-e2e/src/support/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable */
var __TEARDOWN_MESSAGE__: string;

module.exports = async function () {
// Start services that that the app needs to run (e.g. database, docker-compose, etc.).
console.log("\nSetting up...\n");

// Hint: Use `globalThis` to pass variables to global teardown.
globalThis.__TEARDOWN_MESSAGE__ = "\nTearing down...\n";
};
7 changes: 7 additions & 0 deletions apps/entity-service-e2e/src/support/global-teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable */

module.exports = async function () {
// Put clean up logic here (e.g. stopping services, docker-compose, etc.).
// Hint: `globalThis` is shared between setup and teardown.
console.log(globalThis.__TEARDOWN_MESSAGE__);
};
10 changes: 10 additions & 0 deletions apps/entity-service-e2e/src/support/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable */

import axios from "axios";

module.exports = async function () {
// Configure axios for tests to use.
const host = process.env.HOST ?? "localhost";
const port = process.env.PORT ?? "3000";
axios.defaults.baseURL = `http://${host}:${port}`;
};
13 changes: 13 additions & 0 deletions apps/entity-service-e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}
9 changes: 9 additions & 0 deletions apps/entity-service-e2e/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.ts"]
}
18 changes: 18 additions & 0 deletions apps/entity-service/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
15 changes: 15 additions & 0 deletions apps/entity-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM terramatch-microservices-base:nx-base AS builder

ARG BUILD_FLAG
WORKDIR /app/builder
COPY . .
RUN npx nx build entity-service ${BUILD_FLAG}

FROM terramatch-microservices-base:nx-base

ARG NODE_ENV
WORKDIR /app
COPY --from=builder /app/builder ./
ENV NODE_ENV=${NODE_ENV}

CMD ["node", "./dist/apps/entity-service/main.js"]
11 changes: 11 additions & 0 deletions apps/entity-service/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: "entity-service",
preset: "../../jest.preset.js",
testEnvironment: "node",
transform: {
"^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }]
},
moduleFileExtensions: ["ts", "js", "html"],
coverageDirectory: "../../coverage/apps/entity-service"
};
26 changes: 26 additions & 0 deletions apps/entity-service/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "entity-service",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/entity-service/src",
"projectType": "application",
"tags": [],
"targets": {
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": ["build"],
"options": {
"buildTarget": "entity-service:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "entity-service:build:development"
},
"production": {
"buildTarget": "entity-service:build:production"
}
}
}
}
}
13 changes: 13 additions & 0 deletions apps/entity-service/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from "@nestjs/common";
import { DatabaseModule } from "@terramatch-microservices/database";
import { CommonModule } from "@terramatch-microservices/common";
import { HealthModule } from "./health/health.module";
import { TreesController } from "./trees/trees.controller";
import { TreeService } from "./trees/tree.service";

@Module({
imports: [DatabaseModule, CommonModule, HealthModule],
controllers: [TreesController],
providers: [TreeService]
})
export class AppModule {}
Empty file.
28 changes: 28 additions & 0 deletions apps/entity-service/src/health/health.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, SequelizeHealthIndicator } from '@nestjs/terminus';
import { NoBearerAuth } from '@terramatch-microservices/common/guards';
import { ApiExcludeController } from '@nestjs/swagger';
import { User } from '@terramatch-microservices/database/entities';

@Controller('health')
@ApiExcludeController()
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly db: SequelizeHealthIndicator
) {}

@Get()
@HealthCheck()
@NoBearerAuth
async check() {
const connection = await User.sequelize.connectionManager.getConnection({ type: 'read' });
try {
return this.health.check([
() => this.db.pingCheck('database', { connection })
]);
} finally {
User.sequelize.connectionManager.releaseConnection(connection);
}
}
}
9 changes: 9 additions & 0 deletions apps/entity-service/src/health/health.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';

@Module({
imports: [TerminusModule],
controllers: [HealthController],
})
export class HealthModule {}
33 changes: 33 additions & 0 deletions apps/entity-service/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Logger, ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { TMLogService } from "@terramatch-microservices/common/util/tm-log.service";

async function bootstrap() {
const app = await NestFactory.create(AppModule);

if (process.env.NODE_ENV === "development") {
// CORS is handled by the Api Gateway in AWS
app.enableCors();
}

const config = new DocumentBuilder()
.setTitle("TerraMatch Entity Service")
.setDescription("APIs related to entities")
.setVersion("1.0")
.addTag("entity-service")
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("entity-service/documentation/api", app, document);

app.useGlobalPipes(new ValidationPipe({ transform: true, transformOptions: { enableImplicitConversion: true } }));
app.useLogger(app.get(TMLogService));

const port = process.env.NODE_ENV === "production" ? 80 : process.env.ENTITY_SERVICE_PORT ?? 4050;
await app.listen(port);

Logger.log(`TerraMatch Entity Service is running on: http://localhost:${port}`);
}

bootstrap();
23 changes: 23 additions & 0 deletions apps/entity-service/src/trees/dto/establishment-trees.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { JsonApiAttributes } from "@terramatch-microservices/common/dto/json-api-attributes";
import { JsonApiDto } from "@terramatch-microservices/common/decorators";
import { ApiProperty } from "@nestjs/swagger";

// The ID for this DTO is formed of "entityType|entityUuid". This is a virtual resource, not directly
// backed by a single DB table.
@JsonApiDto({ type: "establishmentTrees", id: "string" })
export class EstablishmentsTreesDto extends JsonApiAttributes<EstablishmentsTreesDto> {
@ApiProperty({
type: [String],
description: "The species that were specified at the establishment of the parent entity."
})
establishmentTrees: string[];

@ApiProperty({
type: "object",
additionalProperties: { type: "number" },
nullable: true,
description: "If the entity in this request is a report, the sum totals of previous planting by species.",
example: { "Aster persaliens": 256, "Cirsium carniolicum": 1024 }
})
previousPlantingCounts?: Record<string, number>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// The entities that are able to ask for what their establishment tree data was.
import { ApiProperty } from "@nestjs/swagger";
import { IsIn, IsUUID } from "class-validator";
import { ESTABLISHMENT_ENTITIES, EstablishmentEntity } from "../tree.service";

export class EstablishmentsTreesParamsDto {
@IsIn(ESTABLISHMENT_ENTITIES)
@ApiProperty({
enum: ESTABLISHMENT_ENTITIES,
description: "Entity type for which to retrieve the establishment tree data."
})
entity: EstablishmentEntity;

@IsUUID()
@ApiProperty({ description: "Entity UUID for which to retrieve the establishment tree data." })
uuid: string;
}
Loading
Loading