feat: Notifiers CRUD (#337)

* introduce scaffold for new models

* wip: shoutrrr wrapper (may remove)

* update schema files

* gen: ent code

* gen: migrations

* go mod tidy

* add group_id to notifier

* db migration

* new mapper helpers

* notifier repo

* introduce experimental adapter pattern for hdlrs

* refactor adapters to fit more common use cases

* new routes for notifiers

* update errors to fix validation panic

* go tidy

* reverse checkbox label display

* wip: notifiers UI

* use badges instead of text

* improve documentation

* add scaffold schema reference

* remove notifier service

* refactor schema folder

* support group edges via scaffold

* delete test file

* include link to API docs

* audit and update documentation + improve format

* refactor schema edges

* refactor

* add custom validator

* set validate + order fields by name

* fix failing tests
This commit is contained in:
Hayden 2023-03-06 21:18:58 -09:00 committed by GitHub
parent 2665b666f1
commit 23b5892aef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
100 changed files with 11437 additions and 2075 deletions

View file

@ -0,0 +1,59 @@
import { faker } from "@faker-js/faker";
import { describe, expect, test } from "vitest";
import { factories } from "../factories";
describe("basic notifier workflows", () => {
test("user should be able to create, update, and delete a notifier", async () => {
const { client } = await factories.client.singleUse();
// Create Notifier
const result = await client.notifiers.create({
name: faker.name.firstName(),
url: "discord://" + faker.random.alphaNumeric(10),
isActive: true,
});
expect(result.error).toBeFalsy();
expect(result.status).toBe(201);
expect(result.data).toBeTruthy();
const notifier = result.data;
// Update Notifier with new URL
{
const updateData = {
name: faker.name.firstName(),
url: "discord://" + faker.random.alphaNumeric(10),
isActive: true,
};
const updateResult = await client.notifiers.update(notifier.id, updateData);
expect(updateResult.error).toBeFalsy();
expect(updateResult.status).toBe(200);
expect(updateResult.data).toBeTruthy();
expect(updateResult.data.name).not.toBe(notifier.name);
}
// Update Notifier with empty URL
{
const updateData = {
name: faker.name.firstName(),
url: null,
isActive: true,
};
const updateResult = await client.notifiers.update(notifier.id, updateData);
expect(updateResult.error).toBeFalsy();
expect(updateResult.status).toBe(200);
expect(updateResult.data).toBeTruthy();
expect(updateResult.data.name).not.toBe(notifier.name);
}
// Delete Notifier
{
const deleteResult = await client.notifiers.delete(notifier.id);
expect(deleteResult.error).toBeFalsy();
expect(deleteResult.status).toBe(204);
}
});
});

View file

@ -0,0 +1,28 @@
import { BaseAPI, route } from "../base";
import { NotifierCreate, NotifierOut, NotifierUpdate } from "../types/data-contracts";
export class NotifiersAPI extends BaseAPI {
getAll() {
return this.http.get<NotifierOut[]>({ url: route("/notifiers") });
}
create(body: NotifierCreate) {
return this.http.post<NotifierCreate, NotifierOut>({ url: route("/notifiers"), body });
}
update(id: string, body: NotifierUpdate) {
if (body.url === "") {
body.url = null;
}
return this.http.put<NotifierUpdate, NotifierOut>({ url: route(`/notifiers/${id}`), body });
}
delete(id: string) {
return this.http.delete<void>({ url: route(`/notifiers/${id}`) });
}
test(url: string) {
return this.http.post<{ url: string }, null>({ url: route(`/notifiers/test`), body: { url } });
}
}

View file

@ -267,6 +267,36 @@ export interface MaintenanceLog {
itemId: string;
}
export interface NotifierCreate {
isActive: boolean;
/**
* @minLength 1
* @maxLength 255
*/
name: string;
url: string;
}
export interface NotifierOut {
createdAt: Date | string;
groupId: string;
id: string;
isActive: boolean;
name: string;
updatedAt: Date | string;
userId: string;
}
export interface NotifierUpdate {
isActive: boolean;
/**
* @minLength 1
* @maxLength 255
*/
name: string;
url: string | null;
}
export interface PaginationResultItemSummary {
items: ItemSummary[];
page: number;

View file

@ -8,6 +8,7 @@ import { ActionsAPI } from "./classes/actions";
import { StatsAPI } from "./classes/stats";
import { AssetsApi } from "./classes/assets";
import { ReportsAPI } from "./classes/reports";
import { NotifiersAPI } from "./classes/notifiers";
import { Requests } from "~~/lib/requests";
export class UserClient extends BaseAPI {
@ -20,6 +21,7 @@ export class UserClient extends BaseAPI {
stats: StatsAPI;
assets: AssetsApi;
reports: ReportsAPI;
notifiers: NotifiersAPI;
constructor(requests: Requests, attachmentToken: string) {
super(requests, attachmentToken);
@ -33,6 +35,7 @@ export class UserClient extends BaseAPI {
this.stats = new StatsAPI(requests);
this.assets = new AssetsApi(requests);
this.reports = new ReportsAPI(requests);
this.notifiers = new NotifiersAPI(requests);
Object.freeze(this);
}