Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
_PLACEHOLDER FILE_
i file in src/features/db/class sono deprecati. tutti i punti che li usano dovrebbero invece usare knex (tramite tryber in src/features/database.ts).
51 changes: 51 additions & 0 deletions .project-management/current-prd/prd-db-class-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## 1. Introduction/Overview

The project currently has several deprecated classes in `src/features/db/class` that are still being used by parts of the codebase. The goal is to replace these classes with the Knex-based implementation provided by `tryber` in `src/features/database.ts`. The replacement should be one-to-one with no API changes and no behavioral regressions.

## 2. Goals

- Remove all dependencies on code in `src/features/db/class`.
- Migrate all database queries to use the `tryber` Knex instance from `src/features/database.ts`.
- Ensure the test suite passes after migration.

## 3. User Stories

1. **As a developer**, I want the database access layer to use a single Knex-based implementation so that the codebase is easier to maintain.
2. **As a developer**, I need the migration to avoid any API or behavior changes so that existing integrations continue to work.

## 4. Functional Requirements

1. Identify all modules and endpoints that import or instantiate classes from `src/features/db/class`.
2. Refactor these modules to use Knex queries via `src/features/database.ts` instead.
3. Ensure that all database interactions preserve existing data structures and behavior.
4. Update or create tests where needed so the entire test suite passes.

## 5. Non-Goals (Out of Scope)

- Changing API endpoints or response formats.
- Adding new features unrelated to database access.
- Modifying database schemas or introducing migrations.

## 6. Design Considerations

- Keep code changes localized to maintain readability.
- Favor small, incremental refactors with comprehensive tests.

## 7. Technical Considerations

- `@appquality/tryber-database` provides the Knex-based connection and should remain up to date.
- Existing environment configuration in `src/features/database.ts` must be reused for all queries.

## 8. Success Metrics

- All previous unit and integration tests pass without modification (unless tests rely on deprecated classes).
- No regressions reported after deployment.

## 9. Open Questions

- Are there modules outside of `src/features` that depend on the deprecated classes?
- Do we need additional logging to monitor for unexpected behavior during migration?

## 10. Referenced PRD-background files

- `.project-management/current-prd/prd-background/feature-specification.md` – states the goal of replacing deprecated classes in `src/features/db/class` with the `tryber` Knex implementation.
1 change: 0 additions & 1 deletion .project-management/current-prd/prd-feature2.md

This file was deleted.

91 changes: 91 additions & 0 deletions .project-management/current-prd/tasks-prd-db-class-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## Pre-Feature Development Project Tree

```
.
deployment
keys
src
src/__mocks__
src/features
src/middleware
src/reference
src/routes
features
features/OpenapiError
features/__mocks__
features/busboyMapper
features/checkUrl
features/class
features/db
features/debugMessage
features/deleteFromS3
features/escapeCharacters
features/getMimetypeFromS3
features/jotform
features/leaderboard
features/mail
features/paypal
features/routes
features/s3
features/sentry
features/sqlite
features/tranferwise
features/upload
features/webhookTrigger
features/wp
```

## Relevant Files

- `src/features/db/class/*` - Deprecated database classes to be replaced
- `src/features/database.ts` - Knex connection instance (`tryber`)
- Various modules under `src/` importing from `src/features/db/class`
- `.project-management/current-prd/tasks-prd-db-class-migration.md` - Task list for DB class migration

### Proposed New Files

- _None at this stage_

### Existing Files Modified

- `src/routes/campaigns/forms/_get/index.ts` - migrated to use `tryber` instead of db class
- `src/routes/campaigns/campaignId/forms/_get/index.ts` - migrated to use `tryber`
- `src/routes/users/me/campaigns/campaignId/compatible_devices/_get/index.ts` - migrated to use `tryber`
- Modules in `src/features` and `src/routes` that rely on deprecated classes
- Test files associated with the above modules

### Notes

- Keep `@appquality/tryber-database` updated in `package.json`
- Ensure no API or behavior changes during migration

## Tasks

- [x] 1.0 Inventory current usage of classes from `src/features/db/class`
- [ ] 2.0 Refactor identified modules to use `tryber` queries via `src/features/database.ts`
- [x] 2.1 Refactor `src/routes/campaigns/forms/_get/index.ts` to use `tryber`
- [x] 2.2 Refactor `src/routes/campaigns/campaignId/forms/_get/index.ts` to use `tryber`
- [x] 2.3 Refactor `src/routes/users/me/campaigns/campaignId/compatible_devices/_get/index.ts` to use `tryber`
- [ ] 3.0 Update or create tests to cover the refactored code
- [x] 3.1 Update tests for `src/routes/campaigns/campaignId/forms/_get/index.ts`
- [ ] 4.0 Remove deprecated classes once all references are migrated
- [ ] 5.0 Run full test suite and verify no regressions

### Task 1.0 Inventory Results

- `src/routes/campaigns/forms/_get/index.ts`
- `src/routes/campaigns/campaignId/forms/_get/index.ts`
- `src/routes/campaigns/campaignId/candidates/_post/index.ts`
- `src/routes/users/me/campaigns/campaignId/compatible_devices/_get/index.ts`
- `src/routes/users/me/campaigns/campaignId/forms/_get/index.ts`
- `src/routes/users/me/campaigns/campaignId/forms/_post/index.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/index.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/index.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/AddressQuestion.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/CufMultiSelectQuestion.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/CufSelectableQuestion.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/CufTextQuestion.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/GenderQuestion.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/PhoneQuestion.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/SelectableQuestion.ts`
- `src/routes/users/me/campaigns/campaignId/forms/QuestionFactory/Questions/SimpleTextQuestion.ts`
1 change: 0 additions & 1 deletion .project-management/current-prd/tasks-prd-feature2.md

This file was deleted.

1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ npx prettier --ignore-unknown --write

## Testing Instructions

Run the tests only when a ts, js or json file is changed. NEVER run if only md files are changed.
Run tests with `yarn test`. Any tests that require network connectivity should either be ignored and not run, -or- have network test path that shunts to a success when network connectivity can't be demonstrated so failed tests in this scenario don't confuse the codex agent progress.

## CHANGELOG.md Instructions
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
2025-06-05 - add PRD for database class migration
2025-06-05 - add high-level tasks for db class migration PRD
2025-06-05 - refactor campaigns forms route to use tryber
2025-06-05 - migrated campaignId forms route to use tryber
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@appquality/tryber-database": "^0.44.6",
"@appquality/tryber-database": "0.44.17",
"@appquality/wp-auth": "^1.0.7",
"@googlemaps/google-maps-services-js": "^3.3.7",
"@sendgrid/mail": "^7.6.0",
Expand Down
41 changes: 19 additions & 22 deletions src/routes/campaigns/campaignId/forms/_get/index.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
/** OPENAPI-CLASS: get-campaigns-campaign-forms */
import UserRoute from "@src/features/routes/UserRoute";
import PreselectionFormFields, {
PreselectionFormFieldsObject,
} from "@src/features/db/class/PreselectionFormFields";
import PreselectionForm from "@src/features/db/class/PreselectionForms";
import { tryber } from "@src/features/database";
import OpenapiError from "@src/features/OpenapiError";

export default class RouteItem extends UserRoute<{
response: StoplightOperations["get-campaigns-campaign-forms"]["responses"][200]["content"]["application/json"];
parameters: StoplightOperations["get-campaigns-campaign-forms"]["parameters"]["path"];
}> {
private db: { questions: PreselectionFormFields; form_id: PreselectionForm };
private campaign_id: number;
private form_id: number = 0;
private questions: PreselectionFormFieldsObject[] | false = false;
private questions:
| {
id: number;
question: string;
short_name: string | null;
}[]
| false = false;

constructor(config: RouteClassConfiguration) {
super(config);
const parameters = this.getParameters();
this.campaign_id = parseInt(parameters.campaign);
this.db = {
questions: new PreselectionFormFields(["id", "question", "short_name"]),
form_id: new PreselectionForm(["id"]),
};
}
protected async init() {
const forms = await this.db.form_id.query({
where: [{ campaign_id: this.campaign_id }],
});
if (forms.length === 0) {
this.form_id = 0;
} else {
this.form_id = forms[0].id;
}
const form = await tryber.tables.WpAppqCampaignPreselectionForm.do()
.select("id")
.where("campaign_id", this.campaign_id)
.first();

this.questions = await this.db.questions.query({
where: [{ form_id: this.form_id }],
orderBy: [{ field: "priority" }],
});
this.form_id = form ? form.id : 0;

this.questions =
await tryber.tables.WpAppqCampaignPreselectionFormFields.do()
.select("id", "question", "short_name")
.where("form_id", this.form_id)
.orderBy("priority");
if (this.questions.length === 0) {
this.questions = false;
}
Expand Down
3 changes: 2 additions & 1 deletion src/routes/campaigns/forms/_get/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import preselectionForm from "@src/__mocks__/mockedDb/preselectionForm";
import app from "@src/app";
import preselectionForm from "@src/__mocks__/mockedDb/preselectionForm";
import request from "supertest";
describe("GET /campaigns/forms ", () => {
beforeAll(async () => {
Expand Down Expand Up @@ -244,6 +244,7 @@ describe("GET /campaigns/forms ", () => {
"authorization",
`Bearer tester capability ["manage_preselection_forms"]`
);
console.log(response.body);
expect(response.body.results[0]).toMatchObject({
id: 3,
name: "Form Name3 with campaign Id",
Expand Down
64 changes: 36 additions & 28 deletions src/routes/campaigns/forms/_get/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
/** OPENAPI-CLASS: get-campaigns-forms */

import { tryber } from "@src/features/database";
import UserRoute from "@src/features/routes/UserRoute";
import PreselectionForms from "@src/features/db/class/PreselectionForms";

export default class RouteItem extends UserRoute<{
response: StoplightOperations["get-campaigns-forms"]["responses"][200]["content"]["application/json"];
query: StoplightOperations["get-campaigns-forms"]["parameters"]["query"];
}> {
private db: { forms: PreselectionForms };
private limit: number | undefined;
private start: number;
private searchBy: ("name" | "campaign_id")[] | undefined;
private search: string | undefined;

constructor(config: RouteClassConfiguration) {
super(config);
this.db = { forms: new PreselectionForms(["id", "name", "campaign_id"]) };
const query = this.getQuery();
this.limit = parseInt(query.limit as unknown as string) || undefined;
this.start = parseInt((query.start as unknown as string) || "0");
Expand Down Expand Up @@ -51,37 +49,47 @@ export default class RouteItem extends UserRoute<{
}

private async getForms() {
const results = await this.db.forms.query({
limit: this.limit,
where: this.getWhere(),
orderBy: [{ field: "id", order: "DESC" }],
offset: this.start,
});
return results.map((form) => {
return {
id: form.id,
name: form.name,
campaign: form.campaign_id !== null ? form.campaign_id : undefined,
};
});
const query = tryber.tables.WpAppqCampaignPreselectionForm.do()
.select("id", "name", "campaign_id")
.orderBy("id", "DESC")
.offset(this.start);

if (this.limit) query.limit(this.limit);
this.applySearch(query);

const results = await query;
return results.map((form) => ({
id: form.id,
name: form.name,
campaign: form.campaign_id !== null ? form.campaign_id : undefined,
}));
}

private async getTotal() {
const results = await this.db.forms.query({ where: this.getWhere() });
return this.limit ? results.length : undefined;
if (this.limit === undefined) return undefined;
const query = tryber.tables.WpAppqCampaignPreselectionForm.do().count({
count: "id",
});
this.applySearch(query);
const result = await query;
const total = result[0].count as number | string;
return typeof total === "number" ? total : parseInt(total);
}

private getWhere() {
if (!this.searchBy || !this.search) return undefined;
const searchFields = this.searchBy;
private applySearch<
T extends ReturnType<
ReturnType<
typeof tryber.tables.WpAppqCampaignPreselectionForm.do
>["count"]
>
>(query: T) {
if (!this.searchBy || !this.search) return;
const search = this.search;
const orQuery: PreselectionForms["where"][number] = searchFields.map(
(searchField) => {
return { [searchField]: "%" + search + "%", isLike: true };
}
);

return [orQuery];
query.where((builder) => {
this.searchBy!.forEach((field) => {
builder.orWhereLike(field, `%${search}%`);
});
});
}
private isSearchByAcceptable(searchField: string) {
return ["name", "campaign_id"].includes(searchField);
Expand Down
Loading
Loading