Skip to content

Commit a0dd1ec

Browse files
authored
Merge pull request #115 from wri/release/blazing-beryl
[RELEASE] Blazing Beryl
2 parents 39cf16a + eb13ef4 commit a0dd1ec

File tree

120 files changed

+10329
-4596
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+10329
-4596
lines changed

.github/workflows/deploy-service.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,12 @@ jobs:
8282
docker build -t terramatch-microservices-base:nx-base .
8383
SERVICE_IMAGE=$ECR_REGISTRY/$ECR_REPOSITORY:${{ env.IMAGE_TAG }}
8484
docker build \
85+
--build-arg SERVICE=${{ inputs.service }} \
8586
--build-arg NODE_ENV=production \
8687
--build-arg DEPLOY_ENV=${{ inputs.env }} \
8788
--build-arg SENTRY_DSN="${{ secrets.SENTRY_DSN }}" \
8889
--build-arg BUILD_FLAG='--prod --verbose --no-cloud' \
89-
-f apps/${{ inputs.service }}/Dockerfile \
90+
-f service.Dockerfile \
9091
-t $SERVICE_IMAGE .
9192
docker push $SERVICE_IMAGE
9293
echo "image=$SERVICE_IMAGE"

.github/workflows/pull-request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
node-version: 20
3535
cache: "npm"
3636

37-
- run: npm ci --legacy-peer-deps
37+
- run: npm ci
3838

3939
- run: npm run lint-build
4040

README.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,53 @@ Repository for the Microservices API backend of the TerraMatch service
2727
- New `NEXT_PUBLIC_<SERVICE>_URL` values are needed for each service you're running locally. This will typically match
2828
the services defined in `V3_NAMESPACES` in `src/generated/v3/utils.ts`.
2929

30+
# CLI
31+
32+
We have a CLI app in this repo. Currently it's responsible for building and launching REPL processes locally and in the cloud.
33+
34+
To build the CLI and make it executable:
35+
36+
- `nx executable tm-v3-cli`
37+
- `(cd dist/tm-v3-cli; npm link)`
38+
39+
The CLI may then be invoked as a direct shell command:
40+
41+
- `tm-v3-cli -h`
42+
43+
The verbose flag will put extra debugging output in the console:
44+
45+
- `tm-v3-cli -v <command and args>`
46+
47+
Development:
48+
49+
- If you're working on active development of the CLI, you can run a build watcher:
50+
- `nx build tm-v3-cli --watch --no-cloud`
51+
- Note: starting this process will regenerate the `dist/tm-v3-cli` directory, which may remove the executable flag on the script.
52+
If that happens, you can re-enable by running `nx exectuable tm-v3-cli` while the build watch command above is running.
53+
54+
# REPL (local and in the cloud)
55+
56+
We utilize the [Nest JS REPL](https://docs.nestjs.com/recipes/repl) to be able to access the code running in a given AWS
57+
environment, and use the same tools for local development.
58+
59+
Start by following the steps above in the CLI section to get the CLI built and running locally.
60+
61+
The command for running the REPL is simply `repl`:
62+
63+
- `tm-v3-cli repl -h`
64+
65+
The service name is required. The environment name is optional and will default to building and running the local REPL for that given service.
66+
67+
If connecting to a remote REPL environment, the [AWS Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html)
68+
is required to be installed on your machine.
69+
70+
As you will note on the NestJS documentation above, the REPL gives you access to all services exposed by your AppModule.
71+
In addition, the `boostrap-repl.ts` utility that is used by all services exposes a couple of things to make life a bit
72+
easier in the REPL env:
73+
74+
- All of lodash accessible through `lodash` (e.g. `lodash.join([1, 2])`)
75+
- All database models are made accessible in the global context (e.g. `await User.findOne({ emailAddress: "[email protected]" })`)
76+
3077
# Deployment
3178

3279
Deployment is handled via manual trigger of GitHub actions. There is one for services, and one for the ApiGateway. The
@@ -48,12 +95,14 @@ and main branches.
4895
import "../../../instrument-sentry";
4996
```
5097
- Add the `SentryModule` and `SentryGlobalFilter` to your main `app.module.ts`. See an existing service for an example.
98+
- Set up REPL access:
99+
- Copy `repl.ts` from an existing service (and modify to specify the new service's name)
100+
- Add the `build-repl` target to `project.json`, which an empty definition.
51101
- In your `.env` and `.env.local.sample`, add `_PORT` for the new service
52102
- In `api-gateway-stack.ts`, add the new service and namespace to `V3_SERVICES`
53103
- In your local web repo, follow directions in `README.md` for setting up a new service.
54104
- This step can be skipped for services that will not be used by the FE website.
55105
- For deployment to AWS:
56-
- Add a Dockerfile in the new app directory. A simple copy and modify from user-service is sufficient
57106
- Add the new service name to the "service" workflow input options in `deploy-service.yml`
58107
- Add a new job to `deploy-services.yml` to include the new services in the "all" service deployment workflow.
59108
- Make sure to update the `check-services` step and follow the pattern for the `if` conditions on the individual service deploy jobs.

apps/entity-service/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"projectType": "application",
66
"tags": [],
77
"targets": {
8+
"build-repl": {},
89
"serve": {
910
"executor": "@nx/js:node",
1011
"defaultConfiguration": "development",

apps/entity-service/src/entities/dto/entity-query.dto.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ApiProperty, IntersectionType } from "@nestjs/swagger";
2-
import { IsEnum, IsOptional, ValidateNested } from "class-validator";
2+
import { IsArray, IsEnum, IsIn, IsInt, IsOptional, Max, Min, ValidateNested } from "class-validator";
33
import { NumberPage } from "@terramatch-microservices/common/dto/page.dto";
4+
import { MAX_PAGE_SIZE, PROCESSABLE_ENTITIES, ProcessableEntity } from "../entities.service";
5+
import { Type } from "class-transformer";
46

57
class QuerySort {
68
@ApiProperty({ name: "sort[field]", required: false })
@@ -13,6 +15,18 @@ class QuerySort {
1315
direction?: "ASC" | "DESC";
1416
}
1517

18+
export class EntitySideload {
19+
@IsIn(PROCESSABLE_ENTITIES)
20+
@ApiProperty({ name: "entity", enum: PROCESSABLE_ENTITIES, description: "Entity type to sideload" })
21+
entity: ProcessableEntity;
22+
23+
@ApiProperty({ name: "pageSize", description: "The page size to include." })
24+
@IsInt()
25+
@Min(1)
26+
@Max(MAX_PAGE_SIZE)
27+
pageSize: number;
28+
}
29+
1630
export class EntityQueryDto extends IntersectionType(QuerySort, NumberPage) {
1731
@ValidateNested()
1832
@IsOptional()
@@ -48,4 +62,23 @@ export class EntityQueryDto extends IntersectionType(QuerySort, NumberPage) {
4862
@ApiProperty({ required: false })
4963
@IsOptional()
5064
projectUuid?: string;
65+
66+
@ApiProperty({ required: false })
67+
@IsOptional()
68+
nurseryUuid?: string;
69+
70+
@ApiProperty({ required: false })
71+
@IsOptional()
72+
siteUuid?: string;
73+
74+
@ApiProperty({
75+
required: false,
76+
description: "If the base entity supports it, this will load the first page of associated entities",
77+
type: [EntitySideload]
78+
})
79+
@IsArray()
80+
@IsOptional()
81+
@Type(() => EntitySideload)
82+
@ValidateNested({ each: true })
83+
sideloads?: EntitySideload[];
5184
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { NurseryReport } from "@terramatch-microservices/database/entities";
2+
import { EntityDto, AdditionalProps } from "./entity.dto";
3+
import { pickApiProperties } from "@terramatch-microservices/common/dto/json-api-attributes";
4+
import { JsonApiDto } from "@terramatch-microservices/common/decorators/json-api-dto.decorator";
5+
import { ApiProperty } from "@nestjs/swagger";
6+
import { MediaDto } from "./media.dto";
7+
8+
@JsonApiDto({ type: "nurseryReports" })
9+
export class NurseryReportLightDto extends EntityDto {
10+
constructor(nurseryReport?: NurseryReport, props?: AdditionalNurseryReportLightProps) {
11+
super();
12+
if (nurseryReport != null) {
13+
this.populate(NurseryReportLightDto, {
14+
...pickApiProperties(nurseryReport, NurseryReportLightDto),
15+
lightResource: true,
16+
// these two are untyped and marked optional in the base model.
17+
createdAt: nurseryReport.createdAt as Date,
18+
updatedAt: nurseryReport.updatedAt as Date,
19+
...props
20+
});
21+
}
22+
}
23+
24+
@ApiProperty({
25+
nullable: true,
26+
description: "The associated nursery name"
27+
})
28+
nurseryName: string | null;
29+
30+
@ApiProperty({
31+
nullable: true,
32+
description: "The associated nursery uuid"
33+
})
34+
nurseryUuid: string | null;
35+
36+
@ApiProperty()
37+
frameworkKey: string | null;
38+
39+
@ApiProperty()
40+
frameworkUuid: string | null;
41+
42+
@ApiProperty()
43+
status: string;
44+
45+
@ApiProperty()
46+
updateRequestStatus: string;
47+
48+
@ApiProperty({
49+
nullable: true,
50+
description: "The associated project name"
51+
})
52+
projectName: string | null;
53+
54+
@ApiProperty({
55+
nullable: true,
56+
description: "The associated project uuid"
57+
})
58+
projectUuid: string | null;
59+
60+
@ApiProperty({
61+
nullable: true,
62+
description: "The associated organisation name"
63+
})
64+
organisationName: string | null;
65+
66+
@ApiProperty({
67+
nullable: true,
68+
description: "The associated organisation uuid"
69+
})
70+
organisationUuid: string | null;
71+
72+
@ApiProperty()
73+
updatedAt: Date;
74+
75+
@ApiProperty({ nullable: true })
76+
submittedAt: Date | null;
77+
78+
@ApiProperty({ nullable: true })
79+
taskUuid: string | null;
80+
81+
@ApiProperty()
82+
dueAt: Date | null;
83+
84+
@ApiProperty({ nullable: true })
85+
title: string | null;
86+
87+
@ApiProperty({ nullable: true })
88+
reportTitle: string | null;
89+
90+
@ApiProperty()
91+
createdAt: Date;
92+
}
93+
94+
export type AdditionalNurseryReportLightProps = Pick<NurseryReportLightDto, "reportTitle">;
95+
export type AdditionalNurseryReportFullProps = AdditionalNurseryReportLightProps &
96+
AdditionalProps<NurseryReportFullDto, NurseryReportLightDto & Omit<NurseryReport, "nursery">>;
97+
export type NurseryReportMedia = Pick<NurseryReportFullDto, keyof typeof NurseryReport.MEDIA>;
98+
99+
export class NurseryReportFullDto extends NurseryReportLightDto {
100+
constructor(nurseryReport: NurseryReport, props?: AdditionalNurseryReportFullProps) {
101+
super();
102+
if (nurseryReport != null) {
103+
this.populate(NurseryReportFullDto, {
104+
...pickApiProperties(nurseryReport, NurseryReportFullDto),
105+
lightResource: false,
106+
// these two are untyped and marked optional in the base model.
107+
createdAt: nurseryReport.createdAt as Date,
108+
updatedAt: nurseryReport.updatedAt as Date,
109+
...props
110+
});
111+
}
112+
}
113+
114+
@ApiProperty({ nullable: true })
115+
reportTitle: string | null;
116+
117+
@ApiProperty({ nullable: true })
118+
projectReportTitle: string | null;
119+
120+
@ApiProperty({
121+
nullable: true,
122+
description: "The associated nursery name"
123+
})
124+
nurseryName: string | null;
125+
126+
@ApiProperty({
127+
nullable: true,
128+
description: "The associated nursery uuid"
129+
})
130+
nurseryUuid: string | null;
131+
132+
@ApiProperty({
133+
nullable: true,
134+
description: "The associated organisation name"
135+
})
136+
organisationName: string | null;
137+
138+
@ApiProperty({
139+
nullable: true,
140+
description: "The associated organisation uuid"
141+
})
142+
organisationUuid: string | null;
143+
144+
@ApiProperty()
145+
dueAt: Date | null;
146+
147+
@ApiProperty()
148+
status: string;
149+
150+
@ApiProperty()
151+
updateRequestStatus: string;
152+
153+
@ApiProperty({ nullable: true })
154+
feedback: string | null;
155+
156+
@ApiProperty({ nullable: true })
157+
feedbackFields: string[] | null;
158+
159+
@ApiProperty()
160+
nothingToReport: boolean;
161+
162+
@ApiProperty({ nullable: true })
163+
completion: number | null;
164+
165+
@ApiProperty({ nullable: true })
166+
title: string | null;
167+
168+
@ApiProperty({ nullable: true })
169+
seedlingsYoungTrees: number | null;
170+
171+
@ApiProperty({ nullable: true })
172+
interestingFacts: string | null;
173+
174+
@ApiProperty({ nullable: true })
175+
sitePrep: string | null;
176+
177+
@ApiProperty({ nullable: true })
178+
sharedDriveLink: string | null;
179+
180+
@ApiProperty({ nullable: true })
181+
createdByFirstName: string | null;
182+
183+
@ApiProperty({ nullable: true })
184+
createdByLastName: string | null;
185+
186+
@ApiProperty({ nullable: true })
187+
approvedByFirstName: string | null;
188+
189+
@ApiProperty({ nullable: true })
190+
approvedByLastName: string | null;
191+
192+
@ApiProperty({
193+
nullable: true,
194+
description: "The associated project name"
195+
})
196+
projectName: string | null;
197+
198+
@ApiProperty({
199+
nullable: true,
200+
description: "The associated project uuid"
201+
})
202+
projectUuid: string | null;
203+
204+
@ApiProperty({
205+
nullable: true,
206+
description: "The associated task uuid"
207+
})
208+
taskUuid: string | null;
209+
210+
@ApiProperty({ nullable: true })
211+
submittedAt: Date | null;
212+
213+
@ApiProperty()
214+
migrated: boolean;
215+
216+
@ApiProperty()
217+
createdAt: Date;
218+
219+
@ApiProperty()
220+
updatedAt: Date;
221+
222+
@ApiProperty({ type: () => MediaDto, isArray: true })
223+
file: MediaDto[];
224+
225+
@ApiProperty({ type: () => MediaDto, isArray: true })
226+
otherAdditionalDocuments: MediaDto[];
227+
228+
@ApiProperty({ type: () => MediaDto, isArray: true })
229+
treeSeedlingContributions: MediaDto[];
230+
231+
@ApiProperty({ type: () => MediaDto, isArray: true })
232+
photos: MediaDto[];
233+
}

0 commit comments

Comments
 (0)