From 32e8dfa4bb3abfb9a86ff90eecb880089cd2361e Mon Sep 17 00:00:00 2001 From: ModZero Date: Sun, 27 Oct 2019 13:41:16 +0100 Subject: [PATCH] Connect the website to the API --- src/frontend/api/tasks.ts | 62 +++++++++++++++------------- src/frontend/api/types.ts | 8 ++++ src/frontend/globals.d.ts | 1 + src/frontend/store/sagas.ts | 17 +++++--- src/frontend/store/tasks/actions.ts | 4 +- src/frontend/store/tasks/reducers.ts | 1 + src/frontend/store/tasks/types.ts | 4 +- webpack.config.ts | 7 +++- 8 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 src/frontend/api/types.ts create mode 100644 src/frontend/globals.d.ts diff --git a/src/frontend/api/tasks.ts b/src/frontend/api/tasks.ts index 8820429..7f275aa 100644 --- a/src/frontend/api/tasks.ts +++ b/src/frontend/api/tasks.ts @@ -1,34 +1,38 @@ -import { DateTime } from "luxon"; +import { APIError, Task } from "./types"; -export interface Task { - id: number; - name: string; - due: DateTime; -} +export const getTasks = async ( + limit: number = 10, + offset: number = 0 +): Promise<{ items: Task[]; count: number }> => { + const url = new URL("tasks", API_URL); + url.searchParams.set("limit", limit.toString()); + url.searchParams.set("offset", offset.toString()); -const tasks: Task[] = [...Array(5).keys()].map(n => ({ - id: n, - name: `Task ${n}`, - due: DateTime.local().plus({ weeks: n }) -})); + const response = await fetch(url.toString()); + if (!response.ok) { + throw new APIError("Task fetch failed"); + } -export const getTasks = async (): Promise => - new Promise((resolve, reject) => - setTimeout(() => resolve(tasks), 3000) - ); + const json = await response.json(); + if (!Number.isSafeInteger(json.count) || !Array.isArray(json.tasks)) { + throw new APIError("Invalid response from server."); + } + const count: number = json.count; + const items: Task[] = (json.tasks as any[]).map(t => { + if ( + typeof t !== "object" || + !Number.isSafeInteger(t.id) || + typeof t.name !== "string" + ) { + throw new APIError("Invalid response from server."); + } -export const schedule = async (task: Omit): Promise => - new Promise((resolve, reject) => - setTimeout(() => { - const id = Math.max(...tasks.map(t => t.id)) + 1; - tasks[id] = { ...task, id }; - resolve(tasks[id]); - }, 3000) - ); + return { + id: t.id, + name: t.name, + schedule: { type: "Plain" } + }; + }); -export const unschedule = async (taskId: number): Promise => - new Promise((resolve, reject) => - setTimeout(() => { - delete tasks[taskId]; - }, 3000) - ); + return { items, count }; +}; diff --git a/src/frontend/api/types.ts b/src/frontend/api/types.ts new file mode 100644 index 0000000..5170c24 --- /dev/null +++ b/src/frontend/api/types.ts @@ -0,0 +1,8 @@ +export class APIError extends Error {} +export interface Task { + id: number; + name: string; + schedule: { + type: "Plain"; + }; +} diff --git a/src/frontend/globals.d.ts b/src/frontend/globals.d.ts new file mode 100644 index 0000000..c4bc2a7 --- /dev/null +++ b/src/frontend/globals.d.ts @@ -0,0 +1 @@ +declare const API_URL: string; diff --git a/src/frontend/store/sagas.ts b/src/frontend/store/sagas.ts index cae8307..1ffb0eb 100644 --- a/src/frontend/store/sagas.ts +++ b/src/frontend/store/sagas.ts @@ -1,5 +1,6 @@ +import { getTasks } from "@kredens/frontend/api/tasks"; +import { Task as APITask } from "@kredens/frontend/api/types"; import { all, call, put, takeEvery } from "redux-saga/effects"; -import { getTasks, Task as APITask } from "../api/tasks"; import { taskFetchError, taskFetchOk, taskFetchStart } from "./tasks/actions"; import { Task, TaskQuery, TaskScheduleType } from "./tasks/types"; @@ -7,22 +8,26 @@ export function* fetchTasksSaga(query: TaskQuery = { limit: 10 }) { yield put(taskFetchStart(query)); try { - const tasks: APITask[] = yield call(getTasks); + const tasks: { items: APITask[]; count: number } = yield call( + getTasks, + query.limit, + query.offset + ); yield put( taskFetchOk( query, - tasks + tasks.items .map<[string, Task]>(t => [ t.id.toString(), { name: t.name, schedule: { - type: TaskScheduleType.Once, - due: t.due.toISO() + type: TaskScheduleType[t.schedule.type] } } ]) - .reduce((res, [id, task]) => ({ ...res, [id]: task }), {}) + .reduce((res, [id, task]) => ({ ...res, [id]: task }), {}), + tasks.count ) ); } catch (error) { diff --git a/src/frontend/store/tasks/actions.ts b/src/frontend/store/tasks/actions.ts index e666432..33f04fa 100644 --- a/src/frontend/store/tasks/actions.ts +++ b/src/frontend/store/tasks/actions.ts @@ -26,12 +26,14 @@ export function taskFetchStart(query: TaskQuery): TasksActionType { export function taskFetchOk( query: TaskQuery, - results: { [key: string]: Task } + results: { [key: string]: Task }, + count: number ): TasksActionType { return { type: TasksAction.TASKS_FETCH_OK, query, results, + count, fetched: DateTime.utc().toISO() }; } diff --git a/src/frontend/store/tasks/reducers.ts b/src/frontend/store/tasks/reducers.ts index a48be12..7a1aef0 100644 --- a/src/frontend/store/tasks/reducers.ts +++ b/src/frontend/store/tasks/reducers.ts @@ -3,6 +3,7 @@ import { TasksAction, TasksActionType, TasksState } from "./types"; const initialState: TasksState = { items: {}, + count: 0, queries: {} }; diff --git a/src/frontend/store/tasks/types.ts b/src/frontend/store/tasks/types.ts index 57299bf..0e2e03c 100644 --- a/src/frontend/store/tasks/types.ts +++ b/src/frontend/store/tasks/types.ts @@ -21,8 +21,8 @@ export interface Task { export interface TaskQuery { query?: string; - after?: string; limit: number; + offset?: number; } interface TaskQueryOk { @@ -48,6 +48,7 @@ export interface TasksState { items: { [key: string]: Task; }; + count: number; queries: { [key: string]: TaskQueryResult; }; @@ -82,6 +83,7 @@ interface TaskFetchOkAction { type: TasksAction.TASKS_FETCH_OK; query: TaskQuery; results: { [key: string]: Task }; + count: number; fetched: string; } diff --git a/webpack.config.ts b/webpack.config.ts index 81c7715..900a10c 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -24,7 +24,12 @@ const config: webpack.Configuration = { alias: { "@kredens": path.resolve(__dirname, "src/") } - } + }, + plugins: [ + new webpack.DefinePlugin({ + API_URL: JSON.stringify("http://localhost:3000/api/") + }) + ] }; export default config;