Convert Server from submodule to normal files
This commit is contained in:
339
Server/src/services/accountsService.js
Normal file
339
Server/src/services/accountsService.js
Normal file
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* Accounts Service
|
||||
* CRUD-Operationen für Accounts aus Appwrite
|
||||
* Verwaltet nur "Managed Accounts" (account_owner_user_id == authUserId)
|
||||
*/
|
||||
|
||||
import { databases, databaseId, accountsCollectionId } from "../lib/appwrite";
|
||||
import { ID, Query } from "appwrite";
|
||||
|
||||
/**
|
||||
* Lädt ein einzelnes Account nach ID
|
||||
* @param {string} accountId - ID des Accounts
|
||||
* @returns {Promise<Object|null>} Account-Dokument oder null
|
||||
*/
|
||||
export async function getAccountById(accountId) {
|
||||
if (!accountId) {
|
||||
console.warn("getAccountById: accountId fehlt");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const document = await databases.getDocument(
|
||||
databaseId,
|
||||
accountsCollectionId,
|
||||
accountId
|
||||
);
|
||||
return document;
|
||||
} catch (e) {
|
||||
// 404 bedeutet, dass das Dokument nicht existiert
|
||||
if (e.code === 404 || e.type === 'document_not_found') {
|
||||
return null;
|
||||
}
|
||||
console.error("Fehler beim Laden des Accounts:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leitet den Market-Code aus einem Account ab
|
||||
* @param {Object} account - Account-Dokument
|
||||
* @returns {string} Market-Code (z.B. "DE", "US", "UNKNOWN")
|
||||
*/
|
||||
export function deriveMarketFromAccount(account) {
|
||||
if (!account) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// Priority 1: account_platform_market Feld vorhanden
|
||||
if (account.account_platform_market && account.account_platform_market.trim()) {
|
||||
return account.account_platform_market.trim().toUpperCase();
|
||||
}
|
||||
|
||||
// Priority 2: Ableitung aus account_url Hostname
|
||||
if (account.account_url) {
|
||||
try {
|
||||
const url = new URL(account.account_url);
|
||||
const hostname = url.hostname.toLowerCase();
|
||||
|
||||
// Mapping von Hostname zu Market-Code
|
||||
const hostnameToMarket = {
|
||||
"ebay.de": "DE",
|
||||
"www.ebay.de": "DE",
|
||||
"ebay.com": "US",
|
||||
"www.ebay.com": "US",
|
||||
"ebay.co.uk": "UK",
|
||||
"www.ebay.co.uk": "UK",
|
||||
"ebay.fr": "FR",
|
||||
"www.ebay.fr": "FR",
|
||||
"ebay.it": "IT",
|
||||
"www.ebay.it": "IT",
|
||||
"ebay.es": "ES",
|
||||
"www.ebay.es": "ES",
|
||||
"ebay.ca": "CA",
|
||||
"www.ebay.ca": "CA",
|
||||
"ebay.com.au": "AU",
|
||||
"www.ebay.com.au": "AU",
|
||||
};
|
||||
|
||||
if (hostnameToMarket[hostname]) {
|
||||
return hostnameToMarket[hostname];
|
||||
}
|
||||
|
||||
// Fallback: Prüfe ob hostname ein bekanntes Pattern enthält
|
||||
if (hostname.includes("ebay.de")) return "DE";
|
||||
if (hostname.includes("ebay.com") && !hostname.includes("ebay.com.au")) return "US";
|
||||
if (hostname.includes("ebay.co.uk")) return "UK";
|
||||
if (hostname.includes("ebay.fr")) return "FR";
|
||||
if (hostname.includes("ebay.it")) return "IT";
|
||||
if (hostname.includes("ebay.es")) return "ES";
|
||||
if (hostname.includes("ebay.ca")) return "CA";
|
||||
if (hostname.includes("ebay.com.au")) return "AU";
|
||||
} catch (e) {
|
||||
// URL parsing fehlgeschlagen
|
||||
console.debug("Fehler beim Parsen der Account-URL:", e);
|
||||
}
|
||||
}
|
||||
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
/**
|
||||
* Leitet die Währung aus einem Market-Code ab
|
||||
* @param {string} market - Market-Code (z.B. "DE", "US", "UK")
|
||||
* @returns {string} Währungscode (z.B. "EUR", "USD", "GBP")
|
||||
*/
|
||||
export function deriveCurrencyFromMarket(market) {
|
||||
if (!market || market === "UNKNOWN") {
|
||||
return "EUR"; // Fallback
|
||||
}
|
||||
|
||||
const marketUpper = market.toUpperCase();
|
||||
|
||||
// Mapping Market -> Currency
|
||||
const marketToCurrency = {
|
||||
// EUR Länder
|
||||
"DE": "EUR",
|
||||
"FR": "EUR",
|
||||
"IT": "EUR",
|
||||
"ES": "EUR",
|
||||
"NL": "EUR",
|
||||
"AT": "EUR",
|
||||
"BE": "EUR",
|
||||
"IE": "EUR",
|
||||
// Andere Währungen
|
||||
"US": "USD",
|
||||
"UK": "GBP",
|
||||
"CA": "CAD",
|
||||
"AU": "AUD",
|
||||
};
|
||||
|
||||
return marketToCurrency[marketUpper] || "EUR"; // Fallback zu EUR
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt alle Managed Accounts für einen User
|
||||
* @param {string} authUserId - ID des eingeloggten Users
|
||||
* @returns {Promise<Array>} Array von Account-Dokumenten
|
||||
*/
|
||||
export async function fetchManagedAccounts(authUserId) {
|
||||
if (!authUserId) {
|
||||
console.warn("fetchManagedAccounts: authUserId fehlt");
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await databases.listDocuments(
|
||||
databaseId,
|
||||
accountsCollectionId,
|
||||
[
|
||||
Query.equal("account_owner_user_id", authUserId),
|
||||
Query.orderDesc("$createdAt"),
|
||||
]
|
||||
);
|
||||
|
||||
return response.documents;
|
||||
} catch (e) {
|
||||
console.error("Fehler beim Laden der Accounts:", e);
|
||||
|
||||
// Wenn Collection nicht existiert oder Berechtigungen fehlen
|
||||
if (e.code === 404 || e.code === 401) {
|
||||
return [];
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein neues Managed Account
|
||||
* @param {string} authUserId - ID des eingeloggten Users
|
||||
* @param {Object} accountData - Account-Daten
|
||||
* @param {string} accountData.account_platform_market - Required: Market (z.B. "DE", "US")
|
||||
* @param {string} accountData.account_platform_account_id - Required: Platform Account ID
|
||||
* @param {string} [accountData.account_shop_name] - Optional: Shop-Name
|
||||
* @param {string} [accountData.account_url] - Optional: Account URL
|
||||
* @param {string} [accountData.account_status] - Optional: Status (z.B. "active", "unknown", "disabled")
|
||||
* @returns {Promise<Object>} Erstelltes Account-Dokument
|
||||
*/
|
||||
export async function createManagedAccount(authUserId, accountData) {
|
||||
if (!authUserId) {
|
||||
throw new Error("authUserId ist erforderlich");
|
||||
}
|
||||
|
||||
// Validierung der Required-Felder
|
||||
const { account_platform_market, account_platform_account_id } = accountData;
|
||||
|
||||
// account_platform wird IMMER "ebay" gesetzt (eBay-only Tool)
|
||||
if (!account_platform_market) {
|
||||
throw new Error("account_platform_market ist erforderlich");
|
||||
}
|
||||
if (!account_platform_account_id) {
|
||||
throw new Error("account_platform_account_id ist erforderlich");
|
||||
}
|
||||
|
||||
// Payload zusammenstellen
|
||||
const payload = {
|
||||
account_owner_user_id: authUserId,
|
||||
account_platform: "ebay", // IMMER "ebay" für über UI erstellte Accounts
|
||||
account_platform_market,
|
||||
account_platform_account_id,
|
||||
account_shop_name: accountData.account_shop_name || null,
|
||||
account_url: accountData.account_url || null,
|
||||
account_sells: accountData.account_sells ?? null,
|
||||
account_managed: true, // Immer true für über die UI erstellte Accounts
|
||||
};
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:72',message:'createManagedAccount: payload before Appwrite',data:{account_sells:payload.account_sells,accountData_account_sells:accountData.account_sells},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'E'})}).catch(()=>{});
|
||||
// #endregion
|
||||
|
||||
// account_status ist optional - aufgrund Schema-Konflikt vorerst weglassen
|
||||
// TODO: Schema in Appwrite prüfen und korrigieren (Enum-Feld sollte String akzeptieren, nicht Array)
|
||||
// Das Feld wird erst wieder hinzugefügt, wenn das Schema korrekt konfiguriert ist
|
||||
|
||||
// Document-Level Permissions für den User setzen
|
||||
const permissions = [
|
||||
`read("user:${authUserId}")`,
|
||||
`update("user:${authUserId}")`,
|
||||
`delete("user:${authUserId}")`,
|
||||
];
|
||||
|
||||
try {
|
||||
const document = await databases.createDocument(
|
||||
databaseId,
|
||||
accountsCollectionId,
|
||||
ID.unique(),
|
||||
payload,
|
||||
permissions
|
||||
);
|
||||
|
||||
return document;
|
||||
} catch (e) {
|
||||
// Duplicate-Conflict behandeln (falls Unique-Index auf platform+market+platform_account_id existiert)
|
||||
if (e.code === 409 || e.message?.includes("duplicate") || e.message?.includes("unique")) {
|
||||
throw new Error(
|
||||
"Dieser Account ist bereits verbunden."
|
||||
);
|
||||
}
|
||||
|
||||
console.error("Fehler beim Erstellen des Accounts:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert ein Managed Account (optional für v1)
|
||||
* @param {string} accountId - ID des Accounts
|
||||
* @param {Object} accountData - Zu aktualisierende Account-Daten
|
||||
* @returns {Promise<Object>} Aktualisiertes Account-Dokument
|
||||
*/
|
||||
export async function updateManagedAccount(accountId, accountData) {
|
||||
if (!accountId) {
|
||||
throw new Error("accountId ist erforderlich");
|
||||
}
|
||||
|
||||
try {
|
||||
// Entferne undefined/null Werte aus accountData (behalte nur geänderte Felder)
|
||||
const payload = {};
|
||||
Object.keys(accountData).forEach((key) => {
|
||||
if (accountData[key] !== undefined && accountData[key] !== null) {
|
||||
payload[key] = accountData[key];
|
||||
}
|
||||
});
|
||||
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:133',message:'updateManagedAccount: before updateDocument',data:{accountId,payload,payloadKeys:Object.keys(payload)},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
|
||||
// #endregion
|
||||
|
||||
const document = await databases.updateDocument(
|
||||
databaseId,
|
||||
accountsCollectionId,
|
||||
accountId,
|
||||
payload
|
||||
);
|
||||
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:147',message:'updateManagedAccount: success',data:{accountId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
|
||||
// #endregion
|
||||
return document;
|
||||
} catch (e) {
|
||||
console.error("Fehler beim Aktualisieren des Accounts:", e);
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:149',message:'updateManagedAccount: error',data:{accountId,errorCode:e.code,errorMessage:e.message,errorType:e.type},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{});
|
||||
// #endregion
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert das account_last_scan_at Feld eines Accounts
|
||||
* @param {string} accountId - ID des Accounts
|
||||
* @param {string} isoString - ISO 8601 Datum-String (z.B. "2026-01-18T12:34:56.000Z")
|
||||
* @returns {Promise<Object>} Aktualisiertes Account-Dokument
|
||||
*/
|
||||
export async function updateAccountLastScanAt(accountId, isoString) {
|
||||
if (!accountId) {
|
||||
throw new Error("accountId ist erforderlich");
|
||||
}
|
||||
if (!isoString) {
|
||||
throw new Error("isoString ist erforderlich");
|
||||
}
|
||||
|
||||
try {
|
||||
const document = await databases.updateDocument(
|
||||
databaseId,
|
||||
accountsCollectionId,
|
||||
accountId,
|
||||
{
|
||||
account_last_scan_at: isoString,
|
||||
}
|
||||
);
|
||||
|
||||
return document;
|
||||
} catch (e) {
|
||||
console.error("Fehler beim Aktualisieren des account_last_scan_at:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht ein Managed Account (optional für v1)
|
||||
* @param {string} accountId - ID des Accounts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function deleteManagedAccount(accountId) {
|
||||
if (!accountId) {
|
||||
throw new Error("accountId ist erforderlich");
|
||||
}
|
||||
|
||||
try {
|
||||
await databases.deleteDocument(
|
||||
databaseId,
|
||||
accountsCollectionId,
|
||||
accountId
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Fehler beim Löschen des Accounts:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user