Skip to content

Commit

Permalink
Merge pull request #126 from SE-UUlm/feat/125-feature-add-basic-api-c…
Browse files Browse the repository at this point in the history
…ontroller-structure

Added basic api controller structure
  • Loading branch information
Merseleo authored Dec 10, 2024
2 parents cd89d35 + 26953a2 commit adde219
Show file tree
Hide file tree
Showing 20 changed files with 1,213 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; Prefix client code env variables with 'PUBLIC_' (https://svelte.dev/tutorial/kit/env-static-public)
PUBLIC_API_BASE_URL=http://localhost:8080
1 change: 1 addition & 0 deletions .github/workflows/code_quality_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:

env:
node_version: ${{ vars.NODE_VERSION }}
PUBLIC_API_BASE_URL: http://localhost:8080

jobs:
linting:
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/lighthouse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
types:
- review_requested

env:
node_version: ${{ vars.NODE_VERSION }}
PUBLIC_API_BASE_URL: http://localhost:8080

jobs:
lighthouse:
name: Lighthouse Auditing
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ Frontend of the SnowballR project.

To keep track of the use cases we are working on, we are using the GitHub project board feature. You can find them [here](https://github.com/orgs/SE-UUlm/projects/2/views/7).

## Environment Variables

To run the app, you need to create a `.env` file in the root directory of the project. The file should contain the following environment variables (see [.env.example](./.env.example) for an example):

| Variable | Description |
| --------------------- | --------------------------- |
| `PUBLIC_API_BASE_URL` | The URL of the backend API. |

## Developing

Once you've installed dependencies with `npm install`, start a development server:
Expand Down
560 changes: 560 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"test": "npm run test:unit && npm run test:integration && npm run test:e2e",
"test": "vitest && npm run test:e2e",
"show-coverage": "open coverage/index.html",
"test:unit": "vitest run --project unit",
"test:unit:ui": "npm run test:unit -- --ui",
"test:integration": "vitest run --project integration",
"test:integration:ui": "npm run test:integration -- --ui",
"test:unit": "vitest --project unit",
"test:unit:ui": "npm run test:unit -- --ui --watch --open",
"test:integration": "vitest --project integration",
"test:integration:ui": "npm run test:integration -- --ui --watch --open",
"test:e2e": "playwright test",
"test:and-open-coverage": "npm run test && npm run show-coverage"
},
Expand All @@ -40,6 +40,7 @@
"globals": "^15.13.0",
"jsdom": "^25.0.1",
"lucide-svelte": "^0.456.0",
"msw": "^2.6.8",
"postcss": "^8.4.49",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.8",
Expand Down
8 changes: 4 additions & 4 deletions src/lib/backend-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface IStageEntryController {

getReviews(): Promise<Review[]>;
addReview(spec: ReviewSpec): Promise<Review>;
review(userId: number): IReviewController;
review(reviewerId: number): IReviewController;
}

export interface IStageController {
Expand All @@ -71,7 +71,7 @@ export interface IProjectController {
getReplicationPackageAsZip(): Promise<Blob>;
getAllPapersAsCsv(): Promise<Blob>;

getStageCount(): number;
getStageCount(): Promise<number>;
stage(stageIndex: number): IStageController;

getMembers(): Promise<User[]>;
Expand All @@ -90,8 +90,8 @@ export interface IAuthorController {
}

export interface IBackendController {
login(username: string, password: string): Promise<User>;
logout(): Promise<void>;
signIn(email: string, password: string): Promise<User>;
signOut(): Promise<void>;
requestEmailForgottenPassword(email: string): Promise<void>;

getProjects(): Promise<Project[]>;
Expand Down
24 changes: 24 additions & 0 deletions src/lib/controller/author-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Author, AuthorSpec } from "$lib/model/backend";
import type { IAuthorController } from "../backend-api";
import { HttpClient } from "./http-client";

export class AuthorController implements IAuthorController {
private readonly client: HttpClient;

constructor(authorId: number) {
this.client = new HttpClient(`authors/${authorId}`);
}

async get(): Promise<Author> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async update(newSpec: AuthorSpec): Promise<Author> {
throw new Error("Method not implemented.");
}

async remove(): Promise<void> {
throw new Error("Method not implemented.");
}
}
105 changes: 105 additions & 0 deletions src/lib/controller/backend-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type {
IAuthorController,
IBackendController,
IPaperController,
IProjectController,
IUserController,
} from "../backend-api";
import type {
User,
Project,
ProjectSpec,
UserSpec,
Author,
AuthorSpec,
Paper,
PaperSpec,
} from "../model/backend";
import { AuthorController } from "./author-controller";
import { HttpClient } from "./http-client";
import { PaperController } from "./paper-controller";
import { ProjectController } from "./project-controller";
import { UserController } from "./user-controller";

export class BackendController implements IBackendController {
private static readonly instance: BackendController = new BackendController();
private readonly client: HttpClient;

private constructor() {
this.client = new HttpClient();
}

static getInstance() {
return BackendController.instance;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async signIn(email: string, password: string): Promise<User> {
throw new Error("Method not implemented.");
}

async signOut(): Promise<void> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async requestEmailForgottenPassword(email: string): Promise<void> {
throw new Error("Method not implemented.");
}

async getProjects(): Promise<Project[]> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async createProject(spec: ProjectSpec): Promise<Project> {
throw new Error("Method not implemented.");
}

project(projectId: number): IProjectController {
return new ProjectController(projectId);
}

async getUsers(): Promise<User[]> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async createUser(personalInfo: UserSpec, password: string): Promise<User> {
throw new Error("Method not implemented.");
}

user(userId: number): IUserController {
return new UserController(userId);
}

thisUser(): IUserController {
throw new Error("Method not implemented.");
}

async getAuthors(): Promise<Author[]> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async createAuthor(spec: AuthorSpec): Promise<Author> {
throw new Error("Method not implemented.");
}

author(authorId: number): IAuthorController {
return new AuthorController(authorId);
}

async getPapers(): Promise<Paper[]> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async createPaper(spec: PaperSpec): Promise<Paper> {
throw new Error("Method not implemented.");
}

paper(paperId: number): IPaperController {
return new PaperController(paperId);
}
}
24 changes: 24 additions & 0 deletions src/lib/controller/criterion-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Criterion, CriterionSpec } from "$lib/model/backend";
import type { ICriterionController } from "../backend-api";
import { HttpClient } from "./http-client";

export class CriterionController implements ICriterionController {
private readonly client: HttpClient;

constructor(basePath: string, criterionId: number) {
this.client = new HttpClient(`${basePath}/criteria/${criterionId}`);
}

async get(): Promise<Criterion> {
throw new Error("Method not implemented.");
}

async remove(): Promise<void> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async update(spec: CriterionSpec): Promise<Criterion> {
throw new Error("Method not implemented.");
}
}
107 changes: 107 additions & 0 deletions src/lib/controller/http-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { PUBLIC_API_BASE_URL } from "$env/static/public";

/**
* A simple HTTP client that can make requests to the backend API.
*
* This class is used to make requests to the backend API. It is a simple wrapper around the `fetch` API.
* It uses the `PUBLIC_API_BASE_URL` environment variable to determine the base URL of the API.
*
* @example
* const client = new HttpClient("api/v1/example");
* await client.get();
* await client.post({ data: "example" });
* await client.put({ data: "example" });
* await client.patch({ data: "example" });
* await client.delete();
*/
export class HttpClient {
private readonly basePath: string;

/**
* Creates a new HTTP client.
*
* @param path The path to the API endpoint. This path will be appended to the `PUBLIC_API_BASE_URL` environment variable.
*
* @example
* const client = new HttpClient("api/v1/example");
* // When the `PUBLIC_API_BASE_URL` environment variable is `http://localhost:3000`
* // the base URL of the API will be `http://localhost:3000/api/v1/example`
*/
constructor(path: string = "") {
this.basePath = `${PUBLIC_API_BASE_URL}${path ? `/${path}` : ""}`;
}

private async fetch<T>(
path: string = "",
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
body?: T,
headers?: HeadersInit,
): Promise<Response> {
return fetch(`${this.basePath}/${path}`, {
method,
headers: {
"Content-Type": "application/json",
...headers,
},
body: body ? JSON.stringify(body) : undefined,
});
}

/**
* Makes a GET request to the API.
*
* @param path The path to the API endpoint. This path will be appended to the base URL of the API.
* @param headers The headers to include in the request.
* @returns A promise that resolves to the response from the API.
*/
async get(path: string = "", headers?: HeadersInit): Promise<Response> {
return this.fetch(path, "GET", undefined, headers);
}

/**
* Makes a POST request to the API.
*
* @param path The path to the API endpoint. This path will be appended to the base URL of the API.
* @param body The body of the request.
* @param headers The headers to include in the request.
* @returns A promise that resolves to the response from the API.
*/
async post<T>(path: string = "", body?: T, headers?: HeadersInit): Promise<Response> {
return this.fetch(path, "POST", body, headers);
}

/**
* Makes a PUT request to the API.
*
* @param path The path to the API endpoint. This path will be appended to the base URL of the API.
* @param body The body of the request.
* @param headers The headers to include in the request.
* @returns A promise that resolves to the response from the API.
*/
async put<T>(path: string = "", body?: T, headers?: HeadersInit): Promise<Response> {
return this.fetch(path, "PUT", body, headers);
}

/**
* Makes a PATCH request to the API.
*
* @param path The path to the API endpoint. This path will be appended to the base URL of the API.
* @param body The body of the request.
* @param headers The headers to include in the request.
* @returns A promise that resolves to the response from the API.
*/
async patch<T>(path: string = "", body?: T, headers?: HeadersInit): Promise<Response> {
return this.fetch(path, "PATCH", body, headers);
}

/**
* Makes a DELETE request to the API.
*
* @param path The path to the API endpoint. This path will be appended to the base URL of the API.
* @param headers The headers to include in the request.
* @returns A promise that resolves to the response from the API.
*/
async delete(path: string = "", headers?: HeadersInit): Promise<Response> {
return this.fetch(path, "DELETE", undefined, headers);
}
}
28 changes: 28 additions & 0 deletions src/lib/controller/paper-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Paper, PaperSpec } from "$lib/model/backend";
import type { IPaperController } from "../backend-api";
import { HttpClient } from "./http-client";

export class PaperController implements IPaperController {
private readonly client: HttpClient;

constructor(paperId: number) {
this.client = new HttpClient(`papers/${paperId}`);
}

async get(): Promise<Paper> {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async update(newSpec: PaperSpec): Promise<Paper> {
throw new Error("Method not implemented.");
}

async getForwardReferencedPapers(): Promise<Paper[]> {
throw new Error("Method not implemented.");
}

async getBackwardReferencedPapers(): Promise<Paper[]> {
throw new Error("Method not implemented.");
}
}
Loading

0 comments on commit adde219

Please sign in to comment.