From 5f53c33521cef72b0c0bf822181e72cffbdd0d18 Mon Sep 17 00:00:00 2001 From: Marcel Lorbeer Date: Fri, 30 Jun 2023 21:17:24 +0200 Subject: [PATCH] Added functionality --- src/Collection.ts | 392 ++++++++++++++++++++++++++++++++++++++++++++++ src/Entry.ts | 240 ++++++++++++++++++++++++++++ src/marcsync.ts | 107 ++++++++++++- tsconfig.json | 1 + 4 files changed, 739 insertions(+), 1 deletion(-) create mode 100644 src/Collection.ts create mode 100644 src/Entry.ts diff --git a/src/Collection.ts b/src/Collection.ts new file mode 100644 index 0000000..991e640 --- /dev/null +++ b/src/Collection.ts @@ -0,0 +1,392 @@ +import { Entry, EntryData, EntryNotFound } from "./Entry"; +import { Unauthorized } from "./marcsync"; + +export class Collection { + + private _accessToken: string; + private _collectionName: string; + + /** + * + * @param accessToken - The access token to use for communication with MarcSync + * @param collectionName - The name of the collection to use + * @returns A new instance of the MarcSync collection + * + **/ + constructor(accessToken: string, collectionName: string) { + this._accessToken = accessToken; + this._collectionName = collectionName; + } + + /** + * + * **__warning: This method will delete the collection and all of its entries. This action cannot be undone.__** + * + */ + async drop(): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v0/collection/${this._collectionName}`, { + method: "DELETE", + headers: { + authorization: this._accessToken + } + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new CollectionNotFound("Failed to drop collection"); + } + } + + /** + * + * @returns The name of the collection + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * console.log(await collection.setName("my-new-collection-name")); + * + */ + async setName(name: string): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v0/collection/${this._collectionName}`, { + method: "PUT", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + collectionName: name + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new CollectionNotFound("Failed to set collection name"); + } + } + + /** + * + * @returns The name of the collection + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * console.log(await collection.getName()); + * + */ + getName(): string { + return this._collectionName; + } + + /** + * + * @returns Whether or not the collection exists + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * if (await collection.exists()) { + * console.log("Collection exists!"); + * } else { + * console.log("Collection does not exist!"); + * } + * + * @remarks + * This method is useful if you want to fetch the collection from the server to check if it exists before using it. + * + */ + async exists(): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v0/collection/${this._collectionName}`, { + method: "GET", + headers: { + authorization: this._accessToken + } + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + return true; + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + return false; + } + } + + /** + * + * @returns Creates an entry in the collection + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * await collection.createEntry({ + * name: "MarcSync", + * description: "A simple, easy to use database for your projects" + * }); + * + */ + async createEntry(data: EntryData): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v0/entries/${this._collectionName}`, { + method: "POST", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + data: data + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + data._id = json.objectId; + return new Entry(this._accessToken, this._collectionName, data); + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new CollectionNotFound("Failed to create entry"); + } + } + + /** + * + * @returns The entry with the specified ID + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * const entry = await collection.getEntryById("my-entry-id"); + */ + async getEntryById(id: string): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}?methodOverwrite=GET`, { + method: "PATCH", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: { + _id: id + } + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + if(json.entries.length === 0) throw new EntryNotFound(); + return new Entry(this._accessToken, this._collectionName, json.entries[0]); + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + if(e instanceof EntryNotFound) throw new EntryNotFound(); + throw new CollectionNotFound("Failed to fetch entry"); + } + } + + /** + * + * @returns The entries with the specified filter + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * const entries = await collection.getEntries({ + * name: "MarcSync" + * }); + * + * console.log(entries); + * + * @remarks + * This method is useful if you want to fetch multiple entries from the server at once. + * + * @see {@link getEntryById} if you want to fetch a single entry by its Id. + * @see {@link Entry} for more information about entries. + * @see {@link EntryData} for more information about entry data. + * + */ + async getEntries(filter: EntryData): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}?methodOverwrite=GET`, { + method: "PATCH", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: filter + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + return json.entries.map((entry: EntryData) => new Entry(this._accessToken, this._collectionName, entry)); + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new CollectionNotFound("Failed to fetch entries"); + } + } + + /** + * + * @returns The Id of the deleted entry + * + * **__warning: Will delete the entry from the collection. This action cannot be undone.__** + * + */ + async deleteEntryById(id: string): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}`, { + method: "DELETE", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: { + _id: id + } + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + if(json.deletedEntries === 0) throw new EntryNotFound(); + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new EntryNotFound("Failed to delete entry"); + } + return id; + } + + /** + * + * @returns The amount of deleted entries + * + * **__warning: Will delete the entries from the collection. This action cannot be undone.__** + * + */ + async deleteEntries(filter: EntryData): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}`, { + method: "DELETE", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: filter + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + return json.deletedEntries; + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new EntryNotFound("Failed to delete entries"); + } + } + + /** + * + * @returns The Id of the updated entry + * + */ + async updateEntryById(id: string, data: EntryData): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}`, { + method: "PUT", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: { + _id: id + }, + data: data + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + if(json.modifiedEntries === 0) throw new EntryNotFound(); + return id; + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new EntryNotFound("Failed to update entry"); + } + } + + /** + * + * @returns The amount of updated entries + * + */ + async updateEntries(filter: EntryData, data: EntryData): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}`, { + method: "PUT", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: filter, + data: data + }) + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + return json.modifiedEntries; + } catch (e) { + if(e instanceof Unauthorized) throw new Unauthorized(); + throw new EntryNotFound("Failed to update entries"); + } + } +} + +export class CollectionNotFound extends Error { + constructor(message: string = "Failed to fetch collection") { + super(message); + } +} + +export class CollectionAlreadyExists extends Error { + constructor(message: string = "Collection already exists") { + super(message); + } +} \ No newline at end of file diff --git a/src/Entry.ts b/src/Entry.ts new file mode 100644 index 0000000..fb08397 --- /dev/null +++ b/src/Entry.ts @@ -0,0 +1,240 @@ +export class Entry { + + private _accessToken: string; + private _collectionName: string; + private _entryId: string; + private _data: EntryData; + + constructor(accessToken: string, collectionName: string, data: EntryData) { + this._accessToken = accessToken; + this._collectionName = collectionName; + this._entryId = data._id; + this._data = data; + } + + /** + * + * @returns The EntryData object of the entry + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * const entry = await collection.getEntryById("my-entry-id"); + * + * const data = entry.getValues(); + * + * console.log(data); + * + * @remarks + * This method is useful if you want to get the values of the entry. + * + * @see {@link EntryData} for more information about entry data. + * + */ + getValues(): EntryData { + return this._data; + } + + /** + * + * @param key - The key of the value to get + * @returns The value of the specified key + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * const entry = await collection.getEntryById("my-entry-id"); + * + * const name = entry.getValueAs("name"); + * + * console.log(name); + * + * @remarks + * This method is useful if you want to get the value of a specific key as a specific type. + * + * @see {@link EntryData} for more information about entry data. + * + */ + getValueAs(key: string): T { + return this._data[key]; + } + + /** + * + * @param key - The key of the value to get + * @returns The value of the specified key + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * const entry = await collection.getEntryById("my-entry-id"); + * + * const name = entry.getValue("name"); + * + * console.log(name); + * + * @remarks + * This method is useful if you want to get the value of a specific key without specifying the type. + * + * @see {@link EntryData} for more information about entry data. + * + */ + getValue(key: string): any { + return this._data[key]; + } + + /** + * + * @param key - The key of the value to update + * @param value - The value to update + * @returns The values of the entry after update + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * const entry = await collection.getEntryById("my-entry-id"); + * + * const name = entry.updateValue("name", "MarcSync"); + * + * console.log(name); + * + * @remarks + * This method is useful if you want to update the value of a specific key. + * + */ + async updateValue(key: string, value: any): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}`, { + method: "PUT", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: { + _id: this._entryId + }, + data: { + [key]: value + } + }) + }) + if (result.status !== 200) + throw new Error("Failed to update entry"); + } catch (err) { + throw new EntryUpdateFailed(err); + } + this._data[key] = value; + return this._data; + } + + /** + * + * @param values - The values to update + * @returns The values of the entry after update + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + * const entry = await collection.getEntryById("my-entry-id"); + * + * await entry.updateValues({ + * name: "MarcSync", + * age: 18 + * }); + * + * @remarks + * This method is useful if you want to update multiple values of the entry. + * + * @see {@link EntryData} for more information about entry data. + * @see {@link updateValue} for more information about updating a single value. + * + */ + async updateValues(values: EntryData): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}`, { + method: "PUT", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: { + _id: this._entryId + }, + data: values + }) + }) + if (result.status !== 200) + throw new Error("Failed to update entry"); + } catch (err) { + throw new EntryUpdateFailed(err); + } + for (const key in values) { + this._data[key] = values[key]; + } + return this._data; + } + + /** + * + * **__warning: Will delete the entry from the collection. This action cannot be undone.__** + * + */ + async delete(): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v1/entries/${this._collectionName}`, { + method: "DELETE", + headers: { + authorization: this._accessToken, + "content-type": "application/json" + }, + body: JSON.stringify({ + filters: { + _id: this._entryId + } + }) + }) + if (result.status !== 200) + throw new Error(); + } catch (err) { + throw new EntryUpdateFailed("Could not delete entry"); + } + } +} + +export interface EntryData { + [key: string]: any; +} + +export class EntryNotFound extends Error { + constructor(message: string = "Failed to fetch entry by Id") { + super(message); + } +} + +export class EntryUpdateFailed extends Error { + constructor(message: any = "Failed to update entry") { + super(message); + } +} \ No newline at end of file diff --git a/src/marcsync.ts b/src/marcsync.ts index eac3cbf..44d53b6 100644 --- a/src/marcsync.ts +++ b/src/marcsync.ts @@ -1 +1,106 @@ -console.log("Placeholder"); \ No newline at end of file +import { Collection, CollectionAlreadyExists, CollectionNotFound } from "./Collection"; +import { Entry, EntryData } from "./Entry"; + +export class Client { + + private _accessToken: string; + + /** + * + * @param accessToken - The access token to use for communication with MarcSync + * @returns A new instance of the MarcSync client + * + */ + constructor(accessToken: string) { + this._accessToken = accessToken; + } + + /** + * + * @param collectionName - The name of the collection to use + * @returns A new instance of the MarcSync collection + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.getCollection("my-collection"); + * + */ + getCollection(collectionName: string) { + return new Collection(this._accessToken, collectionName); + } + + /** + * + * @param collectionName - The name of the collection to use + * @returns A new instance of the MarcSync collection + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.fetchCollection("my-collection"); + * + * @remarks + * This method is useful if you want to fetch the collection from the server to check if it exists before using it. + * + */ + async fetchCollection(collectionName: string): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v0/collection/${collectionName}`, { + method: "GET", + headers: { + authorization: this._accessToken + } + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + } catch (e) { + if (e instanceof Unauthorized) throw new Unauthorized(); + throw new CollectionNotFound(); + } + return new Collection(this._accessToken, collectionName); + } + + /** + * + * @param collectionName - The name of the collection to create + * @returns A new instance of the MarcSync collection + * + * @example + * + * import { Client } from "marcsync"; + * + * const client = new Client(""); + * const collection = client.createCollection("my-collection"); + * + * @remarks + */ + async createCollection(collectionName: string): Promise { + try { + const result = await fetch(`https://api.marcsync.dev/v0/collection/${collectionName}`, { + method: "POST", + headers: { + authorization: this._accessToken + } + }) + if (result.status === 401) throw new Unauthorized(); + const json = await result.json(); + if (!json.success) throw new Error(); + } catch (e) { + if (e instanceof Unauthorized) throw new Unauthorized(); + throw new CollectionAlreadyExists(); + } + return new Collection(this._accessToken, collectionName); + } +} + +export class Unauthorized extends Error { + constructor(message: string = "Invalid access token") { + super(message); + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index beb250c..0dd6211 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES2020", "module": "commonjs", "outDir": "dist", + "declaration": true, "strict": true, "esModuleInterop": true },