commit e73ddd52ad532f099f30ff16cef56b0ca9f5db83 Author: Kenso Grimm Date: Sat Jan 17 17:07:46 2026 +0100 Sure! Pl diff --git a/.cursor/rules/setup.mdc b/.cursor/rules/setup.mdc new file mode 100644 index 0000000..4a4f964 --- /dev/null +++ b/.cursor/rules/setup.mdc @@ -0,0 +1,36 @@ +--- +name: setup +description: Alle Setup-, Konfigurations- und temporären Dateien gehören in den setup/ Ordner +--- + +# Setup-Dateien Organisation + +## Regel + +Alle Setup-, Konfigurations- und temporären Dateien müssen im `setup/` Ordner gespeichert werden, nicht im Root-Verzeichnis oder in Code-Ordner wie `Extension/` oder `Server/`. + +## Beispiele für Setup-Dateien + +- `db.txt` - Datenbank-Schema-Skripte +- `setup-*.ps1` - PowerShell-Setup-Skripte +- `*-SETUP.md`, `*_SETUP.md` - Setup-Dokumentation +- `appwrite.config.json` - Appwrite-Konfigurationsdateien +- Temporäre Konfigurationsdateien +- Setup-Anleitungen und Notizen + +## Projektstruktur + +``` +eship/ +├── Extension/ # Browser-Extension (Code) +├── Server/ # React Server-App (Code) +├── setup/ # Setup & Konfiguration (hierhin gehören alle Setup-Dateien) +└── README.md # Haupt-Projekt-README +``` + +## Zweck + +Diese Regel stellt sicher, dass: +- Der Code in `Extension/` und `Server/` sauber und klar bleibt +- Setup- und Konfigurationsdateien zentral organisiert sind +- Das Projekt übersichtlich und gut strukturiert ist diff --git a/Extension/README.md b/Extension/README.md new file mode 100644 index 0000000..bba79c5 --- /dev/null +++ b/Extension/README.md @@ -0,0 +1,189 @@ +# EShip Browser Extension + +Chrome Extension (Manifest V3) fuer die Authentifizierung und Tool-Verwaltung der EShip Web-App. + +## Funktionen + +- **Authentifizierung**: Login via Appwrite (Email/Password) +- **Website-Schutz**: Blockiert die Web-App fuer nicht-authentifizierte Benutzer +- **Tools-System**: Aktivierbare Tools mit individuellen Einstellungen +- **Live-Updates**: Einstellungen werden sofort auf der Website angewendet + +## Projektstruktur + +``` +Extension/ +├── manifest.json # Chrome Extension Manifest (MV3) +├── config.js # Appwrite Konfiguration +├── background/ +│ └── service-worker.js # Background Service Worker +├── popup/ +│ ├── popup.html # Popup UI +│ ├── popup.css # Popup Styles +│ └── popup.js # Popup Logic +├── content/ +│ └── content-script.js # Content Script fuer Website +└── lib/ + └── appwrite.min.js # Appwrite SDK Bundle +``` + +## Installation + +### 1. Appwrite API Key erstellen + +**WICHTIG**: Du musst einen API Key in Appwrite erstellen: + +1. Gehe zu https://cloud.appwrite.io +2. Waehle dein Projekt +3. Settings > API Keys > "Create API Key" +4. Scopes: `users.read`, `users.write`, `sessions.write` +5. Kopiere den API Key (wird nur einmal angezeigt!) + +Siehe auch: `setup/API_KEY_SETUP.md` fuer detaillierte Anleitung. + +### 2. Extension konfigurieren + +Bearbeite `Extension/config.js`: + +```javascript +var APPWRITE_CONFIG = { + endpoint: 'https://cloud.appwrite.io/v1', + projectId: '696b82bb0036d2e547ad', + apiKey: 'DEIN_API_KEY_HIER' // WICHTIG: API Key hier einfuegen +}; +``` + +### 3. Extension in Chrome laden + +1. Oeffne Chrome und navigiere zu `chrome://extensions` +2. Aktiviere den **Entwicklermodus** (Toggle oben rechts) +3. Klicke auf **Entpackte Erweiterung laden** +4. Waehle den `Extension/` Ordner aus +5. Die Extension erscheint in der Toolbar + +### 4. Server starten + +```bash +cd Server +npm install +npm run dev +``` + +Der Server startet unter `http://localhost:5173` + +## Testen + +### Auth-Flow testen + +1. **Ohne Login**: + - Oeffne `http://localhost:5173` + - Die Seite zeigt einen "Zugriff gesperrt" Bildschirm + +2. **Login durchfuehren**: + - Klicke auf das Extension-Icon in der Toolbar + - Gib deine Appwrite-Zugangsdaten ein + - Klicke auf "Anmelden" + - Nach erfolgreichem Login wird die Website automatisch geoeffnet + +3. **Nach Login**: + - Die Website ist voll zugaenglich + - Das Extension-Popup zeigt das Tools-Menu + +### Tools testen + +1. Oeffne das Extension-Popup (nach Login) +2. Aktiviere das Tool "Preise hervorheben" +3. Auf der Website werden alle `.price` Elemente mit rotem Rahmen hervorgehoben +4. Aendere den Selector oder die Farbe in den Tool-Einstellungen +5. Die Aenderungen werden sofort angewendet + +### Demo-Preise + +Die Web-App enthaelt Demo-Preiselemente in der unteren rechten Ecke zum Testen des Highlight-Tools. + +## Tools-Registry + +Tools werden in `background/service-worker.js` definiert: + +```javascript +const DEFAULT_TOOLS = [ + { + id: 'highlight_prices', + name: 'Preise hervorheben', + enabled: false, + settings: { + selector: '.price', + borderColor: '#ff0000', + borderWidth: '2px' + } + } +]; +``` + +### Neues Tool hinzufuegen + +1. Fuege das Tool zur `DEFAULT_TOOLS` Liste hinzu +2. Implementiere die Logik in `content/content-script.js`: + ```javascript + function applyTool(tool) { + switch (tool.id) { + case 'dein_tool_id': + applyDeinTool(tool.settings); + break; + } + } + ``` + +## Message-Kommunikation + +### Popup <-> Service Worker + +| Action | Beschreibung | +|--------|--------------| +| `CHECK_AUTH` | Prueft Auth-Status | +| `LOGIN` | Login mit Email/Password | +| `LOGOUT` | Beendet Session | +| `GET_SETTINGS` | Laedt Tool-Einstellungen | +| `SAVE_SETTINGS` | Speichert Tool-Einstellungen | + +### Service Worker <-> Content Script + +| Action | Beschreibung | +|--------|--------------| +| `SETTINGS_UPDATED` | Neue Tool-Einstellungen | +| `AUTH_CHANGED` | Auth-Status geaendert | + +## Sicherheit + +- Passwoerter werden **nie** im Klartext gespeichert +- Authentifizierung laeuft vollstaendig ueber Appwrite Sessions +- Session-Cookies sind HTTP-only und werden von Appwrite verwaltet +- Tool-Einstellungen werden in `chrome.storage.sync` gespeichert + +## Fehlerbehebung + +### Extension funktioniert nicht + +1. Pruefe die Browser-Konsole auf Fehler +2. Oeffne `chrome://extensions` und klicke auf "Service Worker" bei der Extension +3. Pruefe die Konsole des Service Workers + +### Login schlaegt fehl + +1. Pruefe die Appwrite-Konfiguration in `config.js` +2. Stelle sicher, dass der **API Key** gesetzt ist +3. Pruefe, ob der API Key die richtigen Scopes hat (`users.read`, `users.write`, `sessions.write`) +4. Stelle sicher, dass der Appwrite-Benutzer existiert +5. Pruefe, ob die Appwrite-Plattform-Einstellungen den Extension-Zugriff erlauben + +### Website bleibt blockiert + +1. Lade die Website neu nach dem Login +2. Pruefe, ob die URL in `manifest.json` unter `content_scripts.matches` steht +3. Pruefe die Content-Script-Konsole (F12 auf der Website) + +## Bekannte Einschraenkungen + +- Die Extension funktioniert nur auf den konfigurierten URLs (localhost:5173, localhost:3000) +- Fuer Produktions-URLs muss `manifest.json` angepasst werden +- Icons sind nicht enthalten (Chrome verwendet Standard-Icon) diff --git a/Extension/background/service-worker.js b/Extension/background/service-worker.js new file mode 100644 index 0000000..feea162 --- /dev/null +++ b/Extension/background/service-worker.js @@ -0,0 +1,419 @@ +// EShip Extension - Background Service Worker +// Handles Appwrite authentication and message passing + +console.log('[SW] Service Worker starting...'); + +// Define APPWRITE_CONFIG directly (fallback if config.js fails to load) +var APPWRITE_CONFIG = { + endpoint: 'https://appwrite.webklar.com/v1', + projectId: '696b82bb0036d2e547ad', + apiKey: 'standard_d48c6eebe825b55e685d8e66ea056161105470702da77b730aca08c106ffbadfa2375ff675dbe9e01d7bb72b4a9fa001ff7c365b73759bc5fb3da432c3cd9cee1151e67517e9838d1f96f942d9891ce66ddc6f11c0fdd67a24f7c84e0fa9999a74dacf2c6aa3533998c177f190fc87ffb5a30b27474be21aece4c70d71d205ba' +}; + +// Global state +let client, account; +let scriptsLoaded = false; +let initError = null; +let DEFAULT_TOOLS = [ + { + id: 'highlight_prices', + name: 'Preise hervorheben', + enabled: false, + settings: { + selector: '.price', + borderColor: '#ff0000', + borderWidth: '2px' + } + } +]; + +// Register message listener FIRST - before any initialization +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log('[SW] Message received:', request.action); + + // PING handler - synchronous response + if (request.action === 'PING') { + console.log('[SW] PING received, responding'); + sendResponse({ success: true, message: 'Service Worker is alive' }); + return false; // Synchronous response + } + + // Handle other messages asynchronously + (async () => { + try { + // Ensure scripts are loaded (synchronous operation) + if (!scriptsLoaded) { + loadScripts(); + } + + // Ensure Appwrite is initialized + if (!client || !account) { + initAppwrite(); + } + + // Handle the message + const response = await handleMessage(request, sender); + console.log('[SW] Sending response:', response); + sendResponse(response); + } catch (error) { + console.error('[SW] Error handling message:', error); + sendResponse({ + success: false, + error: error.message || 'Unbekannter Fehler' + }); + } + })(); + + return true; // Keep channel open for async +}); + +// Load scripts - try relative path first, then fallback to inline +function loadScripts() { + if (scriptsLoaded) return; + + try { + console.log('[SW] Loading scripts...'); + + // Try loading config first (smaller file) - will override default if successful + try { + importScripts('../config.js'); + console.log('[SW] config.js loaded (overriding default)'); + } catch (e) { + console.error('[SW] Failed to load config.js:', e); + console.log('[SW] Using default inline config'); + } + + // Try loading Appwrite SDK + try { + importScripts('../lib/appwrite.min.js'); + console.log('[SW] appwrite.min.js loaded via importScripts'); + } catch (e) { + console.error('[SW] Failed to load appwrite.min.js via importScripts:', e); + // Load Appwrite SDK inline + loadAppwriteSDKInline(); + console.log('[SW] appwrite.min.js loaded inline'); + } + + scriptsLoaded = true; + console.log('[SW] Scripts loaded successfully'); + } catch (error) { + console.error('[SW] Failed to load scripts:', error); + throw error; + } +} + +// Load Appwrite SDK inline if importScripts fails +function loadAppwriteSDKInline() { + if (typeof Appwrite !== 'undefined') return; + + (function(global) { + 'use strict'; + const Appwrite = {}; + + class Client { + constructor() { + this.config = { endpoint: 'https://appwrite.webklar.com/v1', project: '' }; + this.headers = { + 'content-type': 'application/json', + 'x-sdk-name': 'Chrome Extension', + 'x-sdk-platform': 'client', + 'x-sdk-language': 'web', + 'x-sdk-version': '21.0.0', + }; + } + setEndpoint(endpoint) { this.config.endpoint = endpoint; return this; } + setProject(project) { this.config.project = project; this.headers['x-appwrite-project'] = project; return this; } + setKey(key) { if (key) this.headers['x-appwrite-key'] = key; else delete this.headers['x-appwrite-key']; return this; } + async call(method, path, headers = {}, params = {}) { + const url = new URL(this.config.endpoint + path); + const options = { method: method.toUpperCase(), headers: { ...this.headers, ...headers }, credentials: 'include' }; + if (method === 'GET') { + for (const [key, value] of Object.entries(params)) { + if (value !== undefined) url.searchParams.append(key, value); + } + } else { + options.body = JSON.stringify(params); + } + const response = await fetch(url.toString(), options); + const contentType = response.headers.get('content-type') || ''; + let data = contentType.includes('application/json') ? await response.json() : await response.text(); + if (response.status >= 400) { + throw new AppwriteException(data.message || 'Unknown error', response.status, data.type || '', data); + } + return data; + } + } + + class Account { + constructor(client) { this.client = client; } + async get() { return await this.client.call('GET', '/account'); } + async createEmailPasswordSession(email, password) { + return await this.client.call('POST', '/account/sessions/email', {}, { email, password }); + } + async deleteSession(sessionId) { return await this.client.call('DELETE', `/account/sessions/${sessionId}`); } + async getSession(sessionId) { return await this.client.call('GET', `/account/sessions/${sessionId}`); } + } + + class AppwriteException extends Error { + constructor(message, code = 0, type = '', response = null) { + super(message); + this.name = 'AppwriteException'; + this.code = code; + this.type = type; + this.response = response; + } + } + + Appwrite.Client = Client; + Appwrite.Account = Account; + Appwrite.AppwriteException = AppwriteException; + global.Appwrite = Appwrite; + })(typeof self !== 'undefined' ? self : this); +} + +// Initialize Appwrite +// Note: For user authentication, we don't use API key (only for server operations) +function initAppwrite() { + try { + if (!scriptsLoaded) { + throw new Error('Scripts nicht geladen'); + } + if (typeof Appwrite === 'undefined') { + throw new Error('Appwrite SDK nicht geladen'); + } + + // APPWRITE_CONFIG should be available (defined at top of file or loaded from config.js) + if (typeof APPWRITE_CONFIG === 'undefined') { + throw new Error('APPWRITE_CONFIG nicht definiert'); + } + + const config = APPWRITE_CONFIG; + console.log('[SW] Using config:', { + endpoint: config.endpoint, + projectId: config.projectId + }); + + // Initialize client WITHOUT API key for user authentication + // API key is only for server-side operations, not user login + client = new Appwrite.Client(); + client + .setEndpoint(config.endpoint) + .setProject(config.projectId); + // Do NOT set API key here - it conflicts with user sessions + + console.log('[SW] Client configured with project:', config.projectId); + console.log('[SW] Note: API Key not set (only for server operations, not user auth)'); + + account = new Appwrite.Account(client); + initError = null; + console.log('[SW] Appwrite Client initialized (session-based, no API key)'); + return true; + } catch (error) { + console.error('[SW] Failed to initialize Appwrite:', error); + initError = error.message; + return false; + } +} + +// Handle messages +async function handleMessage(request, sender) { + switch (request.action) { + case 'CHECK_AUTH': + return await checkAuth(); + + case 'LOGIN': + return await login(request.email, request.password); + + case 'LOGOUT': + return await logout(); + + case 'GET_USER': + return await getUser(); + + case 'GET_SETTINGS': + return await getSettings(); + + case 'SAVE_SETTINGS': + return await saveSettings(request.settings); + + case 'GET_SESSION': + return await getSession(); + + default: + return { success: false, error: 'Unknown action: ' + request.action }; + } +} + +// Check if user is authenticated +async function checkAuth() { + try { + // Check if we have a stored session + const stored = await chrome.storage.local.get(['session']); + if (!stored.session || !stored.session.sessionToken) { + return { success: true, authenticated: false, user: null }; + } + + // Verify session with API server + const API_SERVER_URL = 'http://localhost:3001'; + try { + const response = await fetch(`${API_SERVER_URL}/api/extension/auth`, { + method: 'GET', + headers: { + 'x-session-token': stored.session.sessionToken + } + }); + const data = await response.json(); + + if (data.success && data.authenticated) { + return { success: true, authenticated: true, user: data.user }; + } + } catch (e) { + console.log('[SW] API server check failed:', e.message); + } + + return { success: true, authenticated: false, user: null }; + } catch (error) { + console.log('[SW] Auth check failed:', error.message); + return { success: true, authenticated: false, user: null }; + } +} + +// Login with email and password +// Uses API server proxy to avoid platform registration issues +async function login(email, password) { + try { + console.log('[SW] Attempting login for:', email); + + // Use API server instead of direct Appwrite call + // This avoids the need to register Extension ID as platform + const API_SERVER_URL = 'http://localhost:3001'; + + const response = await fetch(`${API_SERVER_URL}/api/extension/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email, password }) + }); + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'Login fehlgeschlagen'); + } + + // Store session info including token + await chrome.storage.local.set({ + session: { + userId: data.user.$id, + sessionId: data.session.$id, + sessionToken: data.session.token || data.session.$id, + expire: data.session.expire + } + }); + + console.log('[SW] Login successful for:', data.user.email); + + // Initialize Appwrite client with session for future requests + if (!client || !account) { + initAppwrite(); + } + + return { success: true, user: data.user, session: data.session }; + } catch (error) { + console.error('[SW] Login error:', error); + return { success: false, error: error.message || 'Login fehlgeschlagen' }; + } +} + +// Logout +async function logout() { + try { + // Get session token + const stored = await chrome.storage.local.get(['session']); + const sessionToken = stored.session?.sessionToken; + + if (sessionToken) { + // Logout via API server + const API_SERVER_URL = 'http://localhost:3001'; + try { + await fetch(`${API_SERVER_URL}/api/extension/logout`, { + method: 'POST', + headers: { + 'x-session-token': sessionToken + } + }); + } catch (e) { + // Ignore errors + } + } + + await chrome.storage.local.remove(['session']); + return { success: true }; + } catch (error) { + await chrome.storage.local.remove(['session']); + return { success: true }; + } +} + +// Get current user +async function getUser() { + try { + const user = await account.get(); + return { success: true, user }; + } catch (error) { + return { success: false, error: error.message }; + } +} + +// Get session +async function getSession() { + try { + const session = await account.getSession('current'); + return { success: true, session }; + } catch (error) { + return { success: false, error: error.message }; + } +} + +// Get tool settings +async function getSettings() { + try { + const result = await chrome.storage.sync.get(['tools']); + const tools = result.tools || DEFAULT_TOOLS; + return { success: true, tools }; + } catch (error) { + return { success: true, tools: DEFAULT_TOOLS }; + } +} + +// Save tool settings +async function saveSettings(tools) { + try { + await chrome.storage.sync.set({ tools }); + + // Notify content scripts + const tabs = await chrome.tabs.query({ url: ['http://localhost:5173/*', 'http://localhost:3000/*'] }); + for (const tab of tabs) { + try { + await chrome.tabs.sendMessage(tab.id, { action: 'SETTINGS_UPDATED', tools }); + } catch (e) { + // Tab might not have content script + } + } + + return { success: true }; + } catch (error) { + return { success: false, error: error.message }; + } +} + +// Initialize on install +chrome.runtime.onInstalled.addListener(async (details) => { + if (details.reason === 'install') { + await chrome.storage.sync.set({ tools: DEFAULT_TOOLS }); + console.log('[SW] Extension installed'); + } +}); + +console.log('[SW] Service Worker ready'); diff --git a/Extension/config.js b/Extension/config.js new file mode 100644 index 0000000..f7176af --- /dev/null +++ b/Extension/config.js @@ -0,0 +1,17 @@ +// Appwrite Configuration +// Update these values with your Appwrite instance details + +// Use var instead of const to ensure global scope in service worker +var APPWRITE_CONFIG = { + endpoint: 'https://appwrite.webklar.com/v1', + projectId: '696b82bb0036d2e547ad', + apiKey: 'standard_d48c6eebe825b55e685d8e66ea056161105470702da77b730aca08c106ffbadfa2375ff675dbe9e01d7bb72b4a9fa001ff7c365b73759bc5fb3da432c3cd9cee1151e67517e9838d1f96f942d9891ce66ddc6f11c0fdd67a24f7c84e0fa9999a74dacf2c6aa3533998c177f190fc87ffb5a30b27474be21aece4c70d71d205ba' // Set your API Key here (see README for instructions) +}; + +// Protected website URL (where the extension will be active) +var PROTECTED_SITE_URL = 'http://localhost:5173'; + +// Export for use in service worker and content scripts +if (typeof module !== 'undefined' && module.exports) { + module.exports = { APPWRITE_CONFIG, PROTECTED_SITE_URL }; +} diff --git a/Extension/content/content-script.js b/Extension/content/content-script.js new file mode 100644 index 0000000..d09a197 --- /dev/null +++ b/Extension/content/content-script.js @@ -0,0 +1,310 @@ +// EShip Extension Content Script +// Injects into protected website to enforce auth and apply tools + +(function() { + 'use strict'; + + // State + let isAuthenticated = false; + let currentTools = []; + let blockedOverlay = null; + + // Initialize on page load + init(); + + async function init() { + // Check auth status immediately + const authResponse = await sendMessage({ action: 'CHECK_AUTH' }); + + if (authResponse.success && authResponse.authenticated) { + isAuthenticated = true; + await loadAndApplyTools(); + } else { + isAuthenticated = false; + showBlockedScreen(); + } + + // Listen for messages from service worker (settings updates) + chrome.runtime.onMessage.addListener(handleMessage); + } + + // Send message to service worker + function sendMessage(message) { + return new Promise((resolve) => { + chrome.runtime.sendMessage(message, (response) => { + if (chrome.runtime.lastError) { + resolve({ success: false, error: chrome.runtime.lastError.message }); + } else { + resolve(response || { success: false, error: 'No response' }); + } + }); + }); + } + + // Handle incoming messages + function handleMessage(request, sender, sendResponse) { + switch (request.action) { + case 'SETTINGS_UPDATED': + currentTools = request.tools; + applyTools(); + sendResponse({ success: true }); + break; + + case 'AUTH_CHANGED': + if (request.authenticated) { + isAuthenticated = true; + hideBlockedScreen(); + loadAndApplyTools(); + } else { + isAuthenticated = false; + showBlockedScreen(); + } + sendResponse({ success: true }); + break; + } + return true; + } + + // Show blocked screen when not authenticated + function showBlockedScreen() { + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', createBlockedOverlay); + } else { + createBlockedOverlay(); + } + } + + function createBlockedOverlay() { + // Remove existing overlay if any + hideBlockedScreen(); + + // Create overlay + blockedOverlay = document.createElement('div'); + blockedOverlay.id = 'eship-blocked-overlay'; + blockedOverlay.innerHTML = ` + +
🔒
+

Zugriff gesperrt

+

Diese Website ist geschuetzt und erfordert eine Authentifizierung ueber die EShip Browser-Extension.

+
+

So melden Sie sich an:

+
    +
  1. Klicken Sie auf das EShip-Icon in der Browser-Toolbar
  2. +
  3. Geben Sie Ihre E-Mail und Passwort ein
  4. +
  5. Klicken Sie auf "Anmelden"
  6. +
  7. Die Seite wird automatisch freigeschaltet
  8. +
+
+ `; + + document.body.appendChild(blockedOverlay); + + // Prevent scrolling on body + document.body.style.overflow = 'hidden'; + } + + // Hide blocked screen + function hideBlockedScreen() { + if (blockedOverlay) { + blockedOverlay.remove(); + blockedOverlay = null; + document.body.style.overflow = ''; + } + } + + // Load and apply tool settings + async function loadAndApplyTools() { + const response = await sendMessage({ action: 'GET_SETTINGS' }); + if (response.success) { + currentTools = response.tools; + applyTools(); + } + } + + // Apply all enabled tools + function applyTools() { + if (!isAuthenticated) return; + + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => executeTools()); + } else { + executeTools(); + } + } + + function executeTools() { + currentTools.forEach(tool => { + if (tool.enabled) { + applyTool(tool); + } else { + removeTool(tool); + } + }); + } + + // Apply a specific tool + function applyTool(tool) { + switch (tool.id) { + case 'highlight_prices': + applyHighlightPrices(tool.settings); + break; + // Add more tools here as needed + default: + console.log('[EShip] Unknown tool:', tool.id); + } + } + + // Remove a specific tool's effects + function removeTool(tool) { + switch (tool.id) { + case 'highlight_prices': + removeHighlightPrices(tool.settings); + break; + default: + break; + } + } + + // ============================================ + // TOOL: Highlight Prices + // ============================================ + + function applyHighlightPrices(settings) { + const selector = settings.selector || '.price'; + const borderColor = settings.borderColor || '#ff0000'; + const borderWidth = settings.borderWidth || '2px'; + + // Add custom style if not exists + let styleEl = document.getElementById('eship-highlight-prices-style'); + if (!styleEl) { + styleEl = document.createElement('style'); + styleEl.id = 'eship-highlight-prices-style'; + document.head.appendChild(styleEl); + } + + styleEl.textContent = ` + ${selector} { + border: ${borderWidth} solid ${borderColor} !important; + box-shadow: 0 0 8px ${borderColor}40 !important; + border-radius: 4px !important; + padding: 2px 4px !important; + transition: all 0.2s ease !important; + } + `; + + // Also add data attribute to track highlighted elements + document.querySelectorAll(selector).forEach(el => { + el.dataset.eshipHighlighted = 'true'; + }); + + console.log('[EShip] Highlight Prices applied with selector:', selector); + } + + function removeHighlightPrices(settings) { + // Remove style element + const styleEl = document.getElementById('eship-highlight-prices-style'); + if (styleEl) { + styleEl.remove(); + } + + // Remove data attributes + document.querySelectorAll('[data-eship-highlighted]').forEach(el => { + delete el.dataset.eshipHighlighted; + }); + + console.log('[EShip] Highlight Prices removed'); + } + + // ============================================ + // Mutation Observer for dynamic content + // ============================================ + + // Re-apply tools when new content is added + const observer = new MutationObserver((mutations) => { + if (!isAuthenticated) return; + + let shouldReapply = false; + mutations.forEach(mutation => { + if (mutation.addedNodes.length > 0) { + shouldReapply = true; + } + }); + + if (shouldReapply) { + // Debounce reapplication + clearTimeout(observer.timeout); + observer.timeout = setTimeout(() => { + applyTools(); + }, 100); + } + }); + + // Start observing once DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + observer.observe(document.body, { childList: true, subtree: true }); + }); + } else { + observer.observe(document.body, { childList: true, subtree: true }); + } + +})(); diff --git a/Extension/lib/appwrite.min.js b/Extension/lib/appwrite.min.js new file mode 100644 index 0000000..b218f77 --- /dev/null +++ b/Extension/lib/appwrite.min.js @@ -0,0 +1,209 @@ +/** + * Appwrite Web SDK - Minimal Bundle for Chrome Extension + * This is a simplified version for extension use + * For full SDK, download from: https://cdn.jsdelivr.net/npm/appwrite@latest/dist/iife/sdk.min.js + * + * Version: 21.x compatible + */ + +(function(global) { + 'use strict'; + + // Appwrite namespace + const Appwrite = {}; + + // Client class + class Client { + constructor() { + this.config = { + endpoint: 'https://cloud.appwrite.io/v1', + project: '', + }; + this.headers = { + 'content-type': 'application/json', + 'x-sdk-name': 'Chrome Extension', + 'x-sdk-platform': 'client', + 'x-sdk-language': 'web', + 'x-sdk-version': '21.0.0', + }; + } + + setEndpoint(endpoint) { + this.config.endpoint = endpoint; + return this; + } + + setProject(project) { + this.config.project = project; + this.headers['x-appwrite-project'] = project; + return this; + } + + setKey(key) { + if (key) { + this.headers['x-appwrite-key'] = key; + } else { + delete this.headers['x-appwrite-key']; + } + return this; + } + + async call(method, path, headers = {}, params = {}) { + const url = new URL(this.config.endpoint + path); + const options = { + method: method.toUpperCase(), + headers: { ...this.headers, ...headers }, + credentials: 'include', + }; + + if (method === 'GET') { + for (const [key, value] of Object.entries(params)) { + if (value !== undefined) { + url.searchParams.append(key, value); + } + } + } else { + options.body = JSON.stringify(params); + } + + const response = await fetch(url.toString(), options); + const contentType = response.headers.get('content-type') || ''; + + let data; + if (contentType.includes('application/json')) { + data = await response.json(); + } else { + data = await response.text(); + } + + if (response.status >= 400) { + throw new AppwriteException( + data.message || 'Unknown error', + response.status, + data.type || '', + data + ); + } + + return data; + } + } + + // Account class + class Account { + constructor(client) { + this.client = client; + } + + async get() { + return await this.client.call('GET', '/account'); + } + + async create(userId, email, password, name = undefined) { + const params = { userId, email, password }; + if (name) params.name = name; + return await this.client.call('POST', '/account', {}, params); + } + + async createEmailPasswordSession(email, password) { + return await this.client.call('POST', '/account/sessions/email', {}, { + email, + password, + }); + } + + async createSession(userId, secret) { + return await this.client.call('POST', '/account/sessions', {}, { + userId, + secret, + }); + } + + async getSession(sessionId) { + return await this.client.call('GET', `/account/sessions/${sessionId}`); + } + + async listSessions() { + return await this.client.call('GET', '/account/sessions'); + } + + async deleteSession(sessionId) { + return await this.client.call('DELETE', `/account/sessions/${sessionId}`); + } + + async deleteSessions() { + return await this.client.call('DELETE', '/account/sessions'); + } + + async updateEmail(email, password) { + return await this.client.call('PATCH', '/account/email', {}, { + email, + password, + }); + } + + async updatePassword(password, oldPassword = undefined) { + const params = { password }; + if (oldPassword) params.oldPassword = oldPassword; + return await this.client.call('PATCH', '/account/password', {}, params); + } + + async updateName(name) { + return await this.client.call('PATCH', '/account/name', {}, { name }); + } + + async getPrefs() { + return await this.client.call('GET', '/account/prefs'); + } + + async updatePrefs(prefs) { + return await this.client.call('PATCH', '/account/prefs', {}, { prefs }); + } + + async createRecovery(email, url) { + return await this.client.call('POST', '/account/recovery', {}, { + email, + url, + }); + } + + async updateRecovery(userId, secret, password) { + return await this.client.call('PUT', '/account/recovery', {}, { + userId, + secret, + password, + }); + } + + async createVerification(url) { + return await this.client.call('POST', '/account/verification', {}, { url }); + } + + async updateVerification(userId, secret) { + return await this.client.call('PUT', '/account/verification', {}, { + userId, + secret, + }); + } + } + + // Exception class + class AppwriteException extends Error { + constructor(message, code = 0, type = '', response = null) { + super(message); + this.name = 'AppwriteException'; + this.code = code; + this.type = type; + this.response = response; + } + } + + // Export to Appwrite namespace + Appwrite.Client = Client; + Appwrite.Account = Account; + Appwrite.AppwriteException = AppwriteException; + + // Make available globally + global.Appwrite = Appwrite; + +})(typeof self !== 'undefined' ? self : this); diff --git a/Extension/manifest.json b/Extension/manifest.json new file mode 100644 index 0000000..731747a --- /dev/null +++ b/Extension/manifest.json @@ -0,0 +1,44 @@ +{ + "manifest_version": 3, + "name": "EShip Auth Extension", + "version": "1.0.0", + "description": "Authentication extension for EShip protected web app", + + "permissions": [ + "storage", + "activeTab", + "tabs" + ], + + "host_permissions": [ + "http://localhost:5173/*", + "http://localhost:3000/*", + "http://localhost:3001/*", + "https://appwrite.webklar.com/*" + ], + + "background": { + "service_worker": "background/service-worker.js" + }, + + "action": { + "default_popup": "popup/popup.html", + "default_title": "EShip Login" + }, + + "content_scripts": [ + { + "matches": ["http://localhost:5173/*", "http://localhost:3000/*"], + "js": ["content/content-script.js"], + "run_at": "document_start" + } + ], + + + "web_accessible_resources": [ + { + "resources": ["lib/*", "config.js"], + "matches": ["http://localhost:5173/*", "http://localhost:3000/*"] + } + ] +} diff --git a/Extension/popup/popup.css b/Extension/popup/popup.css new file mode 100644 index 0000000..abdfc5a --- /dev/null +++ b/Extension/popup/popup.css @@ -0,0 +1,315 @@ +/* EShip Extension Popup Styles */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; + font-size: 14px; + color: #2D2D31; + background: #FAFAFB; + min-width: 320px; + max-width: 400px; +} + +.container { + padding: 16px; +} + +/* Header */ +header { + text-align: center; + margin-bottom: 20px; + padding-bottom: 12px; + border-bottom: 1px solid #EDEDF0; +} + +header h1 { + font-size: 20px; + font-weight: 600; + color: #FD366E; +} + +/* States */ +.state { + display: block; +} + +.state.hidden { + display: none; +} + +/* Loading */ +#loading { + text-align: center; + padding: 40px 0; +} + +.spinner { + width: 32px; + height: 32px; + border: 3px solid #EDEDF0; + border-top-color: #FD366E; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin: 0 auto 12px; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Form */ +h2 { + font-size: 16px; + font-weight: 500; + margin-bottom: 16px; +} + +.form-group { + margin-bottom: 14px; +} + +.form-group label { + display: block; + font-size: 12px; + font-weight: 500; + color: #97979B; + margin-bottom: 4px; +} + +.form-group input { + width: 100%; + padding: 10px 12px; + border: 1px solid #EDEDF0; + border-radius: 6px; + font-size: 14px; + background: #fff; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.form-group input:focus { + outline: none; + border-color: #FD366E; + box-shadow: 0 0 0 3px rgba(253, 54, 110, 0.1); +} + +/* Error Message */ +.error { + background: #FEE2E2; + color: #B91C1C; + padding: 10px 12px; + border-radius: 6px; + font-size: 13px; + margin-bottom: 14px; +} + +.error.hidden { + display: none; +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 16px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s, transform 0.1s; +} + +.btn:active { + transform: scale(0.98); +} + +.btn-primary { + width: 100%; + background: #FD366E; + color: white; +} + +.btn-primary:hover { + background: #E8305F; +} + +.btn-primary:disabled { + background: #FDA4B8; + cursor: not-allowed; +} + +.btn-secondary { + background: #EDEDF0; + color: #2D2D31; +} + +.btn-secondary:hover { + background: #D8D8DB; +} + +.btn-small { + padding: 6px 12px; + font-size: 12px; +} + +.btn-spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +.btn-spinner.hidden { + display: none; +} + +/* User Info */ +.user-info { + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + background: #fff; + border: 1px solid #EDEDF0; + border-radius: 8px; + margin-bottom: 20px; +} + +.user-icon { + font-size: 20px; +} + +#user-email { + flex: 1; + font-size: 13px; + color: #97979B; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Tools List */ +#tools-list { + background: #fff; + border: 1px solid #EDEDF0; + border-radius: 8px; + margin-bottom: 16px; +} + +.tool-item { + padding: 12px; + border-bottom: 1px solid #EDEDF0; +} + +.tool-item:last-child { + border-bottom: none; +} + +.tool-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.tool-name { + font-weight: 500; +} + +/* Toggle Switch */ +.toggle { + position: relative; + width: 44px; + height: 24px; +} + +.toggle input { + opacity: 0; + width: 0; + height: 0; +} + +.toggle-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #D8D8DB; + transition: 0.3s; + border-radius: 24px; +} + +.toggle-slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: white; + transition: 0.3s; + border-radius: 50%; +} + +.toggle input:checked + .toggle-slider { + background-color: #FD366E; +} + +.toggle input:checked + .toggle-slider:before { + transform: translateX(20px); +} + +/* Tool Settings */ +.tool-settings { + display: none; + padding-top: 8px; +} + +.tool-settings.visible { + display: block; +} + +.setting-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.setting-row:last-child { + margin-bottom: 0; +} + +.setting-row label { + font-size: 12px; + color: #97979B; + min-width: 80px; +} + +.setting-row input { + flex: 1; + padding: 6px 8px; + border: 1px solid #EDEDF0; + border-radius: 4px; + font-size: 12px; +} + +.setting-row input:focus { + outline: none; + border-color: #FD366E; +} + +/* Actions */ +.actions { + margin-top: 16px; +} diff --git a/Extension/popup/popup.html b/Extension/popup/popup.html new file mode 100644 index 0000000..99a6d96 --- /dev/null +++ b/Extension/popup/popup.html @@ -0,0 +1,62 @@ + + + + + + EShip Extension + + + +
+
+

EShip

+
+ + +
+
+

Laden...

+
+ + + + + + +
+ + + + diff --git a/Extension/popup/popup.js b/Extension/popup/popup.js new file mode 100644 index 0000000..aa24f4b --- /dev/null +++ b/Extension/popup/popup.js @@ -0,0 +1,319 @@ +// EShip Extension Popup Logic + +// DOM Elements +const loadingEl = document.getElementById('loading'); +const loginFormEl = document.getElementById('login-form'); +const toolsMenuEl = document.getElementById('tools-menu'); +const authForm = document.getElementById('auth-form'); +const emailInput = document.getElementById('email'); +const passwordInput = document.getElementById('password'); +const errorMessage = document.getElementById('error-message'); +const loginBtn = document.getElementById('login-btn'); +const logoutBtn = document.getElementById('logout-btn'); +const userEmailEl = document.getElementById('user-email'); +const toolsListEl = document.getElementById('tools-list'); +const openSiteBtn = document.getElementById('open-site-btn'); + +// Protected site URL (should match config.js) +const PROTECTED_SITE_URL = 'http://localhost:5173'; + +// State +let currentUser = null; +let tools = []; + +// Initialize popup +document.addEventListener('DOMContentLoaded', init); + +async function init() { + showState('loading'); + + try { + // Check if service worker is available + if (!chrome.runtime || !chrome.runtime.id) { + throw new Error('Chrome Runtime nicht verfuegbar'); + } + + // First, test if service worker is alive + console.log('Testing service worker...'); + const pingResponse = await sendMessage({ action: 'PING' }); + console.log('PING response:', pingResponse); + + if (!pingResponse) { + throw new Error('Service Worker antwortet nicht. Bitte Extension in chrome://extensions neu laden und Service Worker Konsole pruefen.'); + } + + if (!pingResponse.success) { + throw new Error('Service Worker Fehler: ' + (pingResponse.error || 'Unbekannter Fehler')); + } + + console.log('Service Worker ist aktiv!'); + + console.log('Checking auth status...'); + + // Check current auth status + const response = await sendMessage({ action: 'CHECK_AUTH' }); + + if (!response) { + throw new Error('Keine Antwort vom Service Worker'); + } + + console.log('Auth response:', response); + + // If there's an error, show it but still allow login + if (response.error && !response.success) { + console.error('Service Worker Error:', response.error); + showError('Hinweis: ' + response.error + ' - Login sollte trotzdem funktionieren.'); + } + + if (response.success && response.authenticated) { + currentUser = response.user; + await loadTools(); + showLoggedInState(); + } else { + // Not authenticated - show login form + showState('login-form'); + } + } catch (error) { + console.error('Init error:', error); + showError('Fehler beim Laden: ' + error.message + '. Bitte Extension in chrome://extensions neu laden.'); + showState('login-form'); + } +} + +// Show a specific state (loading, login-form, tools-menu) +function showState(stateId) { + loadingEl.classList.add('hidden'); + loginFormEl.classList.add('hidden'); + toolsMenuEl.classList.add('hidden'); + + document.getElementById(stateId).classList.remove('hidden'); +} + +// Show logged in state with user info +function showLoggedInState() { + userEmailEl.textContent = currentUser.email || currentUser.name || 'Benutzer'; + renderTools(); + showState('tools-menu'); +} + +// Send message to service worker +function sendMessage(message) { + return new Promise((resolve) => { + // Add timeout to prevent hanging + const timeout = setTimeout(() => { + console.error('Message timeout:', message.action); + resolve({ + success: false, + error: 'Service Worker antwortet nicht. Bitte Extension in chrome://extensions neu laden und Service Worker Konsole pruefen.' + }); + }, 2000); // Reduced timeout to 2 seconds for faster feedback + + try { + if (!chrome.runtime || !chrome.runtime.id) { + clearTimeout(timeout); + resolve({ success: false, error: 'Chrome Runtime nicht verfuegbar' }); + return; + } + + chrome.runtime.sendMessage(message, (response) => { + clearTimeout(timeout); + if (chrome.runtime.lastError) { + console.error('Chrome runtime error:', chrome.runtime.lastError); + resolve({ + success: false, + error: chrome.runtime.lastError.message || 'Service Worker Fehler' + }); + } else { + console.log('Response received:', response); + resolve(response || { success: false, error: 'Keine Antwort vom Service Worker' }); + } + }); + } catch (error) { + clearTimeout(timeout); + console.error('Send message error:', error); + resolve({ success: false, error: error.message || 'Fehler beim Senden der Nachricht' }); + } + }); +} + +// Login form submission +authForm.addEventListener('submit', async (e) => { + e.preventDefault(); + + const email = emailInput.value.trim(); + const password = passwordInput.value; + + if (!email || !password) { + showError('Bitte E-Mail und Passwort eingeben'); + return; + } + + setLoginLoading(true); + hideError(); + + const response = await sendMessage({ + action: 'LOGIN', + email, + password + }); + + setLoginLoading(false); + + if (response.success) { + currentUser = response.user; + await loadTools(); + showLoggedInState(); + + // Open protected site after successful login + openProtectedSite(); + } else { + showError(response.error || 'Anmeldung fehlgeschlagen'); + } +}); + +// Logout button +logoutBtn.addEventListener('click', async () => { + const response = await sendMessage({ action: 'LOGOUT' }); + + if (response.success) { + currentUser = null; + tools = []; + emailInput.value = ''; + passwordInput.value = ''; + showState('login-form'); + } +}); + +// Open site button +openSiteBtn.addEventListener('click', () => { + openProtectedSite(); +}); + +// Open protected site in new tab +function openProtectedSite() { + chrome.tabs.create({ url: PROTECTED_SITE_URL }); +} + +// Load tools settings +async function loadTools() { + const response = await sendMessage({ action: 'GET_SETTINGS' }); + if (response.success) { + tools = response.tools; + } +} + +// Render tools list +function renderTools() { + toolsListEl.innerHTML = ''; + + tools.forEach((tool, index) => { + const toolEl = document.createElement('div'); + toolEl.className = 'tool-item'; + toolEl.innerHTML = ` +
+ ${escapeHtml(tool.name)} + +
+
+ ${renderToolSettings(tool)} +
+ `; + toolsListEl.appendChild(toolEl); + + // Toggle event listener + const toggle = toolEl.querySelector('input[type="checkbox"]'); + toggle.addEventListener('change', (e) => { + handleToolToggle(tool.id, e.target.checked); + }); + + // Settings change listeners + const settingsInputs = toolEl.querySelectorAll('.setting-row input'); + settingsInputs.forEach(input => { + input.addEventListener('change', (e) => { + handleSettingChange(tool.id, e.target.dataset.setting, e.target.value); + }); + }); + }); +} + +// Render settings inputs for a tool +function renderToolSettings(tool) { + if (!tool.settings) return ''; + + let html = ''; + for (const [key, value] of Object.entries(tool.settings)) { + html += ` +
+ + +
+ `; + } + return html; +} + +// Format setting key to readable name +function formatSettingName(key) { + return key + .replace(/([A-Z])/g, ' $1') + .replace(/^./, str => str.toUpperCase()) + .replace(/_/g, ' '); +} + +// Handle tool toggle +async function handleToolToggle(toolId, enabled) { + const tool = tools.find(t => t.id === toolId); + if (tool) { + tool.enabled = enabled; + + // Show/hide settings + const settingsEl = document.getElementById(`settings-${toolId}`); + if (settingsEl) { + settingsEl.classList.toggle('visible', enabled); + } + + await saveTools(); + } +} + +// Handle setting value change +async function handleSettingChange(toolId, settingKey, value) { + const tool = tools.find(t => t.id === toolId); + if (tool && tool.settings) { + tool.settings[settingKey] = value; + await saveTools(); + } +} + +// Save tools to storage +async function saveTools() { + await sendMessage({ action: 'SAVE_SETTINGS', settings: tools }); +} + +// Show error message +function showError(message) { + errorMessage.textContent = message; + errorMessage.classList.remove('hidden'); +} + +// Hide error message +function hideError() { + errorMessage.classList.add('hidden'); +} + +// Set login button loading state +function setLoginLoading(loading) { + loginBtn.disabled = loading; + loginBtn.querySelector('.btn-text').classList.toggle('hidden', loading); + loginBtn.querySelector('.btn-spinner').classList.toggle('hidden', !loading); +} + +// Escape HTML to prevent XSS +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d662017 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Extension & Server Projekt + +Dieses Projekt besteht aus zwei Hauptkomponenten: + +## Extension +- **Zweck**: Frontend und Datenextraktion +- **Speicherort**: `/Extension` +- Enthält die Browser-Extension für Datenextraktion (z.B. von Amazon) + +## Server +- **Zweck**: Backend-Logik, Verschlüsselung und Sicherheit +- **Speicherort**: `/Server` +- Basiert auf React mit Appwrite-Integration +- Implementiert Schutzmechanismen gegen Kopieren der Extension + +## Getting Started + +### Server Setup + +1. Navigiere zum Server-Ordner: `cd Server` +2. Dependencies installieren: `npm install` +3. App starten: `npm run dev` +4. Öffne http://localhost:5173 im Browser +5. Klicke auf "Send a ping" um die Appwrite-Verbindung zu testen + +### Extension Setup + +Die Extension-Struktur wird in Kürze implementiert. diff --git a/Server b/Server new file mode 160000 index 0000000..6100df3 --- /dev/null +++ b/Server @@ -0,0 +1 @@ +Subproject commit 6100df3f8340ed5df41cc21c4042320d7013a363 diff --git a/db.txt b/db.txt new file mode 100644 index 0000000..7eed06b --- /dev/null +++ b/db.txt @@ -0,0 +1,230 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Appwrite schema bootstrap (4 tables) for Appwrite Server 1.8.1 +# Tables: users, accounts, products, product_details +# +# Notes: +# - Appwrite CLI is compatible with Appwrite Server 1.8.x. (see sdk-for-cli README) :contentReference[oaicite:0]{index=0} +# - The CLI supports "tables" (create-table, create-*-column, create-index). :contentReference[oaicite:1]{index=1} +# +# Prereqs: +# appwrite login +# appwrite init project +# +# Run: +# chmod +x appwrite_schema_v1_8_1.sh +# ./appwrite_schema_v1_8_1.sh + +# ---------------- CONFIG ---------------- +DATABASE_ID="YOUR_DATABASE_ID" + +T_USERS="users" +T_ACCOUNTS="accounts" +T_PRODUCTS="products" +T_PRODUCT_DETAILS="product_details" + +# Permissions: keep minimal for now. Adjust later. +# Example roles: any, users, user:, team: +PERMS_ANY_CRUD='["create(\\"any\\")","read(\\"any\\")","update(\\"any\\")","delete(\\"any\\")"]' + +# If you want more locked down defaults later, tell me your exact access model. + +# ---------------- HELPERS ---------------- +try_cmd() { + # Run command, do not fail script if it errors (idempotent-ish). + # Print what we tried for easy debugging. + echo "+ $*" + set +e + "$@" + local rc=$? + set -e + if [ $rc -ne 0 ]; then + echo " (ignored error, rc=$rc)" + fi + return 0 +} + +# ---------------- CREATE TABLES ---------------- +# Tip: If any of these fail because option names differ in your CLI build, +# run: appwrite databases create-table --help +# and replace flags accordingly. + +try_cmd appwrite databases create-table \ + --database-id "$DATABASE_ID" \ + --table-id "$T_USERS" \ + --name "users" \ + --permissions "$PERMS_ANY_CRUD" \ + --row-security false + +try_cmd appwrite databases create-table \ + --database-id "$DATABASE_ID" \ + --table-id "$T_ACCOUNTS" \ + --name "accounts" \ + --permissions "$PERMS_ANY_CRUD" \ + --row-security false + +try_cmd appwrite databases create-table \ + --database-id "$DATABASE_ID" \ + --table-id "$T_PRODUCTS" \ + --name "products" \ + --permissions "$PERMS_ANY_CRUD" \ + --row-security false + +try_cmd appwrite databases create-table \ + --database-id "$DATABASE_ID" \ + --table-id "$T_PRODUCT_DETAILS" \ + --name "product_details" \ + --permissions "$PERMS_ANY_CRUD" \ + --row-security false + +# ---------------- USERS COLUMNS ---------------- +# You originally wanted basically no fields here. Some people keep this table empty +# (using only system fields), but depending on tooling, an empty table can be annoying. +# Keep one optional column for future user settings/notes. +try_cmd appwrite databases create-string-column \ + --database-id "$DATABASE_ID" \ + --table-id "$T_USERS" \ + --key "user_note" \ + --size 255 \ + --required false \ + --array false + +# ---------------- ACCOUNTS COLUMNS ---------------- +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_owner_user_id" --size 64 --required false --array false +try_cmd appwrite databases create-boolean-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_managed" --required true --array false + +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_platform" --elements '["amazon","ebay"]' --required true --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_platform_account_id" --size 255 --required true --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_platform_market" --size 32 --required true --array false + +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_shop_name" --size 255 --required false --array false +try_cmd appwrite databases create-url-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_url" --required false --array false + +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_ACCOUNTS" --key "account_status" --elements '["active","unknown","disabled"]' --required false --array false + +# ---------------- ACCOUNTS INDEXES ---------------- +try_cmd appwrite databases create-index \ + --database-id "$DATABASE_ID" \ + --table-id "$T_ACCOUNTS" \ + --key "accounts_unique_platform_market_accountid" \ + --type "unique" \ + --columns '["account_platform","account_platform_market","account_platform_account_id"]' + +try_cmd appwrite databases create-index \ + --database-id "$DATABASE_ID" \ + --table-id "$T_ACCOUNTS" \ + --key "accounts_by_owner_user" \ + --type "key" \ + --columns '["account_owner_user_id"]' + +# ---------------- PRODUCTS COLUMNS ---------------- +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_account_id" --size 64 --required true --array false + +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_platform" --elements '["amazon","ebay"]' --required true --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_platform_market" --size 32 --required true --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_platform_product_id" --size 255 --required true --array false + +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_title" --size 1024 --required true --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_category" --size 255 --required false --array false + +try_cmd appwrite databases create-float-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_price" --required true --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_currency" --size 8 --required true --array false + +try_cmd appwrite databases create-integer-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_quantity" --required false --array false + +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_condition" --elements '["new","used_like_new","used_good","used_ok","parts"]' --required false --array false +try_cmd appwrite databases create-url-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_url" --required false --array false +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCTS" --key "product_status" --elements '["active","ended","unknown"]' --required false --array false + +# ---------------- PRODUCTS INDEXES ---------------- +try_cmd appwrite databases create-index \ + --database-id "$DATABASE_ID" \ + --table-id "$T_PRODUCTS" \ + --key "products_by_account" \ + --type "key" \ + --columns '["product_account_id"]' + +try_cmd appwrite databases create-index \ + --database-id "$DATABASE_ID" \ + --table-id "$T_PRODUCTS" \ + --key "products_unique_account_platformproductid" \ + --type "unique" \ + --columns '["product_account_id","product_platform_product_id"]' + +# ---------------- PRODUCT_DETAILS COLUMNS ---------------- +# 1:1 to products +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_product_id" --size 64 --required true --array false + +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_platform" --elements '["amazon","ebay"]' --required true --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_platform_market" --size 32 --required false --array false + +# Identifiers +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_gtin" --size 32 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_ean" --size 32 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_upc" --size 32 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_isbn" --size 32 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_mpn" --size 64 --required false --array false + +# Platform IDs +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_amazon_asin" --size 32 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_ebay_epid" --size 64 --required false --array false + +# Brand / model +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_brand" --size 255 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_manufacturer" --size 255 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_model_name" --size 255 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_model_number" --size 255 --required false --array false + +# Content +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_short_description" --size 2048 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_long_description" --size 8192 --required false --array false + +# Bullet points (no arrays, no JSON) +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_1" --size 512 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_2" --size 512 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_3" --size 512 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_4" --size 512 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_5" --size 512 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_6" --size 512 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_7" --size 512 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_bullet_8" --size 512 --required false --array false + +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_search_terms" --size 1024 --required false --array false + +# Variants / item specifics (common) +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_color" --size 128 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_size" --size 128 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_material" --size 128 --required false --array false +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_pattern" --size 128 --required false --array false + +# Shipping measurements +try_cmd appwrite databases create-float-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_length" --required false --array false +try_cmd appwrite databases create-float-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_width" --required false --array false +try_cmd appwrite databases create-float-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_height" --required false --array false +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_dimension_unit" --elements '["mm","cm","m","in"]' --required false --array false + +try_cmd appwrite databases create-float-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_weight" --required false --array false +try_cmd appwrite databases create-enum-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_weight_unit" --elements '["g","kg","oz","lb"]' --required false --array false + +# Misc +try_cmd appwrite databases create-string-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_country_of_origin" --size 64 --required false --array false +try_cmd appwrite databases create-integer-column --database-id "$DATABASE_ID" --table-id "$T_PRODUCT_DETAILS" --key "product_detail_package_quantity" --required false --array false + +# ---------------- PRODUCT_DETAILS INDEXES ---------------- +# One details row per product +try_cmd appwrite databases create-index \ + --database-id "$DATABASE_ID" \ + --table-id "$T_PRODUCT_DETAILS" \ + --key "product_details_unique_product_id" \ + --type "unique" \ + --columns '["product_detail_product_id"]' + +try_cmd appwrite databases create-index \ + --database-id "$DATABASE_ID" \ + --table-id "$T_PRODUCT_DETAILS" \ + --key "product_details_by_platform" \ + --type "key" \ + --columns '["product_detail_platform"]' + +echo "Done. 4 tables ensured: users, accounts, products, product_details" diff --git a/setup/API_KEY_SETUP.md b/setup/API_KEY_SETUP.md new file mode 100644 index 0000000..2596613 --- /dev/null +++ b/setup/API_KEY_SETUP.md @@ -0,0 +1,53 @@ +# Appwrite API Key Setup + +## API Key erstellen + +1. **Appwrite Console oeffnen**: https://cloud.appwrite.io +2. **Projekt auswaehlen**: Waehle dein Projekt (ID: `696b82bb0036d2e547ad`) +3. **Settings > API Keys** navigieren +4. **"Create API Key"** klicken +5. **Konfiguration**: + - **Name**: `EShip Extension Key` (oder beliebiger Name) + - **Scopes**: + - `users.read` - Benutzer lesen + - `users.write` - Benutzer erstellen/bearbeiten + - `sessions.write` - Sessions erstellen + - Optional: Weitere Scopes je nach Bedarf + - **Expiration**: Optional (leer lassen fuer unbegrenzt) +6. **"Create"** klicken +7. **API Key kopieren** - WICHTIG: Der Key wird nur einmal angezeigt! + +## API Key in Extension konfigurieren + +1. Oeffne `Extension/config.js` +2. Fuege den API Key hinzu: + +```javascript +var APPWRITE_CONFIG = { + endpoint: 'https://cloud.appwrite.io/v1', + projectId: '696b82bb0036d2e547ad', + apiKey: 'DEIN_API_KEY_HIER' // Hier den kopierten Key einfuegen +}; +``` + +3. Extension neu laden in `chrome://extensions` + +## Sicherheit + +- **NIEMALS** den API Key in Git committen +- Der API Key sollte nur in der Extension verwendet werden +- Bei Verlust: Alten Key loeschen und neuen erstellen +- Verwende unterschiedliche Keys fuer Development und Production + +## Alternative: Environment-basierte Konfiguration + +Fuer Production kannst du den API Key auch ueber Chrome Storage setzen: + +1. In der Extension: `chrome.storage.local.set({ apiKey: 'DEIN_KEY' })` +2. Im Service Worker: Key aus Storage laden + +## Troubleshooting + +- **"Invalid API Key"**: Pruefe, ob der Key korrekt kopiert wurde (keine Leerzeichen) +- **"Insufficient permissions"**: Pruefe die Scopes des API Keys +- **"Key expired"**: Erstelle einen neuen API Key diff --git a/setup/API_SERVER_SETUP.md b/setup/API_SERVER_SETUP.md new file mode 100644 index 0000000..5819840 --- /dev/null +++ b/setup/API_SERVER_SETUP.md @@ -0,0 +1,58 @@ +# API Server Setup + +## Problem + +Chrome Extensions haben unterschiedliche IDs bei jeder Installation. Appwrite erfordert Platform-Registrierung, was nicht praktikabel ist. + +## Lösung + +Ein Express API-Server fungiert als Proxy zwischen Extension und Appwrite. Der Server verwendet den API Key (server-seitig, keine Platform-Registrierung nötig) und erstellt Sessions fuer die Extension. + +## Installation + +1. Dependencies installieren: +```bash +cd Server +npm install +``` + +2. API Server starten: +```bash +npm run dev:api +``` + +Der Server laeuft auf `http://localhost:3001` + +## Beide Server gleichzeitig starten + +```bash +npm run dev:all +``` + +Startet sowohl Vite (Port 5173) als auch API Server (Port 3001). + +## Umgebungsvariablen (optional) + +Erstelle eine `.env` Datei im `Server/` Ordner: + +``` +APPWRITE_ENDPOINT=https://appwrite.webklar.com/v1 +APPWRITE_PROJECT_ID=696b82bb0036d2e547ad +APPWRITE_API_KEY=dein_api_key_hier +``` + +Falls nicht gesetzt, werden die Default-Werte aus `api-server.js` verwendet. + +## API Endpoints + +- `POST /api/extension/login` - Login mit Email/Password +- `GET /api/extension/auth` - Prueft Auth-Status +- `POST /api/extension/logout` - Logout +- `GET /api/health` - Health Check + +## Vorteile + +- Keine Platform-Registrierung in Appwrite noetig +- Funktioniert fuer alle Extension-Installationen +- API Key bleibt sicher auf dem Server +- Einfache Skalierung diff --git a/setup/CLI_CONNECTION.md b/setup/CLI_CONNECTION.md new file mode 100644 index 0000000..d6f7f48 --- /dev/null +++ b/setup/CLI_CONNECTION.md @@ -0,0 +1,57 @@ +# Appwrite CLI Verbindung + +## Aktueller Status + +- **Endpoint**: `https://appwrite.webklar.com/v1` +- **Project ID**: `696b82bb0036d2e547ad` +- **Client konfiguriert**: ✓ + +## Einloggen bei Appwrite + +Um dich bei Appwrite einzuloggen, führe diesen Befehl in deinem Terminal aus: + +```bash +cd Server +appwrite login +``` + +Folge den Anweisungen im Terminal. Du wirst zu einem Browser-Fenster weitergeleitet, um dich anzumelden. + +## Verbindung überprüfen + +Nach dem Login kannst du deine Verbindung testen: + +```bash +# Liste alle Datenbanken auf +appwrite databases list + +# Zeige Projekt-Informationen +appwrite projects get --project-id 696b82bb0036d2e547ad + +# Liste alle Collections (wenn Datenbank existiert) +appwrite databases list-collections --database-id eship-db +``` + +## Weitere nützliche Befehle + +```bash +# Client-Einstellungen anzeigen +appwrite client --help + +# Alle verfügbaren Datenbank-Befehle anzeigen +appwrite databases --help + +# Alle verfügbaren Collections anzeigen +appwrite databases list-collections --database-id YOUR_DATABASE_ID + +# Collection-Informationen abrufen +appwrite databases get-collection --database-id YOUR_DATABASE_ID --collection-id YOUR_COLLECTION_ID +``` + +## Wichtige Hinweise + +1. **Login ist interaktiv**: Der `appwrite login` Befehl muss manuell im Terminal ausgeführt werden, da er einen Browser öffnet. + +2. **Projekt muss initialisiert sein**: Wenn du in einem bestimmten Verzeichnis arbeitest, führe `appwrite init project` aus, um das Projekt zu initialisieren. + +3. **Session**: Nach dem Login bleibt deine Session aktiv, bis du dich ausloggst oder die Session abläuft. diff --git a/setup/DATABASE_SETUP.md b/setup/DATABASE_SETUP.md new file mode 100644 index 0000000..11a030a --- /dev/null +++ b/setup/DATABASE_SETUP.md @@ -0,0 +1,81 @@ +# Datenbank-Schema Setup Anleitung + +Diese Anleitung führt dich durch das Setup der Appwrite-Datenbank mit dem Schema-Skript. + +## Voraussetzungen + +1. **Appwrite CLI installiert** ✓ (bereits erledigt) +2. **Projekt konfiguriert** ✓ (bereits erledigt) +3. **Bei Appwrite eingeloggt sein** + +## Schritt 1: Bei Appwrite einloggen + +```bash +appwrite login +``` + +Folge den Anweisungen im Terminal. Du wirst zu einem Browser-Fenster weitergeleitet, um dich anzumelden. + +## Schritt 2: Datenbank erstellen oder ID ermitteln + +### Option A: Neue Datenbank erstellen + +```bash +appwrite databases create --name "eship-database" --database-id "eship-db" +``` + +Die Ausgabe zeigt dir die `$id` der erstellten Datenbank. Kopiere diese ID. + +### Option B: Bestehende Datenbank auflisten + +```bash +appwrite databases list +``` + +Suche die `$id` deiner Datenbank in der Ausgabe. + +## Schritt 3: DATABASE_ID im Skript setzen + +Öffne die Datei `appwrite_schema.sh` und ersetze `YOUR_DATABASE_ID` mit deiner tatsächlichen Datenbank-ID: + +```bash +DATABASE_ID="deine-datenbank-id-hier" +``` + +## Schritt 4: Skript ausführen + +### Windows (Git Bash oder WSL) + +```bash +chmod +x appwrite_schema.sh +./appwrite_schema.sh +``` + +### Windows PowerShell + +Da das Skript Bash-Befehle verwendet, benötigst du entweder: +- **Git Bash**: Öffne Git Bash im Server-Ordner und führe das Skript aus +- **WSL**: Führe das Skript in WSL aus + +Alternativ kannst du die Befehle manuell in PowerShell ausführen (nicht empfohlen). + +## Was wird erstellt? + +Das Skript erstellt 4 Tabellen: + +1. **users** - Benutzerinformationen +2. **accounts** - Plattform-Accounts (Amazon, eBay) +3. **products** - Produktinformationen +4. **product_details** - Detaillierte Produktinformationen + +## Fehlerbehandlung + +Wenn ein Fehler auftritt, zeigt das Skript `(ignored error, rc=X)`. Das bedeutet, dass die Ressource möglicherweise bereits existiert. Das ist normal, wenn du das Skript mehrfach ausführst (idempotent). + +## Hilfe + +Bei Problemen: +```bash +appwrite databases --help +appwrite databases create-table --help +``` diff --git a/setup/README.md b/setup/README.md new file mode 100644 index 0000000..83d6d29 --- /dev/null +++ b/setup/README.md @@ -0,0 +1,31 @@ +# Setup & Konfiguration + +Dieser Ordner enthält alle Setup-Skripte, Konfigurationsdateien und temporäre Dateien, die nicht direkt zum Code gehören. + +## Inhalt + +- **db.txt** - Ursprüngliches Datenbank-Schema-Skript (für ältere Appwrite CLI) +- **setup-database.ps1** - PowerShell-Setup-Skript für die Datenbank +- **appwrite.config.json** - Appwrite-Konfigurationsdatei +- **CLI_CONNECTION.md** - Anleitung zur Appwrite CLI-Verbindung +- **DATABASE_SETUP.md** - Anleitung zum Datenbank-Setup +- **SCHEMA_UPDATE_NOTE.md** - Hinweise zur Schema-Aktualisierung + +## Warum dieser Ordner? + +Diese Dateien sind: +- **Temporäre/Setup-Dateien**: Werden nur einmalig für das Initial-Setup benötigt +- **Konfigurationsdateien**: Enthalten Projekt-spezifische Einstellungen +- **Dokumentation**: Setup-Anleitungen und Notizen + +Sie gehören nicht zum eigentlichen Code in `Extension/` oder `Server/` und halten das Projekt sauber organisiert. + +## Projektstruktur + +``` +eship/ +├── Extension/ # Browser-Extension (Code) +├── Server/ # React Server-App (Code) +├── setup/ # Setup & Konfiguration (dieser Ordner) +└── README.md # Haupt-Projekt-README +``` diff --git a/setup/SCHEMA_UPDATE_NOTE.md b/setup/SCHEMA_UPDATE_NOTE.md new file mode 100644 index 0000000..8107d62 --- /dev/null +++ b/setup/SCHEMA_UPDATE_NOTE.md @@ -0,0 +1,28 @@ +# WICHTIG: Schema-Skript Aktualisierung erforderlich + +Das ursprüngliche Schema-Skript (`appwrite_schema.sh`) wurde für eine ältere Appwrite CLI-Version geschrieben, die "Tables" und "Columns" verwendete. + +Die aktuelle Appwrite CLI verwendet "Collections" und "Attributes". + +## Benötigte Änderungen: + +1. **`create-table` → `create-collection`** +2. **`--table-id` → `--collection-id`** +3. **`--row-security` → `--document-security`** +4. **`create-*-column` → `create-*-attribute`** + - `create-string-column` → `create-string-attribute` + - `create-boolean-column` → `create-boolean-attribute` + - `create-enum-column` → `create-enum-attribute` + - `create-float-column` → `create-float-attribute` + - `create-integer-column` → `create-integer-attribute` + - `create-url-column` → `create-url-attribute` +5. **`create-index` benötigt `--collection-id` statt `--table-id`** + +## Lösung: + +Das Skript muss vollständig auf die neue API-Struktur aktualisiert werden. +Dies kann entweder: +- Manuell über die Appwrite-Konsole erfolgen +- Oder über ein aktualisiertes Skript mit den neuen Befehlen + +**Hinweis:** Wenn du eine ältere Appwrite-Server-Version verwendest, die noch "Tables" unterstützt, funktioniert das ursprüngliche Skript möglicherweise. diff --git a/setup/appwrite.config.json b/setup/appwrite.config.json new file mode 100644 index 0000000..71c7939 --- /dev/null +++ b/setup/appwrite.config.json @@ -0,0 +1,3 @@ +{ + "projectId": "696b82bb0036d2e547ad" +} \ No newline at end of file diff --git a/setup/db.txt b/setup/db.txt new file mode 100644 index 0000000..e69de29 diff --git a/setup/setup-database.ps1 b/setup/setup-database.ps1 new file mode 100644 index 0000000..5672595 --- /dev/null +++ b/setup/setup-database.ps1 @@ -0,0 +1,119 @@ +# PowerShell Script zum Setup der Appwrite-Datenbank +# Dieses Skript erstellt die Datenbank und führt das Schema-Setup aus + +$ErrorActionPreference = "Continue" + +Write-Host "=== Appwrite Datenbank Setup ===" -ForegroundColor Cyan +Write-Host "" + +# 1. Prüfe Login-Status +Write-Host "1. Prüfe Appwrite Login-Status..." -ForegroundColor Yellow +$loginCheck = appwrite databases list 2>&1 + +if ($LASTEXITCODE -ne 0 -and $loginCheck -like "*Session not found*") { + Write-Host " [WARNUNG] Nicht eingeloggt. Bitte zuerst einloggen:" -ForegroundColor Red + Write-Host " appwrite login" -ForegroundColor White + Write-Host "" + Write-Host " Führe diesen Befehl jetzt aus..." -ForegroundColor Yellow + appwrite login + Write-Host "" +} + +# 2. Erstelle oder verwende vorhandene Datenbank +$DATABASE_ID = "eship-db" +$DATABASE_NAME = "eship-database" + +Write-Host "2. Prüfe ob Datenbank '$DATABASE_ID' existiert..." -ForegroundColor Yellow + +# Versuche die Datenbank zu finden +$dbList = appwrite databases list 2>&1 +$dbExists = $dbList -like "*$DATABASE_ID*" + +if (-not $dbExists) { + Write-Host " Datenbank nicht gefunden. Erstelle neue Datenbank..." -ForegroundColor Yellow + $createResult = appwrite databases create --name $DATABASE_NAME --database-id $DATABASE_ID 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] Datenbank '$DATABASE_ID' erfolgreich erstellt!" -ForegroundColor Green + } else { + Write-Host " [WARNUNG] Fehler beim Erstellen der Datenbank." -ForegroundColor Yellow + Write-Host " Verwende ID: $DATABASE_ID" -ForegroundColor Gray + Write-Host " Stelle sicher, dass die Datenbank-ID korrekt ist." -ForegroundColor Yellow + } +} else { + Write-Host " [OK] Datenbank '$DATABASE_ID' existiert bereits!" -ForegroundColor Green +} + +Write-Host "" +Write-Host "3. Erstelle Datenbank-Schema..." -ForegroundColor Yellow +Write-Host " Dies kann einige Minuten dauern..." -ForegroundColor Gray +Write-Host "" + +# Aktualisiere DATABASE_ID im Bash-Skript +$schemaScriptContent = Get-Content "appwrite_schema.sh" -Raw -Encoding UTF8 +$oldDatabaseIdPattern = 'DATABASE_ID="YOUR_DATABASE_ID"' +$newDatabaseIdValue = "DATABASE_ID=`"$DATABASE_ID`"" +$schemaScriptContent = $schemaScriptContent -replace [regex]::Escape($oldDatabaseIdPattern), $newDatabaseIdValue +Set-Content -Path "appwrite_schema.sh" -Value $schemaScriptContent -NoNewline -Encoding UTF8 + +Write-Host " DATABASE_ID im Schema-Skript auf '$DATABASE_ID' gesetzt" -ForegroundColor Green +Write-Host "" + +# Prüfe ob Git Bash oder WSL verfügbar ist +$bashPath = $null +$useBash = $false + +# Versuche Git Bash zu finden +if (Test-Path "C:\Program Files\Git\bin\bash.exe") { + $bashPath = "C:\Program Files\Git\bin\bash.exe" + $useBash = $true +} elseif (Test-Path "C:\Program Files (x86)\Git\bin\bash.exe") { + $bashPath = "C:\Program Files (x86)\Git\bin\bash.exe" + $useBash = $true +} else { + # Prüfe ob WSL verfügbar ist + $wslCheck = wsl --list 2>&1 + if ($LASTEXITCODE -eq 0) { + $useBash = $true + $bashPath = "wsl" + } +} + +if ($useBash) { + Write-Host "4. Führe Schema-Skript aus (über Bash)..." -ForegroundColor Yellow + Write-Host "" + + $scriptPath = (Resolve-Path "appwrite_schema.sh").Path + + if ($bashPath -eq "wsl") { + # Konvertiere Windows-Pfad zu WSL-Pfad + $driveLetter = $scriptPath.Substring(0, 1).ToLower() + $remainingPath = $scriptPath.Substring(3) -replace '\\', '/' + $wslPath = "/mnt/$driveLetter$remainingPath" + wsl bash $wslPath + } else { + & $bashPath $scriptPath + } + + if ($LASTEXITCODE -eq 0) { + Write-Host "" + Write-Host "[OK] Schema-Setup erfolgreich abgeschlossen!" -ForegroundColor Green + } else { + Write-Host "" + Write-Host "[WARNUNG] Schema-Setup abgeschlossen (einige Fehler wurden ignoriert)" -ForegroundColor Yellow + } +} else { + Write-Host "4. Bash nicht gefunden." -ForegroundColor Yellow + Write-Host "" + Write-Host " Option 1: Installiere Git Bash und führe dann aus:" -ForegroundColor White + Write-Host " bash appwrite_schema.sh" -ForegroundColor Cyan + Write-Host "" + Write-Host " Option 2: Siehe DATABASE_SETUP.md für manuelle Anweisungen" -ForegroundColor White + Write-Host "" +} + +Write-Host "" +Write-Host "=== Setup abgeschlossen ===" -ForegroundColor Cyan +Write-Host "" +Write-Host "Datenbank-ID: $DATABASE_ID" -ForegroundColor White +Write-Host "Du kannst die Datenbank in der Appwrite-Konsole überprüfen." -ForegroundColor Gray