diff --git a/src/auth/fetch-jwt.ts b/src/auth/fetch-jwt.ts
new file mode 100644
index 0000000..02c7a50
--- /dev/null
+++ b/src/auth/fetch-jwt.ts
@@ -0,0 +1,56 @@
+/*!
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+import { createUrl } from '../common/url';
+import { handleError } from '../common/error';
+
+interface MCRJWTResponse {
+ login_success: boolean;
+ access_token: string;
+}
+
+/**
+ * Fetches a JSON Web Token (JWT) from a remote server.
+ *
+ * @param baseUrl - The base URL of the server from which the JWT will be fetched
+ * @param params - Optional query parameters to be included in the request
+ *
+ * @returns A promise that resolves to a string containing the JWT access token if the login is successful
+ *
+ * @throws Will throw an error if the fetch operation fails, if the server response is invalid or unexpected,
+ * or if the login attempt fails.
+ */
+const fetchJWT = async(baseUrl: string, params?: Record): Promise => {
+ const url = createUrl(baseUrl, 'rsc/jwt', params);
+ let response: Response;
+ try {
+ response = await fetch(url);
+ } catch (error) {
+ return handleError('Error while fetching JWT', error);
+ }
+ if (!response.ok) {
+ throw new Error('Failed to fetch JWT for current user.');
+ }
+ const result: MCRJWTResponse = await response.json() as MCRJWTResponse;
+ if (!result.login_success) {
+ throw new Error('Login failed.');
+ }
+ return result.access_token;
+};
+
+export { fetchJWT };
diff --git a/src/auth/index.ts b/src/auth/index.ts
new file mode 100644
index 0000000..fcd066b
--- /dev/null
+++ b/src/auth/index.ts
@@ -0,0 +1 @@
+export * from './fetch-jwt';
diff --git a/src/common/cache/cache.ts b/src/common/cache/cache.ts
new file mode 100644
index 0000000..585d295
--- /dev/null
+++ b/src/common/cache/cache.ts
@@ -0,0 +1,71 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+/**
+ * A generic interface representing a cache with basic CRUD operations and TTL support.
+ *
+ * @typeParam T - The type of the cached values. This can be any type, such as `string`, `number`, or more complex objects
+ */
+interface MCRCache {
+
+ /**
+ * Sets a value in the cache with an optional time-to-live (TTL) in seconds.
+ *
+ * @param key - The key to associate with the value
+ * @param value - The value to store in the cache
+ * @param ttl - Optional time-to-live for the cache entry in seconds
+ */
+ set(key: string, value: T, ttl?: number): void;
+
+ /**
+ * Retrieves a value from the cache by its key.
+ *
+ * @param key - The key of the cached value to retrieve
+ * @returns The cached value if it exists, or `undefined` if the key does not exist in the cache
+ */
+ get(key: string): T | undefined;
+
+ /**
+ * Checks if a key exists in the cache.
+ *
+ * @param key - The key to check in the cache
+ * @returns `true` if the key exists in the cache, `false` otherwise
+ */
+ has(key: string): boolean;
+
+ /**
+ * Deletes a value from the cache by its key.
+ *
+ * @param key - The key of the cached value to delete
+ */
+ delete(key: string): void;
+
+ /**
+ * Clears all values from the cache.
+ */
+ clear(): void;
+
+ /**
+ * Gets the number of items currently in the cache.
+ *
+ * @returns The number of cached items
+ */
+ size(): number;
+}
+
+export { MCRCache };
diff --git a/src/common/cache/index.ts b/src/common/cache/index.ts
new file mode 100644
index 0000000..22a759d
--- /dev/null
+++ b/src/common/cache/index.ts
@@ -0,0 +1,2 @@
+export * from './cache';
+export * from './memory-cache';
diff --git a/src/common/cache/memory-cache.ts b/src/common/cache/memory-cache.ts
new file mode 100644
index 0000000..028e4dc
--- /dev/null
+++ b/src/common/cache/memory-cache.ts
@@ -0,0 +1,89 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+import { MCRCache } from './cache';
+
+/**
+ * A memory-based cache implementation that stores values in memory using a `Map` with optional TTL (Time-to-Live).
+ *
+ * @typeParam T - The type of the cached values. This can be any type, such as `string`, `number`, or more complex objects.
+ */
+class MCRMemoryCache implements MCRCache {
+ private cache: Map;
+
+ constructor() {
+ this.cache = new Map();
+ }
+
+ /**
+ * @override
+ */
+ public set(key: string, value: T, ttl = 0): void {
+ const expiry = ttl === 0 ? null : Date.now() + ttl * 1000;
+ this.cache.set(key, { value, expiry });
+ }
+
+ /**
+ * @override
+ */
+ public get(key: string): T | undefined {
+ const cached = this.cache.get(key);
+ if (!cached) return undefined;
+ if (cached.expiry !== null && cached.expiry < Date.now()) {
+ this.cache.delete(key);
+ return undefined;
+ }
+ return cached.value;
+ }
+
+ /**
+ * @override
+ */
+ public has(key: string): boolean {
+ const cached = this.cache.get(key);
+ if (!cached) return false;
+ if (cached.expiry !== null && cached.expiry < Date.now()) {
+ this.cache.delete(key);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @override
+ */
+ public delete(key: string): void {
+ this.cache.delete(key);
+ }
+
+ /**
+ * @override
+ */
+ public clear(): void {
+ this.cache.clear();
+ }
+
+ /**
+ * @override
+ */
+ public size(): number {
+ return this.cache.size;
+ }
+}
+
+export { MCRMemoryCache };
diff --git a/src/common/error.ts b/src/common/error.ts
new file mode 100644
index 0000000..1502508
--- /dev/null
+++ b/src/common/error.ts
@@ -0,0 +1,31 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+/**
+ * Handles an error by creating a new error message and throwing an error.
+ *
+ * @param message - A custom error message to prepend to the error
+ * @param error - The error object to handle. If the provided `error` is not an instance of `Error`, a generic message ('Unknown error') will be used
+ *
+ * @throws Always throws an `Error` with a detailed message, including the provided message and the error's message.
+ */
+const handleError = (message: string, error: unknown): never => {
+ throw new Error(`${message}: ${error instanceof Error ? error.message : 'Unknown error'}`);
+};
+
+export { handleError };
diff --git a/src/common/url.ts b/src/common/url.ts
new file mode 100644
index 0000000..73d5190
--- /dev/null
+++ b/src/common/url.ts
@@ -0,0 +1,56 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+import { handleError } from './error';
+
+/**
+ * Creates a `URL` object by combining a base URL, a path, query parameters and a fragment.
+ *
+ * @param baseUrl - The base URL, either as a `URL` object or a string
+ * @param path - An optional path to append to the base URL. Defaults to an empty string
+ * @param queryParams - An optional object containing key-value pairs for query parameters
+ * @param fragment - An optional fragment identifier (the part after `#`) to append to the URL
+ *
+ * @returns A `URL` object representing the constructed URL.
+ *
+ * @throws Will throw an error if the `baseUrl` or `path` is invalid.
+ */
+const createUrl = (
+ baseUrl: URL | string,
+ path?: string,
+ queryParams?: Record,
+ fragment?: string
+): URL => {
+ let url;
+ try {
+ url = (path) ? new URL(path, baseUrl) : new URL (baseUrl);
+ } catch (error) {
+ return handleError('Invalid URL input', error);
+ }
+ if (queryParams) {
+ Object.entries(queryParams).forEach(([key, value]) => {
+ url.searchParams.set(key, value.toString());
+ });
+ }
+ if (fragment) {
+ url.hash = `#${fragment}`;
+ }
+ return url;
+};
+
+export { createUrl };
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
new file mode 100644
index 0000000..5cd0529
--- /dev/null
+++ b/src/i18n/index.ts
@@ -0,0 +1,3 @@
+export * from './lang-service';
+export * from './lang-service-impl';
+
diff --git a/src/i18n/lang-service-impl.ts b/src/i18n/lang-service-impl.ts
new file mode 100644
index 0000000..1dd56d1
--- /dev/null
+++ b/src/i18n/lang-service-impl.ts
@@ -0,0 +1,148 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+import { MCRLangService } from './lang-service';
+import { MCRCache } from '../common/cache/cache';
+import { MCRMemoryCache } from '../common/cache/memory-cache';
+
+/**
+ * Implementation of the `MCRLangService` interface that provides language translation functionality.
+ *
+ * This class uses a specified base URL to fetch translations, and stores them in a cache for better performance.
+ * The class supports an optional in-memory cache implementation, but it can also accept any custom cache that implements the `MCRCache` interface.
+ */
+class MCRLangServiceImpl implements MCRLangService {
+ private baseUrl: URL;
+
+ private lang: string;
+
+ private cache: MCRCache;
+
+ /**
+ * Creates an instance of the `MCRLangServiceImpl` class.
+ *
+ * The constructor accepts a base URL, language, and an optional cache implementation. If no cache is provided,
+ * an in-memory cache (`MCRMemoryCache`) will be used by default.
+ *
+ * @param baseUrl - The base URL for the translation service
+ * @param lang - The language code (e.g., 'en', 'de', etc.) used for translations
+ * @param cache - An optional cache implementation that adheres to the `MCRCache` interface
+ */
+ constructor(
+ baseUrl: URL | string,
+ lang: string,
+ cache: MCRCache = new MCRMemoryCache()
+ ) {
+ this.baseUrl = new URL(baseUrl);
+ this.lang = lang;
+ this.cache = cache;
+ }
+
+ /**
+ * Fetches and caches translation data for a given prefix and stores it in the cache.
+ *
+ * @param prefix - The prefix used to fetch the translations
+ * @throws If the translation fetch operation fails, an error is thrown indicating the failure and the prefix.
+ * @returns A promise that resolves once the translations have been fetched and stored in the cache.
+ */
+ public cacheTranslations = async (prefix: string): Promise => {
+ let translationData: Record;
+ try {
+ translationData = await this.fetchTranslations(prefix);
+ } catch (error) {
+ throw new Error(`Failed to fetch properties for prefix ${prefix}: ${error as string}`);
+ }
+ Object.entries(translationData).forEach(([key, translation]: [string, string]) => {
+ this.cache.set(this.getLangKey(key), translation);
+ });
+ };
+
+ /**
+ * @override
+ */
+ public translate = async (
+ key: string,
+ params?: Record
+ ): Promise => {
+ let translation = null;
+ if (this.cache.has(this.getLangKey(key))) {
+ translation = this.cache.get(key);
+ } else {
+ try {
+ const translationData = await this.fetchTranslations(key);
+ if (translationData[key]) {
+ translation = key;
+ this.cache.set(this.getLangKey(key), translationData[key]);
+ }
+ } catch (error) {
+ console.error(`Error while fetching key ${key}`, error);
+ }
+ }
+ if (!translation) {
+ return `??${key}??`;
+ }
+ if (!params) {
+ return translation;
+ }
+ return this.replacePlaceholders(translation, params);
+ };
+
+ /**
+ * @override
+ */
+ public get currentLang(): string {
+ return this.lang;
+ };
+
+ /**
+ * Sets the current language for translations.
+ *
+ * @param newLang - The language code (e.g., 'en', 'de', etc.) to be set as the current language.
+ */
+ public set currentLang(newLang: string) {
+ this.lang = newLang;
+ };
+
+ private replacePlaceholders(translation: string, params: Record) {
+ return translation.replace(/{(\w+)}/g, (match: string, placeholder: string) => {
+ if (placeholder in params) {
+ return String(params[placeholder] ?? match);
+ }
+ return match;
+ });
+ };
+
+ private fetchTranslations = async (prefix: string): Promise> => {
+ let response: Response;
+ try {
+ response = await fetch(new URL(`rsc/locale/translate/${this.lang}/${prefix}`, this.baseUrl));
+ } catch {
+ throw new Error('Network error occurred');
+ }
+ if (!response.ok) {
+ throw new Error(`Failed to fetch props for ${prefix}. Status: ${String(response.status)}`);
+ }
+ return await response.json() as Record;
+ };
+
+ private getLangKey = (key: string): string => {
+ return `${this.lang}_${key}`;
+ };
+}
+
+export { MCRLangServiceImpl };
diff --git a/src/i18n/lang-service.ts b/src/i18n/lang-service.ts
new file mode 100644
index 0000000..409b9be
--- /dev/null
+++ b/src/i18n/lang-service.ts
@@ -0,0 +1,44 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+/**
+ * Interface for a language service that provides translation functionality.
+ */
+interface MCRLangService {
+
+ /**
+ * Translates a given key into the corresponding language string.
+ *
+ * The translation may include placeholders (e.g., `{name}`) which can be replaced by values
+ * provided in the `params` object. If `params` is not provided, an empty object is used.
+ *
+ * @param key - The key representing the string to be translated.
+ * @param params - A optional record of parameters to replace placeholders in the translation.
+ * @returns A promise that resolves to the translated string.
+ */
+ translate(key: string, params?: Record): Promise;
+
+ /**
+ * Gets the currently selected language code for the translations.
+ *
+ * @returns The language code representing the current language for translations
+ */
+ get currentLang(): string;
+}
+
+export { MCRLangService };
diff --git a/src/orcid/index.ts b/src/orcid/index.ts
new file mode 100644
index 0000000..e010919
--- /dev/null
+++ b/src/orcid/index.ts
@@ -0,0 +1,3 @@
+export * from './orcid-user.ts';
+export * from './orcid-work.ts';
+export * from './orcid-oauth';
diff --git a/src/orcid/orcid-oauth.ts b/src/orcid/orcid-oauth.ts
new file mode 100644
index 0000000..ba58579
--- /dev/null
+++ b/src/orcid/orcid-oauth.ts
@@ -0,0 +1,58 @@
+/*!
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+import { handleError } from '../common/error';
+import { createUrl } from '../common/url';
+
+/**
+ * Generates the URL for initializing the ORCID OAuth process.
+ *
+ * @param baseUrl - The base URL for the ORCID OAuth initiation URL.
+ * @param scope - An optional scope parameter that defines the level of access requested during OAuth authentication
+ * @returns The constructed URL for initiating the ORCID OAuth process
+ */
+const getOrcidOAuthInitUrl = (baseUrl: URL | string, scope?: string): URL => {
+ if (scope) {
+ return createUrl(baseUrl, 'rsc/orcid/oauth/init', { 'scope': scope });
+ }
+ return createUrl(baseUrl, 'rsc/orcid/oauth/init');
+};
+
+// TODO add access token
+/**
+ * Revokes the OAuth authorization for a given ORCID.
+ *
+ * @param baseUrl - The base URL to which the revoke request is sent
+ * @param orcid - The ORCID of the user whose OAuth authorization is to be revoked
+ * @throws If the fetch operation fails or if the response indicates failure
+ * @returns A promise that resolves when the revoke operation is completed successfully
+ */
+const revokeOrcidOAuth = async (baseUrl: URL | string, orcid: string): Promise => {
+ const url = createUrl(baseUrl, `rsc/orcid/oauth/${orcid}`);
+ let response: Response;
+ try {
+ response = await fetch(url, { method: 'DELETE' });
+ } catch (error) {
+ return handleError(`Error during fetch for ORCID revoke: ${orcid}`, error);
+ }
+ if (!response.ok) {
+ throw new Error(`Failed to revoke ORCID OAuth: ${String(response.status)}`);
+ }
+};
+
+export { getOrcidOAuthInitUrl, revokeOrcidOAuth };
diff --git a/src/orcid/orcid-user.ts b/src/orcid/orcid-user.ts
new file mode 100644
index 0000000..b6ea754
--- /dev/null
+++ b/src/orcid/orcid-user.ts
@@ -0,0 +1,163 @@
+/*!
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+import { handleError } from '../common/error';
+
+/**
+ * Interface representing the status of an ORCID user.
+ */
+interface MCROrcidUserStatus {
+
+ /**
+ * A list of ORCIDs associated with the user.
+ */
+ orcids: string[];
+
+ /**
+ * A list of trusted ORCIDs associated with the user.
+ */
+ trustedOrcids: string[];
+}
+
+/**
+ * Interface representing the settings for an ORCID user.
+ */
+interface MCROrcidUserSettings {
+ /**
+ * A flag indicating if works should always be updated.
+ * `null` means that no preference has been set.
+ */
+ isAlwaysUpdateWork: boolean | null;
+
+ /**
+ * A flag indicating if duplicate works should be created.
+ * `null` means that no preference has been set.
+ */
+ isCreateDuplicateWork: boolean | null;
+
+ /**
+ * A flag indicating if the first work should be created.
+ * `null` means that no preference has been set.
+ */
+ isCreateFirstWork: boolean | null;
+
+ /**
+ * A flag indicating if deleted works should be recreated.
+ * `null` means that no preference has been set.
+ */
+ isRecreateDeletedWork: boolean | null;
+}
+
+/**
+ * Service for interacting with ORCID user status and settings.
+ */
+class MCROrcidUserService {
+
+ private baseUrl: URL;
+
+ /**
+ * Creates an instance of the MCROrcidUserService.
+ *
+ * @param baseUrl - The base URL to be used for making API requests to the ORCID service.
+ */
+ constructor(baseUrl: URL) {
+ this.baseUrl = new URL(baseUrl);
+ }
+
+ /**
+ * Fetches the status of the ORCID user.
+ *
+ * @param accessToken - The access token to authorize the request
+ * @returns A promise that resolves to an `MCROrcidUserStatus` object containing the user status
+ * @throws If the fetch operation fails or if the response is not successful
+ */
+ public fetchOrcidUserStatus = async (accessToken: string): Promise => {
+ let response: Response;
+ try {
+ response = await fetch(new URL('api/orcid/v1/user-status', this.baseUrl), {
+ headers: { Authorization: `Bearer ${accessToken}`}
+ });
+ } catch (error) {
+ return handleError('Error while fetching Orcid user status', error);
+ }
+ if (!response.ok) {
+ throw new Error(`Failed to fetch Orcid user status: ${String(response.status)}`);
+ }
+ return await response.json() as MCROrcidUserStatus;
+ };
+
+ /**
+ * Fetches the settings for an ORCID user.
+ *
+ * @param accessToken - The access token to authorize the request
+ * @param orcid - The ORCID of the user whose settings are being fetched
+ * @returns A promise that resolves to an `MCROrcidUserSettings` object containing the user's settings
+ * @throws If the fetch operation fails or if the response is not successful
+ */
+ public fetchOrcidUserSettings = async (
+ accessToken: string,
+ orcid: string
+ ): Promise => {
+ let response: Response;
+ try {
+ response = await fetch(`${this.baseUrl}api/orcid/v1/user-properties/${orcid}`, {
+ headers: { Authorization: `Bearer ${accessToken}`},
+ });
+ } catch (error) {
+ return handleError('Error while fetching Orcid user settings', error);
+ }
+ if (!response.ok) {
+ throw new Error(`Failed to fetch ORCID user settings for ${orcid}.`);
+ }
+ return await response.json() as MCROrcidUserSettings;
+ };
+
+ /**
+ * Updates the settings for an ORCID user.
+ *
+ * @param accessToken - The access token to authorize the request
+ * @param orcid - The ORCID of the user whose settings are being updated
+ * @param settings - An object containing the new settings for the user
+ * @returns A promise that resolves when the settings are successfully updated
+ * @throws If the fetch operation fails or if the response is not successful
+ */
+ public updateOrcidUserSettings = async (
+ accessToken: string,
+ orcid: string,
+ settings: MCROrcidUserSettings
+ ): Promise => {
+ let response: Response;
+ try {
+ response = await fetch(`${this.baseUrl}api/orcid/v1/user-properties/${orcid}`, {
+ method: 'PUT',
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(settings),
+ });
+ } catch (error) {
+ return handleError('Error while fetching Orcid user settings', error);
+ }
+ if (!response.ok) {
+ throw new Error(`Failed to update ORCID user settings for ${orcid}.`);
+ }
+ };
+}
+
+export { MCROrcidUserService, MCROrcidUserStatus, MCROrcidUserSettings };
diff --git a/src/orcid/orcid-work.ts b/src/orcid/orcid-work.ts
new file mode 100644
index 0000000..2a3444b
--- /dev/null
+++ b/src/orcid/orcid-work.ts
@@ -0,0 +1,129 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+import { handleError } from '../common/error';
+
+/**
+ * Interface representing the status of an ORCID work.
+ *
+ * The `MCROrcidWorkStatus` interface describes the status of a work, including whether the work
+ * belongs to the user (`own`) and any other associated works (`other`).
+ */
+interface MCROrcidWorkStatus {
+
+ /**
+ * The put code string of the work owned by the user.
+ * If no status is available, this will be `null`.
+ */
+ own: string | null;
+
+ /**
+ * A list of put code string for other works associated with the user.
+ */
+ other: string[];
+}
+
+/**
+ * A service for interacting with ORCID works, including fetching work status and exporting objects.
+ *
+ * This service allows you to fetch the status of a work by its `objectId` and ORCID, and to export
+ * works to ORCID. It can operate in both "member" and "public" modes.
+ */
+class MCROrcidWorkService {
+
+ private baseUrl: URL;
+
+ /**
+ * Creates an instance of the MCROrcidWorkService.
+ *
+ * @param baseUrl - The base URL to be used for making API requests to the ORCID service.
+ */
+ constructor(baseUrl: URL | string) {
+ this.baseUrl = new URL(baseUrl);
+ }
+
+ /**
+ * Fetches the status of a work for a specific ORCID and object ID.
+ *
+ * This method fetches the status of a work (owned by the user or other associated works) using
+ * the provided access token, ORCID, and object ID. It can operate in "member" or "public" mode,
+ * depending on the `useMember` flag.
+ *
+ * @param accessToken - The access token to authorize the request
+ * @param orcid - The ORCID of the user for whom the work status is to be fetched
+ * @param objectId - The object ID of the work whose status is being requested
+ * @param useMember - A boolean flag indicating whether to fetch in "member" mode (`true`) or "public" mode (`false`)
+ * @returns A promise that resolves to an `MCROrcidWorkStatus` object containing the status of the work
+ * @throws If the fetch operation fails or if the response is not successful
+ */
+ public fetchWorkStatus = async (
+ accessToken: string,
+ orcid: string,
+ objectId: string,
+ useMember = false
+ ): Promise => {
+ const mode = useMember ? 'member' : 'public';
+ const url = new URL(`api/orcid/v1/${mode}/${orcid}/works/object/${objectId}`, this.baseUrl);
+ let response: Response;
+ try {
+ response = await fetch(url, {
+ headers: { Authorization: `Bearer ${accessToken}`}
+ });
+ } catch (error) {
+ return handleError('Failed to fetch work status', error);
+ }
+ if (!response.ok) {
+ throw new Error(`Failed to fetch work status for ${objectId}.`);
+ }
+ return await response.json() as MCROrcidWorkStatus;
+ };
+
+ /**
+ * Exports an object to ORCID for a specific user and object ID.
+ *
+ * This method sends a POST request to export the specified object to ORCID for the provided ORCID.
+ * It requires an OAuth access token for authorization.
+ *
+ * @param accessToken - The access token to authorize the request
+ * @param orcid - The ORCID of the user to whom the object should be exported
+ * @param objectId - The object ID of the work to be exported
+ * @returns A promise that resolves when the export operation is completed
+ * @throws If the fetch operation fails or if the response is not successful
+ */
+ public exportObjectToOrcid = async (
+ accessToken: string,
+ orcid: string,
+ objectId: string
+ ): Promise => {
+ const url = new URL(`api/orcid/v1/member/${orcid}/works/object/${objectId}`, this.baseUrl);
+ let response: Response;
+ try {
+ response = await fetch(url, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${accessToken}`},
+ });
+ } catch (error) {
+ return handleError('Failed to request export', error);
+ }
+ if (!response.ok) {
+ throw new Error(`Failed to export ${objectId} to ${orcid}.`);
+ }
+ };
+}
+
+export { MCROrcidWorkStatus, MCROrcidWorkService };