Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { type Knex } from "knex";

const TABLE_NAME = "page_event";
const ATTEMPT_COLUMN = "attempt";
const DEFAULT_ATTEMPT = 0;
const ONCE_INDEX = "page_event_once";

async function down(knex: Knex): Promise<void> {
await knex.raw(`
DROP INDEX IF EXISTS ${ONCE_INDEX};
`);

await knex.schema.alterTable(TABLE_NAME, (table) => {
table.dropColumn(ATTEMPT_COLUMN);
});

await knex.raw(`
CREATE UNIQUE INDEX ${ONCE_INDEX}
ON ${TABLE_NAME} (page_id, transcription_id, event)
WHERE event IN ('confirm', 'correct', 'skip');
`);
}
Comment thread
MatiStb marked this conversation as resolved.

async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TABLE_NAME, (table) => {
table.integer(ATTEMPT_COLUMN).notNullable().defaultTo(DEFAULT_ATTEMPT);
});

await knex.raw(`
DROP INDEX ${ONCE_INDEX};

CREATE UNIQUE INDEX ${ONCE_INDEX}
ON ${TABLE_NAME} (page_id, transcription_id, event, attempt)
WHERE event IN ('confirm', 'correct', 'skip');
`);
}

export { down, up };
12 changes: 12 additions & 0 deletions apps/backend/src/modules/documents/document.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,18 @@ class DocumentRepository {
.execute();
}

public async setCursorPageNo(
documentId: number,
cursorPageNo: number,
trx: Transaction,
): Promise<void> {
await this.documentModel
.query(trx)
.patch({ cursorPageNo })
.where({ id: documentId })
.execute();
}

public async setError(id: number, errorMessage: string): Promise<void> {
await this.documentModel
.query()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const PageEventName = {
TRANSCRIBE_FAILED: "transcribe_failed",
UNDO: "undo",
} as const;

export { PageEventName };
2 changes: 2 additions & 0 deletions apps/backend/src/modules/pages/libs/constants/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { CLOSED_PAGE_STATUSES } from "./closed-page-statuses.constant.js";
export { PAGE_EVENT_ATTEMPT_INCREMENT } from "./page-event-attempt-increment.constant.js";
export { NUMBER_OF_PAGES_TO_INCREMENT } from "./page-number-increment.constant.js";
export { REPROCESSABLE_PAGE_STATUSES } from "./reprocessable-page-statuses.constant.js";
export { UNDOABLE_PAGE_STATUSES } from "./undoable-page-statuses.constant.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const PAGE_EVENT_ATTEMPT_INCREMENT = 1;

export { PAGE_EVENT_ATTEMPT_INCREMENT };
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PageStatus, type PageStatusValue } from "@transcripta/shared";

const UNDOABLE_PAGE_STATUSES: ReadonlySet<PageStatusValue> = new Set([
PageStatus.CONFIRMED,
PageStatus.CORRECTED,
PageStatus.SKIPPED,
]);

export { UNDOABLE_PAGE_STATUSES };
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const PageErrorMessage = {
PAGE_NOT_FOUND: "Page not found",
PAGE_NOT_REPROCESSABLE: "Page cannot be reprocessed",
PAGE_NOT_VERIFIED: "Page is not verified",
REPROCESS_FAILED: "Failed to reprocess page",
TEXT_REQUIRED_FOR_CORRECTION: "Text is required for correction",
TRANSCRIPTION_NOT_FOUND: "Transcription is no longer current",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type PageVerificationActionValue } from "@transcripta/shared";

type CreatePageEventPayload = {
actorId: number;
attempt: number;
documentId: number;
durationMs: number;
event: PageVerificationActionValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type CreateUndoPageEventPayload = {
actorId: number;
attempt: number;
documentId: number;
pageId: number;
transcriptionId: number;
};

export { type CreateUndoPageEventPayload };
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type PageVerificationActionValue } from "@transcripta/shared";

type FindVerificationEventPayload = {
attempt: number;
event: PageVerificationActionValue;
pageId: number;
transcriptionId: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { type CreatePageEventPayload } from "./page-event-create-payload.type.js";
export { type CreateUndoPageEventPayload } from "./page-event-create-undo-payload.type.js";
export { type FindVerificationEventPayload } from "./page-event-find-verification-event.type.js";
2 changes: 2 additions & 0 deletions apps/backend/src/modules/pages/page-event/page-event.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
class PageEventModel extends AbstractModel {
public actorId!: null | number;

public attempt!: number;

public details!: Record<string, unknown>;

public documentId!: number;
Expand Down
33 changes: 33 additions & 0 deletions apps/backend/src/modules/pages/page-event/page-event.repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { type Transaction } from "objection";

import { PageEventName } from "~/modules/jobs/libs/enums/enums.js";

const INITIAL_PAGE_EVENT_ATTEMPT = 0;

import {
type CreatePageEventPayload,
type CreateUndoPageEventPayload,
type FindVerificationEventPayload,
} from "./libs/types/types.js";
import { type PageEventModel } from "./page-event.model.js";
Expand All @@ -13,6 +18,20 @@ class PageEventRepository {
this.pageEventModel = pageEventModel;
}

public async createUndoEvent(
payload: CreateUndoPageEventPayload,
trx?: Transaction,
): Promise<void> {
await this.pageEventModel
.query(trx)
.insert({
...payload,
details: {},
event: PageEventName.UNDO,
})
.execute();
}

public async createVerificationEvent(
payload: CreatePageEventPayload,
trx?: Transaction,
Expand All @@ -26,6 +45,20 @@ class PageEventRepository {
.execute();
}

public async findLatestAttempt(
pageId: number,
trx?: Transaction,
): Promise<number> {
const event = await this.pageEventModel
.query(trx)
.where({ pageId })
.orderBy("attempt", "desc")
.first()
.execute();

return event?.attempt ?? INITIAL_PAGE_EVENT_ATTEMPT;
}

public async findVerificationEvent(
payload: FindVerificationEventPayload,
trx?: Transaction,
Expand Down
53 changes: 53 additions & 0 deletions apps/backend/src/modules/pages/page.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
import { APIPath } from "~/libs/enums/enums.js";
import { authGuard } from "~/libs/modules/auth/auth.js";
import {
type APIHandlerOptions,
type APIHandlerResponse,
BaseController,
} from "~/libs/modules/controller/controller.js";
import { HTTPCode, HTTPMethod } from "~/libs/modules/http/http.js";
import { type Logger } from "~/libs/modules/logger/logger.js";
import { type TokenPayload } from "~/libs/modules/token/token.js";

import {
type GetPageDebugHandlerOptions,
Expand All @@ -21,6 +23,13 @@ import {
} from "./libs/types/types.js";
import { type PageService } from "./page.service.js";

type UndoPageHandlerOptions = APIHandlerOptions<{
params: {
id: number;
};
user: TokenPayload;
}>;

class PageController extends BaseController {
private pageService: PageService;

Expand Down Expand Up @@ -61,6 +70,16 @@ class PageController extends BaseController {
params: reprocessPageParameters,
},
});

this.addRoute({
handler: (options) => this.undo(options as UndoPageHandlerOptions),
method: HTTPMethod.POST,
path: PageApiPath.UNDO,
preHandler: authGuard,
validation: {
params: verifyPageParameters,
},
});
}

/**
Expand Down Expand Up @@ -133,6 +152,40 @@ class PageController extends BaseController {
};
}

/**
* @swagger
* /pages/{id}/undo:
* post:
* description: Undo the latest verification of a page
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* minimum: 1
* responses:
* 200:
* description: Page verification undone
* 404:
* description: Page not found
* 409:
* description: Page is not verified
*/
private async undo(
options: UndoPageHandlerOptions,
): Promise<APIHandlerResponse> {
return {
payload: await this.pageService.undo(
options.params.id,
options.user.userId,
),
status: HTTPCode.OK,
};
}

/**
* @swagger
* /pages/{id}/verify:
Expand Down
Loading
Loading