Skip to content

Commit

Permalink
Merge pull request #63 from beabee-communityrm/feat/contact-tagging
Browse files Browse the repository at this point in the history
Feat(contact-tagging): Add support tagable contacts with bulk action
  • Loading branch information
JumpLink authored Nov 15, 2024
2 parents e256023 + 418f3c3 commit 6aad62f
Show file tree
Hide file tree
Showing 108 changed files with 2,798 additions and 916 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ The Beabee Monorepo is organized into the following directories:

## Development Setup

> ⚠️⚠️⚠️ **WARNING** ⚠️⚠️⚠️
>
> If you want to deploy beabee on a server refer to
> [beabee/beabee-deploy](https://github.com/beabee-communityrm/beabee-deploy/)
> instead. The instructions below are for running beabee locally for development
To get started with local development, follow these steps:

### Prerequisites
Expand Down
38 changes: 9 additions & 29 deletions apps/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ Your input is invaluable to us as we continue to grow and improve beabee. Don't

## 💻 Install

> ⚠️⚠️⚠️ **WARNING** ⚠️⚠️⚠️
>
> If you want to deploy beabee on a server refer to
> [beabee/beabee-deploy](https://github.com/beabee-communityrm/beabee-deploy/)
> instead. The instructions below are for running beabee locally for development
You need:

- Docker >= 19.03.8
Expand Down Expand Up @@ -90,16 +84,12 @@ If you make changes to `.env` you need to recreate the Docker containers
docker compose -f ../../docker-compose.yml up -d
```

docker compose -f ../../docker-compose.yml up -d

````
If you change the dependencies in `package.json` you must rebuild and recreate the Docker containers

```bash
docker compose -f ../../docker-compose.yml build
docker compose -f ../../docker-compose.yml up -d
````
```

#### Generating database migrations

Expand All @@ -108,16 +98,16 @@ file. TypeORM will automatically generate a migration file based on your schema
changes

```bash
docker compose -f ../../docker-compose.yml start db
docker compose -f ../../docker-compose.yml run app yarn typeorm migration:generate src/migrations/MigrationName && yarn format
yarn build
docker compose -f ../../docker-compose.yml run app yarn typeorm migration:run
yarn typeorm:generate /opt/packages/core/src/migrations/<MigrationName> # The migration file is generated inside the docker container
yarn format # Formats the migration file
yarn build:core # Necessary for the new migration .js files to be found
yarn typeorm:migrate # Runs the migration
```

If you are still in the development phase, you may want to undo your last database migration as follows:

```bash
docker compose -f ../../docker-compose.yml run app yarn typeorm migration:revert
yarn typeorm:revert
```

To find out more about this topic, take a look at the [TypeORM Migration Guide](https://typeorm.io/migrations).
Expand All @@ -128,12 +118,10 @@ The codebase is broadly split into a few different parts

- **beabee core**

Shared between all services (API, webhooks and legacy)
The remaining files that have not yet been moved to the `@beabee/core` package, contains shared business logic between multiple services (API, webhooks and legacy)

```
./src/core
./src/models - Data models and database entities
./src/config - Config loader
```

- **API service**
Expand Down Expand Up @@ -168,14 +156,6 @@ Various tools for administration, including nightly cron jobs
./src/tools
```

- **Database migrations**

Autogenerated by TypeORM

```
./src/migrations
```

#### 📡 Webhooks

Webhooks are handled by the `webhook_app` service. This is a separate service from the API to allow for scaling independently.
Expand All @@ -195,7 +175,7 @@ Webhooks are handled by the `webhook_app` service. This is a separate service fr
By default we are using [MailDev](https://github.com/maildev/maildev) for local development. For this to work it must be configured the first time, run the following command if not already done:

```bash
docker compose -f ../../docker-compose.yml exec app node built/tools/configure
yarn setup:payment
```

If the Docker Compose Stack is started, you can reach MailDev via http://localhost:3025/ by default. If you now receive an e-mail during your tests, you will find it there.
Expand Down Expand Up @@ -224,7 +204,7 @@ BEABEE_STRIPE_SECRETKEY=<secret key>
And also that you have configured the payment methods using

```bash
docker compose -f ../../docker-compose.yml exec app node built/tools/configure
yarn setup:payment
```

You can get the public key and secret key in the [Stripe dashboard](https://dashboard.stripe.com).
Expand Down
11 changes: 9 additions & 2 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,22 @@
"dev:api:watch": "tsc-watch -p tsconfig.build.json --noClear --onSuccess 'docker compose -f ../../docker-compose.yml restart api_app router'",
"dev:api:logs": "yarn exec bash -c 'while true; do docker compose -f ../../docker-compose.yml logs -f --tail=0 api_app; sleep 1; done'",
"build": "rimraf built/ && gulp build && tsc -p ./tsconfig.build.json",
"build:core": "yarn workspace @beabee/core run build",
"generate:index": "ts-node scripts/generate-index.ts",
"watch": "tsc-watch -p tsconfig.build.json --noClear",
"check": "concurrently 'yarn:check:*'",
"check:tsc": "tsc --noEmit",
"check:prettier": "prettier -c .",
"check:dependencies": "dpdm -T --no-warning --no-tree --exit-code circular:1 src/app.ts src/webhooks/app.ts src/api/app.ts",
"format": "prettier --write .",
"setup": "yarn setup:db && yarn setup:user && yarn setup:payment",
"setup:user": "docker compose run --rm app node built/tools/new-user",
"setup:payment": "docker compose exec app node built/tools/configure",
"setup:db": "yarn typeorm:migrate",
"typeorm": "typeorm -d ./built/core/lib/typeorm.js",
"typeorm:migrate": "docker compose -f ../../docker-compose.yml up -d db && docker compose -f ../../docker-compose.yml run --rm app yarn typeorm migration:run"
"typeorm:migrate": "docker compose -f ../../docker-compose.yml up -d db app && docker compose -f ../../docker-compose.yml run --rm app npm run typeorm migration:run && yarn build:core",
"typeorm:revert": "docker compose -f ../../docker-compose.yml up -d db app && docker compose -f ../../docker-compose.yml run --rm app npm run typeorm migration:revert",
"typeorm:generate": "docker compose -f ../../docker-compose.yml up -d db app && docker compose -f ../../docker-compose.yml run --rm app npm run typeorm migration:generate --check"
},
"dependencies": {
"@beabee/beabee-common": "workspace:^",
Expand Down Expand Up @@ -139,4 +146,4 @@
"postcss": "8.4.32"
}
}
}
}
39 changes: 3 additions & 36 deletions apps/backend/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,7 @@ import {
useExpressServer
} from "routing-controllers";

import { ApiKeyController } from "./controllers/ApiKeyController";
import { AuthController } from "./controllers/AuthController";
import { CalloutController } from "./controllers/CalloutController";
import { CalloutResponseController } from "./controllers/CalloutResponseController";
import { CalloutResponseCommentController } from "./controllers/CalloutResponseCommentController";
import { ContentController } from "./controllers/ContentController";
import { EmailController } from "./controllers/EmailController";
import { ContactController } from "./controllers/ContactController";
import { NoticeController } from "./controllers/NoticeController";
import { PaymentController } from "./controllers/PaymentController";
import { SegmentController } from "./controllers/SegmentController";
import { SignupController } from "./controllers/SignupController";
import { StatsController } from "./controllers/StatsController";
import { ResetPasswordController } from "./controllers/ResetPasswordController";
import { ResetDeviceController } from "./controllers/ResetDeviceController";
import { RootController } from "./controllers/RootController";
import { UploadController } from "./controllers/UploadController";
import * as Controllers from "./controllers/index";

import { ValidateResponseInterceptor } from "./interceptors/ValidateResponseInterceptor";

Expand Down Expand Up @@ -71,25 +55,7 @@ initApp()

useExpressServer(app, {
routePrefix: "/1.0",
controllers: [
ApiKeyController,
AuthController,
CalloutController,
CalloutResponseController,
CalloutResponseCommentController,
ContentController,
EmailController,
ContactController,
NoticeController,
PaymentController,
SegmentController,
SignupController,
StatsController,
ResetPasswordController,
ResetDeviceController,
RootController,
UploadController
],
controllers: Object.values(Controllers),
interceptors: [ValidateResponseInterceptor],
middlewares: [AuthMiddleware],
currentUserChecker,
Expand Down Expand Up @@ -126,6 +92,7 @@ initApp()
log.notice("Bad request, probably a validation error", {
body: req.body,
query: req.query,
stack: error.stack,
error
});
}
Expand Down
32 changes: 16 additions & 16 deletions apps/backend/src/api/controllers/CalloutController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { CalloutId } from "@api/decorators/CalloutId";
import { CurrentAuth } from "@api/decorators/CurrentAuth";
import PartialBody from "@api/decorators/PartialBody";
import { InvalidCalloutResponse, UnauthorizedError } from "@beabee/core/errors";
import CalloutTagTransformer from "@api/transformers/CalloutTagTransformer";
import { calloutTagTransformer } from "@api/transformers/TagTransformer";
import CalloutTransformer from "@api/transformers/CalloutTransformer";
import CalloutResponseExporter from "@api/transformers/CalloutResponseExporter";
import CalloutResponseMapTransformer from "@api/transformers/CalloutResponseMapTransformer";
Expand All @@ -59,6 +59,7 @@ import {
import { CalloutCaptcha } from "@beabee/beabee-common";

import { AuthInfo } from "@type/auth-info";
import { ListTagsDto } from "@api/dto";

@JsonController("/callout")
export class CalloutController {
Expand Down Expand Up @@ -207,13 +208,16 @@ export class CalloutController {
}
}

// TODO: move to CalloutTagController like we did for contact tags?
@Authorized("admin")
@Get("/:id/tags")
async getCalloutTags(
@CurrentAuth({ required: true }) auth: AuthInfo,
@CalloutId() id: string
@CalloutId() id: string,
@QueryParams() query: ListTagsDto
): Promise<GetCalloutTagDto[]> {
const result = await CalloutTagTransformer.fetch(auth, {
const result = await calloutTagTransformer.fetch(auth, {
...query,
rules: {
condition: "AND",
rules: [{ field: "calloutId", operator: "equal", value: [id] }]
Expand All @@ -223,6 +227,7 @@ export class CalloutController {
return result.items;
}

// TODO: move to CalloutTagController like we did for contact tags?
@Authorized("admin")
@Post("/:id/tags")
async createCalloutTag(
Expand All @@ -235,46 +240,41 @@ export class CalloutController {
description: data.description,
calloutId: id
});

return CalloutTagTransformer.convert(tag);
return calloutTagTransformer.convert(tag);
}

// TODO: move to CalloutTagController like we did for contact tags?
@Authorized("admin")
@Get("/:id/tags/:tagId")
async getCalloutTag(
@CurrentAuth({ required: true }) auth: AuthInfo,
@Param("tagId") tagId: string
): Promise<GetCalloutTagDto | undefined> {
return CalloutTagTransformer.fetchOneById(auth, tagId);
return calloutTagTransformer.fetchOneById(auth, tagId);
}

// TODO: move to CalloutTagController like we did for contact tags?
@Authorized("admin")
@Patch("/:id/tags/:tagId")
async updateCalloutTag(
@CurrentAuth({ required: true }) auth: AuthInfo,
@CalloutId() id: string,
@Param("tagId") tagId: string,
@PartialBody() data: CreateCalloutTagDto // Partial<CreateCalloutTagData>
@PartialBody() data: CreateCalloutTagDto // Partial<TagCreateData>
): Promise<GetCalloutTagDto | undefined> {
await getRepository(CalloutTag).update({ id: tagId, calloutId: id }, data);

return CalloutTagTransformer.fetchOneById(auth, tagId);
return calloutTagTransformer.fetchOneById(auth, tagId);
}

// TODO: move to CalloutTagController like we did for contact tags?
@Authorized("admin")
@Delete("/:id/tags/:tagId")
@OnUndefined(204)
async deleteCalloutTag(
@CalloutId() id: string,
@Param("tagId") tagId: string
): Promise<void> {
await getRepository(CalloutResponseTag).delete({ tag: { id: tagId } });
const result = await getRepository(CalloutTag).delete({
calloutId: id,
id: tagId
});
if (result.affected === 0) {
throw new NotFoundError();
}
await calloutTagTransformer.delete(tagId, CalloutResponseTag);
}
}
4 changes: 2 additions & 2 deletions apps/backend/src/api/controllers/CalloutResponseController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { UUIDParams } from "@api/params/UUIDParams";
import {
BatchUpdateCalloutResponseDto,
BatchUpdateCalloutResponseResultDto,
CreateCalloutResponseDto,
UpdateCalloutResponseDto,
GetCalloutResponseDto,
GetCalloutResponseOptsDto,
ListCalloutResponsesDto
Expand Down Expand Up @@ -58,7 +58,7 @@ export class CalloutResponseController {
async updateCalloutResponse(
@CurrentAuth({ required: true }) auth: AuthInfo,
@Params() { id }: UUIDParams,
@PartialBody() data: CreateCalloutResponseDto // Should be Partial<CreateCalloutResponseData>
@PartialBody() data: UpdateCalloutResponseDto
): Promise<GetCalloutResponseDto | undefined> {
await CalloutResponseTransformer.updateOneById(auth, id, data);
return await CalloutResponseTransformer.fetchOneById(auth, id);
Expand Down
Loading

0 comments on commit 6aad62f

Please sign in to comment.