This commit is contained in:
2026-01-17 17:07:46 +01:00
commit e73ddd52ad
22 changed files with 2609 additions and 0 deletions

36
.cursor/rules/setup.mdc Normal file
View File

@@ -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

189
Extension/README.md Normal file
View File

@@ -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)

View File

@@ -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');

17
Extension/config.js Normal file
View File

@@ -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 };
}

View File

@@ -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 = `
<style>
#eship-blocked-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
#eship-blocked-overlay .blocked-icon {
font-size: 64px;
margin-bottom: 24px;
}
#eship-blocked-overlay h1 {
color: #FD366E;
font-size: 28px;
margin: 0 0 16px 0;
font-weight: 600;
}
#eship-blocked-overlay p {
color: #9ca3af;
font-size: 16px;
margin: 0 0 32px 0;
text-align: center;
max-width: 400px;
line-height: 1.6;
}
#eship-blocked-overlay .instructions {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 24px;
max-width: 400px;
}
#eship-blocked-overlay .instructions h2 {
color: #fff;
font-size: 14px;
margin: 0 0 16px 0;
text-transform: uppercase;
letter-spacing: 1px;
}
#eship-blocked-overlay .instructions ol {
color: #d1d5db;
font-size: 14px;
margin: 0;
padding-left: 20px;
line-height: 2;
}
#eship-blocked-overlay .instructions li {
margin-bottom: 4px;
}
</style>
<div class="blocked-icon">&#128274;</div>
<h1>Zugriff gesperrt</h1>
<p>Diese Website ist geschuetzt und erfordert eine Authentifizierung ueber die EShip Browser-Extension.</p>
<div class="instructions">
<h2>So melden Sie sich an:</h2>
<ol>
<li>Klicken Sie auf das EShip-Icon in der Browser-Toolbar</li>
<li>Geben Sie Ihre E-Mail und Passwort ein</li>
<li>Klicken Sie auf "Anmelden"</li>
<li>Die Seite wird automatisch freigeschaltet</li>
</ol>
</div>
`;
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 });
}
})();

209
Extension/lib/appwrite.min.js vendored Normal file
View File

@@ -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);

44
Extension/manifest.json Normal file
View File

@@ -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/*"]
}
]
}

315
Extension/popup/popup.css Normal file
View File

@@ -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;
}

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=400, initial-scale=1.0">
<title>EShip Extension</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<header>
<h1>EShip</h1>
</header>
<!-- Loading State -->
<div id="loading" class="state">
<div class="spinner"></div>
<p>Laden...</p>
</div>
<!-- Login Form (logged out state) -->
<div id="login-form" class="state hidden">
<h2>Anmelden</h2>
<form id="auth-form">
<div class="form-group">
<label for="email">E-Mail</label>
<input type="email" id="email" name="email" required autocomplete="email">
</div>
<div class="form-group">
<label for="password">Passwort</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<div id="error-message" class="error hidden"></div>
<button type="submit" id="login-btn" class="btn btn-primary">
<span class="btn-text">Anmelden</span>
<span class="btn-spinner hidden"></span>
</button>
</form>
</div>
<!-- Tools Menu (logged in state) -->
<div id="tools-menu" class="state hidden">
<div class="user-info">
<span class="user-icon">&#128100;</span>
<span id="user-email"></span>
<button id="logout-btn" class="btn btn-small btn-secondary">Abmelden</button>
</div>
<h2>Tools</h2>
<div id="tools-list">
<!-- Tools will be rendered here dynamically -->
</div>
<div class="actions">
<button id="open-site-btn" class="btn btn-primary">Website oeffnen</button>
</div>
</div>
</div>
<script src="popup.js" type="module"></script>
</body>
</html>

319
Extension/popup/popup.js Normal file
View File

@@ -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 = `
<div class="tool-header">
<span class="tool-name">${escapeHtml(tool.name)}</span>
<label class="toggle">
<input type="checkbox" data-tool-id="${tool.id}" ${tool.enabled ? 'checked' : ''}>
<span class="toggle-slider"></span>
</label>
</div>
<div class="tool-settings ${tool.enabled ? 'visible' : ''}" id="settings-${tool.id}">
${renderToolSettings(tool)}
</div>
`;
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 += `
<div class="setting-row">
<label>${escapeHtml(formatSettingName(key))}</label>
<input type="text" data-setting="${key}" value="${escapeHtml(value)}">
</div>
`;
}
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;
}

28
README.md Normal file
View File

@@ -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.

1
Server Submodule

Submodule Server added at 6100df3f83

230
db.txt Normal file
View File

@@ -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:<id>, team:<id>
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"

53
setup/API_KEY_SETUP.md Normal file
View File

@@ -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

58
setup/API_SERVER_SETUP.md Normal file
View File

@@ -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

57
setup/CLI_CONNECTION.md Normal file
View File

@@ -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.

81
setup/DATABASE_SETUP.md Normal file
View File

@@ -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
```

31
setup/README.md Normal file
View File

@@ -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
```

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
{
"projectId": "696b82bb0036d2e547ad"
}

0
setup/db.txt Normal file
View File

119
setup/setup-database.ps1 Normal file
View File

@@ -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