Skip to content

Commit

Permalink
First scaffold of POST /tasks operation
Browse files Browse the repository at this point in the history
  • Loading branch information
kin0992 committed Jan 14, 2025
1 parent 8ae8f60 commit 75c7a97
Show file tree
Hide file tree
Showing 14 changed files with 307 additions and 3 deletions.
1 change: 0 additions & 1 deletion apps/to-do-api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ local.settings.json

# End of https://www.toptal.com/developers/gitignore/api/azurefunctions
# Generated files
src/generated/*
77 changes: 77 additions & 0 deletions apps/to-do-api/docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,35 @@ paths:
'500':
description: Internal Server error

/tasks:
post:
operationId: createTask
summary: Create a new task
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTaskItem'
responses:
'201':
description: Returns the created task
content:
application/json:
schema:
$ref: '#/components/schemas/TaskItem'
'400':
description: Invalid request body
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemJSON'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemJSON'

components:
schemas:
ApplicationInfo:
Expand All @@ -29,3 +58,51 @@ components:
required:
- name
- version
CreateTaskItem:
type: object
required:
- title
properties:
title:
$ref: '#/components/schemas/TaskTitle'
TaskId:
description: The id of the task
type: string
TaskTitle:
description: The title of the task
type: string
TaskState:
type: string
description: |-
- COMPLETE: The task has been completed
- INCOMPLETE: The task is not yet completed
- DELETED: The task has been marked as deleted
enum:
- COMPLETED
- INCOMPLETE
- DELETED
TaskItem:
type: object
required:
- id
- title
- state
properties:
id:
$ref: '#/components/schemas/TaskId'
title:
$ref: '#/components/schemas/TaskTitle'
state:
$ref: '#/components/schemas/TaskState'
ProblemJSON:
type: object
required:
- status
- message
properties:
status:
type: number
description: The HTTP status code
message:
type: string
description: The description of the error
32 changes: 32 additions & 0 deletions apps/to-do-api/src/adapters/azure/functions/create-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as H from "@pagopa/handler-kit";
import { httpAzureFunction } from "@pagopa/handler-kit-azure-func";
import * as RTE from "fp-ts/lib/ReaderTaskEither.js";
import { flow, pipe } from "fp-ts/lib/function.js";

import { CreateTaskItem } from "../../../generated/definitions/internal/CreateTaskItem.js";
import { TaskItem } from "../../../generated/definitions/internal/TaskItem.js";
import { TaskStateEnum } from "../../../generated/definitions/internal/TaskState.js";
import { toHttpProblemJson, toTaskItemAPI } from "../../http/codec.js";
import { parseRequestBody } from "../../http/middleware.js";

const makeHandlerKitHandler: H.Handler<
H.HttpRequest,
| H.HttpResponse<H.ProblemJson, H.HttpErrorStatusCode>
| H.HttpResponse<TaskItem, 201>
> = H.of((req: H.HttpRequest) =>
pipe(
RTE.fromEither(parseRequestBody(CreateTaskItem)(req)),
RTE.flatMap(({ title }) =>
// TODO: Here we are going to add the call to a use case (or in general to the function that creates the task)
RTE.of({
id: "123",
state: TaskStateEnum.INCOMPLETE,
title,
}),
),
RTE.mapBoth(toHttpProblemJson, flow(toTaskItemAPI, H.createdJson)),
RTE.orElseW(RTE.of),
),
);

export const makePostTaskHandler = httpAzureFunction(makeHandlerKitHandler);
19 changes: 19 additions & 0 deletions apps/to-do-api/src/adapters/http/codec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as H from "@pagopa/handler-kit";
import { pipe } from "fp-ts/function";

import { TaskItem as TaskItemAPI } from "../../generated/definitions/internal/TaskItem.js";
import { TaskStateEnum } from "../../generated/definitions/internal/TaskState.js";

// TODO: Here we are going to add the implementation of the function that convert a domain object into a TaskItemAPI object
export const toTaskItemAPI = (): TaskItemAPI => ({
id: "123",
state: TaskStateEnum.INCOMPLETE,
title: "Task Title",
});

/**
* This function converts any Error into an HTTP error response.
* @param err the error to convert.
*/
export const toHttpProblemJson = (err: Error) =>
pipe(err, H.toProblemJson, H.problemJson);
21 changes: 21 additions & 0 deletions apps/to-do-api/src/adapters/http/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as H from "@pagopa/handler-kit";
import * as E from "fp-ts/lib/Either.js";
import { pipe } from "fp-ts/lib/function.js";
import { Decoder } from "io-ts";

/**
* Parses the request body using a specified io-ts schema and validates it.
*
* This function takes a JSON schema decoder and an HTTP request, then attempts to
* parse and validate the request body against the schema.
* If the validation fails, it returns an {@link H.HttpBadRequestError} with an
* appropriate message.
*/
export const parseRequestBody =
<T>(schema: Decoder<unknown, T>) =>
(req: H.HttpRequest) =>
pipe(
req.body,
H.parse(schema),
E.mapLeft(() => new H.HttpBadRequestError("Missing or invalid body")),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Do not edit this file it is auto-generated by io-utils / gen-api-models.
* See https://github.com/pagopa/io-utils
*/
/* eslint-disable */

import * as t from "io-ts";

// required attributes
const ApplicationInfoR = t.interface({
name: t.string,

version: t.string
});

// optional attributes
const ApplicationInfoO = t.partial({});

export const ApplicationInfo = t.intersection(
[ApplicationInfoR, ApplicationInfoO],
"ApplicationInfo"
);

export type ApplicationInfo = t.TypeOf<typeof ApplicationInfo>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Do not edit this file it is auto-generated by io-utils / gen-api-models.
* See https://github.com/pagopa/io-utils
*/
/* eslint-disable */

import { TaskTitle } from "./TaskTitle.js";
import * as t from "io-ts";

// required attributes
const CreateTaskItemR = t.interface({
title: TaskTitle
});

// optional attributes
const CreateTaskItemO = t.partial({});

export const CreateTaskItem = t.intersection(
[CreateTaskItemR, CreateTaskItemO],
"CreateTaskItem"
);

export type CreateTaskItem = t.TypeOf<typeof CreateTaskItem>;
24 changes: 24 additions & 0 deletions apps/to-do-api/src/generated/definitions/internal/ProblemJSON.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Do not edit this file it is auto-generated by io-utils / gen-api-models.
* See https://github.com/pagopa/io-utils
*/
/* eslint-disable */

import * as t from "io-ts";

// required attributes
const ProblemJSONR = t.interface({
status: t.number,

message: t.string
});

// optional attributes
const ProblemJSONO = t.partial({});

export const ProblemJSON = t.intersection(
[ProblemJSONR, ProblemJSONO],
"ProblemJSON"
);

export type ProblemJSON = t.TypeOf<typeof ProblemJSON>;
14 changes: 14 additions & 0 deletions apps/to-do-api/src/generated/definitions/internal/TaskId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Do not edit this file it is auto-generated by io-utils / gen-api-models.
* See https://github.com/pagopa/io-utils
*/
/* eslint-disable */

import * as t from "io-ts";

/**
* The id of the task
*/

export type TaskId = t.TypeOf<typeof TaskId>;
export const TaskId = t.string;
26 changes: 26 additions & 0 deletions apps/to-do-api/src/generated/definitions/internal/TaskItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Do not edit this file it is auto-generated by io-utils / gen-api-models.
* See https://github.com/pagopa/io-utils
*/
/* eslint-disable */

import { TaskId } from "./TaskId.js";
import { TaskTitle } from "./TaskTitle.js";
import { TaskState } from "./TaskState.js";
import * as t from "io-ts";

// required attributes
const TaskItemR = t.interface({
id: TaskId,

title: TaskTitle,

state: TaskState
});

// optional attributes
const TaskItemO = t.partial({});

export const TaskItem = t.intersection([TaskItemR, TaskItemO], "TaskItem");

export type TaskItem = t.TypeOf<typeof TaskItem>;
25 changes: 25 additions & 0 deletions apps/to-do-api/src/generated/definitions/internal/TaskState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Do not edit this file it is auto-generated by io-utils / gen-api-models.
* See https://github.com/pagopa/io-utils
*/
/* eslint-disable */

import { enumType } from "@pagopa/ts-commons/lib/types.js";
import * as t from "io-ts";

export enum TaskStateEnum {
"COMPLETED" = "COMPLETED",

"INCOMPLETE" = "INCOMPLETE",

"DELETED" = "DELETED"
}

/**
* - COMPLETE: The task has been completed
* - INCOMPLETE: The task is not yet completed
* - DELETED: The task has been marked as deleted
*/

export type TaskState = t.TypeOf<typeof TaskState>;
export const TaskState = enumType<TaskStateEnum>(TaskStateEnum, "TaskState");
14 changes: 14 additions & 0 deletions apps/to-do-api/src/generated/definitions/internal/TaskTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Do not edit this file it is auto-generated by io-utils / gen-api-models.
* See https://github.com/pagopa/io-utils
*/
/* eslint-disable */

import * as t from "io-ts";

/**
* The title of the task
*/

export type TaskTitle = t.TypeOf<typeof TaskTitle>;
export const TaskTitle = t.string;
8 changes: 8 additions & 0 deletions apps/to-do-api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DefaultAzureCredential } from "@azure/identity";
import * as E from "fp-ts/lib/Either.js";
import { pipe } from "fp-ts/lib/function.js";

import { makePostTaskHandler } from "./adapters/azure/functions/create-task.js";
import { makeInfoHandler } from "./adapters/azure/functions/info.js";
import { getConfigOrError } from "./config.js";

Expand All @@ -26,3 +27,10 @@ app.http("info", {
methods: ["GET"],
route: "info",
});

app.http("createTask", {
authLevel: "function",
handler: makePostTaskHandler({}),
methods: ["POST"],
route: "tasks",
});
2 changes: 0 additions & 2 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
"dist/**"
],
"dependsOn": [
"^generate",
"generate",
"^build"
]
},
Expand Down

0 comments on commit 75c7a97

Please sign in to comment.