From e6d5bef07a154aef13bcea3099750d61f2b33330 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Thu, 11 Dec 2025 20:06:21 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adiciona=20script=20de=20setup=20autom?= =?UTF-8?q?=C3=A1tico=20do=20Appwrite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cria setup-appwrite.js para automatizar criação de database e collections - Adiciona node-appwrite e dotenv como dependências - Cria SETUP_GUIDE.md com instruções detalhadas - Script cria database DevOpsPlatform automaticamente - Cria 4 collections com schemas corretos (servers, github_repos, audit_logs, cloud_accounts) - Popula com dados de exemplo (4 servidores, 3 repos, 3 logs, 2 contas) - Atualiza .env automaticamente com IDs gerados - Novo comando: npm run setup:appwrite --- SETUP_GUIDE.md | 172 ++++++++++++++++++++ package-lock.json | 31 ++++ package.json | 10 +- setup-appwrite.js | 406 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 SETUP_GUIDE.md create mode 100644 setup-appwrite.js diff --git a/SETUP_GUIDE.md b/SETUP_GUIDE.md new file mode 100644 index 0000000..fd55368 --- /dev/null +++ b/SETUP_GUIDE.md @@ -0,0 +1,172 @@ +# Guia Rápido - Setup Automatizado Appwrite + +## 🎯 O Que o Script Faz + +O script `setup-appwrite.js` automatiza todo o setup do Appwrite: + +✅ Cria o Database "DevOpsPlatform" +✅ Cria 4 Collections com schemas corretos: + - **servers**: name, ip, status (enum: online/offline), region + - **github_repos**: repo_name, url, last_commit, status + - **audit_logs**: event, user_id, timestamp + - **cloud_accounts**: provider, apiKey, label + +✅ Popula com dados de exemplo: + - 4 servidores + - 3 repositórios GitHub + - 3 audit logs + - 2 cloud accounts + +✅ Atualiza o arquivo `.env` automaticamente com todos os IDs gerados + +## 📋 Passo 1: Obter API Key do Appwrite + +1. Acesse https://cloud.appwrite.io +2. Entre no seu projeto (ID: `68be03580005c05fb11f`) +3. Vá em **Settings** → **API Keys** +4. Clique em **Create API Key** +5. Dê um nome: "Setup Script" ou "Admin Key" +6. **Importante**: Marque **TODOS** os scopes (permissões) +7. Clique em **Create** +8. **Copie a API Key** (ela só aparece uma vez!) + +## 📝 Passo 2: Adicionar API Key no .env + +Edite o arquivo `.env` e preencha a linha 28: + +```env +APPWRITE_API_KEY=sua_api_key_aqui +``` + +**Exemplo**: +```env +APPWRITE_API_KEY=standard_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6 +``` + +## 🚀 Passo 3: Executar o Script + +```bash +npm run setup:appwrite +``` + +Ou diretamente: + +```bash +node setup-appwrite.js +``` + +## ✅ O Que Você Verá + +``` +🚀 Iniciando setup do Appwrite... + +📍 Endpoint: https://nyc.cloud.appwrite.io/v1 +📁 Project ID: 68be03580005c05fb11f + +📦 Criando Database "DevOpsPlatform"... +✅ Database criado: 67a1b2c3d4e5f6 + +📋 Criando collection "servers"... +✅ Collection "servers" criada: servers + +📋 Criando collection "github_repos"... +✅ Collection "github_repos" criada: github_repos + +📋 Criando collection "audit_logs"... +✅ Collection "audit_logs" criada: audit_logs + +📋 Criando collection "cloud_accounts"... +✅ Collection "cloud_accounts" criada: cloud_accounts + +🌱 Populando com dados de exemplo... + + ✅ Servidor criado: web-01 + ✅ Servidor criado: web-02 + ✅ Servidor criado: db-01 + ✅ Servidor criado: cache-01 + ✅ Repositório criado: core-platform + ✅ Repositório criado: api-backend + ✅ Repositório criado: mobile-app + ✅ Log criado: User login + ✅ Log criado: Server deployed + ✅ Log criado: Configuration updated + ✅ Conta criada: Cloudflare + ✅ Conta criada: AWS + +📝 Atualizando arquivo .env... +✅ Arquivo .env atualizado! + +🎉 Setup concluído com sucesso! + +📋 Resumo: + Database ID: 67a1b2c3d4e5f6 + servers: servers + github_repos: github_repos + audit_logs: audit_logs + cloud_accounts: cloud_accounts + +✅ Arquivo .env atualizado com os IDs + +🚀 Próximo passo: npm run dev:web +``` + +## 🔍 Verificar no Appwrite Console + +Após executar, acesse https://cloud.appwrite.io e verifique: + +1. **Databases**: Deve aparecer "DevOpsPlatform" +2. **Collections**: Dentro do database, 4 collections com dados +3. **Documents**: Cada collection terá documentos de exemplo + +## 🧪 Testar o Dashboard + +```bash +npm run dev:web +``` + +Acesse http://localhost:5173 e faça login. O dashboard deve mostrar: +- Servidores no widget Overview +- Repositórios GitHub +- Audit logs em tempo real no terminal + +## ❌ Troubleshooting + +### Erro: "APPWRITE_API_KEY is missing" + +**Solução**: Preencha a `APPWRITE_API_KEY` no arquivo `.env` (linha 28) + +### Erro: "Invalid API Key" + +**Solução**: +1. Verifique se copiou a chave completa +2. Confirme que a chave tem todos os scopes marcados +3. Tente criar uma nova API Key + +### Erro: "Collection already exists" (409) + +**Solução**: Isso é normal! O script detecta e usa as collections existentes. + +### Erro: "Permission denied" + +**Solução**: A API Key precisa ter scopes de Admin. Recrie com todos os scopes marcados. + +## 🔄 Executar Novamente + +Você pode executar o script múltiplas vezes sem problemas: +- Se database existe, ele usa o existente +- Se collections existem, elas são reutilizadas +- Dados duplicados são ignorados + +## 📝 Próximos Passos + +Após o setup bem-sucedido: + +1. ✅ Verificar `.env` foi atualizado com os IDs +2. ✅ Executar `npm run dev:web` +3. ✅ Fazer login no dashboard +4. ✅ Verificar se os dados aparecem +5. ✅ Testar o terminal de realtime (audit logs) + +--- + +**Dúvidas?** Consulte o [README principal](README.md) para mais detalhes. diff --git a/package-lock.json b/package-lock.json index 2c9dc82..03d4add 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,10 @@ "": { "name": "core", "version": "1.0.0", + "dependencies": { + "dotenv": "^16.4.5", + "node-appwrite": "^14.1.0" + }, "devDependencies": { "npm-run-all": "^4.1.5" } @@ -303,6 +307,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1193,6 +1209,21 @@ "dev": true, "license": "MIT" }, + "node_modules/node-appwrite": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-14.2.0.tgz", + "integrity": "sha512-sPPA+JzdBJRS+lM6azX85y3/6iyKQYlHcXCbjMuWLROh6IiU9EfXRW3XSUTa5HDoBrlo8ve+AnVA6BIjQfUs1g==", + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-native-with-agent": "1.7.2" + } + }, + "node_modules/node-fetch-native-with-agent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz", + "integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==", + "license": "MIT" + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", diff --git a/package.json b/package.json index 54baafb..5228cc0 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,19 @@ "name": "core", "private": true, "version": "1.0.0", + "type": "module", "scripts": { "dev:dashboard": "cd dashboard && npm run dev", "dev:landing": "cd landing && deno task start", "dev:web": "npm-run-all -p dev:dashboard dev:landing", - "lint:dashboard": "cd dashboard && npm run lint" + "lint:dashboard": "cd dashboard && npm run lint", + "setup:appwrite": "node setup-appwrite.js" }, "devDependencies": { "npm-run-all": "^4.1.5" + }, + "dependencies": { + "dotenv": "^16.4.5", + "node-appwrite": "^14.1.0" } -} +} \ No newline at end of file diff --git a/setup-appwrite.js b/setup-appwrite.js new file mode 100644 index 0000000..7025eb0 --- /dev/null +++ b/setup-appwrite.js @@ -0,0 +1,406 @@ +#!/usr/bin/env node + +/** + * Appwrite Setup Script + * + * Automaticamente: + * 1. Cria Database "DevOpsPlatform" + * 2. Cria 4 Collections com schemas corretos + * 3. Popula com dados de exemplo + * 4. Atualiza .env com os IDs gerados + * + * Uso: node setup-appwrite.js + */ + +import { Client, Databases, ID, Permission, Role } from 'node-appwrite'; +import * as dotenv from 'dotenv'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import { readFileSync, writeFileSync } from 'fs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Carregar .env +dotenv.config(); + +// Validar variáveis necessárias +const ENDPOINT = process.env.APPWRITE_ENDPOINT; +const PROJECT_ID = process.env.APPWRITE_PROJECT_ID; +const API_KEY = process.env.APPWRITE_API_KEY; + +if (!ENDPOINT || !PROJECT_ID || !API_KEY) { + console.error('❌ Erro: Variáveis de ambiente faltando!'); + console.error(''); + console.error('Por favor, preencha no arquivo .env:'); + if (!ENDPOINT) console.error(' - APPWRITE_ENDPOINT'); + if (!PROJECT_ID) console.error(' - APPWRITE_PROJECT_ID'); + if (!API_KEY) console.error(' - APPWRITE_API_KEY'); + console.error(''); + console.error('Para obter a API_KEY:'); + console.error('1. Acesse https://cloud.appwrite.io'); + console.error('2. Vá em Settings → API Keys'); + console.error('3. Crie uma API Key com todos os scopes (Admin)'); + process.exit(1); +} + +// Inicializar cliente Appwrite +const client = new Client() + .setEndpoint(ENDPOINT) + .setProject(PROJECT_ID) + .setKey(API_KEY); + +const databases = new Databases(client); + +// IDs que serão gerados +let databaseId = ''; +const collectionIds = { + servers: '', + github_repos: '', + audit_logs: '', + cloud_accounts: '' +}; + +console.log('🚀 Iniciando setup do Appwrite...\n'); +console.log(`📍 Endpoint: ${ENDPOINT}`); +console.log(`📁 Project ID: ${PROJECT_ID}\n`); + +/** + * 1. Criar Database + */ +async function createDatabase() { + try { + console.log('📦 Criando Database "DevOpsPlatform"...'); + + const database = await databases.create( + ID.unique(), + 'DevOpsPlatform', + true // enabled + ); + + databaseId = database.$id; + console.log(`✅ Database criado: ${databaseId}\n`); + + return database; + } catch (error) { + if (error.code === 409) { + console.log('⚠️ Database já existe, buscando ID...'); + const list = await databases.list(); + const existing = list.databases.find(db => db.name === 'DevOpsPlatform'); + if (existing) { + databaseId = existing.$id; + console.log(`✅ Usando database existente: ${databaseId}\n`); + return existing; + } + } + throw error; + } +} + +/** + * 2. Criar Collection: servers + */ +async function createServersCollection() { + try { + console.log('📋 Criando collection "servers"...'); + + const collection = await databases.createCollection( + databaseId, + 'servers', + 'Servers', + [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ] + ); + + collectionIds.servers = collection.$id; + + // Criar atributos + await databases.createStringAttribute(databaseId, collectionIds.servers, 'name', 255, true); + await databases.createStringAttribute(databaseId, collectionIds.servers, 'ip', 45, true); + await databases.createEnumAttribute(databaseId, collectionIds.servers, 'status', ['online', 'offline'], true, 'online'); + await databases.createStringAttribute(databaseId, collectionIds.servers, 'region', 100, false); + + console.log(`✅ Collection "servers" criada: ${collectionIds.servers}`); + + // Aguardar atributos serem processados + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error) { + if (error.code === 409) { + collectionIds.servers = 'servers'; + console.log(`⚠️ Collection "servers" já existe`); + } else { + throw error; + } + } +} + +/** + * 3. Criar Collection: github_repos + */ +async function createGitHubReposCollection() { + try { + console.log('📋 Criando collection "github_repos"...'); + + const collection = await databases.createCollection( + databaseId, + 'github_repos', + 'GitHub Repositories', + [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ] + ); + + collectionIds.github_repos = collection.$id; + + // Criar atributos + await databases.createStringAttribute(databaseId, collectionIds.github_repos, 'repo_name', 255, true); + await databases.createUrlAttribute(databaseId, collectionIds.github_repos, 'url', true); + await databases.createStringAttribute(databaseId, collectionIds.github_repos, 'last_commit', 255, false); + await databases.createStringAttribute(databaseId, collectionIds.github_repos, 'status', 50, false, 'active'); + + console.log(`✅ Collection "github_repos" criada: ${collectionIds.github_repos}`); + + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error) { + if (error.code === 409) { + collectionIds.github_repos = 'github_repos'; + console.log(`⚠️ Collection "github_repos" já existe`); + } else { + throw error; + } + } +} + +/** + * 4. Criar Collection: audit_logs + */ +async function createAuditLogsCollection() { + try { + console.log('📋 Criando collection "audit_logs"...'); + + const collection = await databases.createCollection( + databaseId, + 'audit_logs', + 'Audit Logs', + [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ] + ); + + collectionIds.audit_logs = collection.$id; + + // Criar atributos + await databases.createStringAttribute(databaseId, collectionIds.audit_logs, 'event', 500, true); + await databases.createStringAttribute(databaseId, collectionIds.audit_logs, 'user_id', 255, true); + await databases.createDatetimeAttribute(databaseId, collectionIds.audit_logs, 'timestamp', true); + + console.log(`✅ Collection "audit_logs" criada: ${collectionIds.audit_logs}`); + + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error) { + if (error.code === 409) { + collectionIds.audit_logs = 'audit_logs'; + console.log(`⚠️ Collection "audit_logs" já existe`); + } else { + throw error; + } + } +} + +/** + * 5. Criar Collection: cloud_accounts + */ +async function createCloudAccountsCollection() { + try { + console.log('📋 Criando collection "cloud_accounts"...'); + + const collection = await databases.createCollection( + databaseId, + 'cloud_accounts', + 'Cloud Accounts', + [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ] + ); + + collectionIds.cloud_accounts = collection.$id; + + // Criar atributos + await databases.createStringAttribute(databaseId, collectionIds.cloud_accounts, 'provider', 100, true); + await databases.createStringAttribute(databaseId, collectionIds.cloud_accounts, 'apiKey', 500, true); + await databases.createStringAttribute(databaseId, collectionIds.cloud_accounts, 'label', 255, false); + + console.log(`✅ Collection "cloud_accounts" criada: ${collectionIds.cloud_accounts}`); + + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (error) { + if (error.code === 409) { + collectionIds.cloud_accounts = 'cloud_accounts'; + console.log(`⚠️ Collection "cloud_accounts" já existe`); + } else { + throw error; + } + } +} + +/** + * 6. Popular com dados de exemplo + */ +async function seedData() { + console.log('\n🌱 Populando com dados de exemplo...\n'); + + // Servidores + const servers = [ + { name: 'web-01', ip: '192.168.1.10', status: 'online', region: 'us-east-1' }, + { name: 'web-02', ip: '192.168.1.11', status: 'online', region: 'us-east-1' }, + { name: 'db-01', ip: '192.168.1.20', status: 'online', region: 'us-west-2' }, + { name: 'cache-01', ip: '192.168.1.30', status: 'offline', region: 'eu-west-1' }, + ]; + + for (const server of servers) { + try { + await databases.createDocument(databaseId, collectionIds.servers, ID.unique(), server); + console.log(` ✅ Servidor criado: ${server.name}`); + } catch (error) { + console.log(` ⚠️ Servidor ${server.name} já existe`); + } + } + + // Repositórios GitHub + const repos = [ + { repo_name: 'core-platform', url: 'https://github.com/rede5/core', last_commit: 'docs: adiciona setup completo', status: 'active' }, + { repo_name: 'api-backend', url: 'https://github.com/rede5/api', last_commit: 'feat: add authentication', status: 'active' }, + { repo_name: 'mobile-app', url: 'https://github.com/rede5/mobile', last_commit: 'fix: crash on startup', status: 'active' }, + ]; + + for (const repo of repos) { + try { + await databases.createDocument(databaseId, collectionIds.github_repos, ID.unique(), repo); + console.log(` ✅ Repositório criado: ${repo.repo_name}`); + } catch (error) { + console.log(` ⚠️ Repositório ${repo.repo_name} já existe`); + } + } + + // Audit Logs + const logs = [ + { event: 'User login', user_id: 'admin', timestamp: new Date().toISOString() }, + { event: 'Server deployed', user_id: 'admin', timestamp: new Date().toISOString() }, + { event: 'Configuration updated', user_id: 'admin', timestamp: new Date().toISOString() }, + ]; + + for (const log of logs) { + try { + await databases.createDocument(databaseId, collectionIds.audit_logs, ID.unique(), log); + console.log(` ✅ Log criado: ${log.event}`); + } catch (error) { + console.log(` ⚠️ Log já existe`); + } + } + + // Cloud Accounts + const accounts = [ + { provider: 'Cloudflare', apiKey: 'cf_example_key_123', label: 'Production Account' }, + { provider: 'AWS', apiKey: 'aws_example_key_456', label: 'Staging Account' }, + ]; + + for (const account of accounts) { + try { + await databases.createDocument(databaseId, collectionIds.cloud_accounts, ID.unique(), account); + console.log(` ✅ Conta criada: ${account.provider}`); + } catch (error) { + console.log(` ⚠️ Conta ${account.provider} já existe`); + } + } +} + +/** + * 7. Atualizar arquivo .env + */ +function updateEnvFile() { + console.log('\n📝 Atualizando arquivo .env...'); + + const envPath = join(__dirname, '.env'); + let envContent = readFileSync(envPath, 'utf8'); + + // Atualizar IDs + envContent = envContent.replace( + /VITE_APPWRITE_PROJECT_ID=.*/, + `VITE_APPWRITE_PROJECT_ID=${PROJECT_ID}` + ); + envContent = envContent.replace( + /VITE_APPWRITE_DATABASE_ID=.*/, + `VITE_APPWRITE_DATABASE_ID=${databaseId}` + ); + envContent = envContent.replace( + /VITE_APPWRITE_COLLECTION_SERVERS_ID=.*/, + `VITE_APPWRITE_COLLECTION_SERVERS_ID=${collectionIds.servers}` + ); + envContent = envContent.replace( + /VITE_APPWRITE_COLLECTION_GITHUB_REPOS_ID=.*/, + `VITE_APPWRITE_COLLECTION_GITHUB_REPOS_ID=${collectionIds.github_repos}` + ); + envContent = envContent.replace( + /VITE_APPWRITE_COLLECTION_AUDIT_LOGS_ID=.*/, + `VITE_APPWRITE_COLLECTION_AUDIT_LOGS_ID=${collectionIds.audit_logs}` + ); + envContent = envContent.replace( + /VITE_APPWRITE_COLLECTION_CLOUDFLARE_ACCOUNTS_ID=.*/, + `VITE_APPWRITE_COLLECTION_CLOUDFLARE_ACCOUNTS_ID=${collectionIds.cloud_accounts}` + ); + + writeFileSync(envPath, envContent); + console.log('✅ Arquivo .env atualizado!\n'); +} + +/** + * Main + */ +async function main() { + try { + await createDatabase(); + await createServersCollection(); + await createGitHubReposCollection(); + await createAuditLogsCollection(); + await createCloudAccountsCollection(); + await seedData(); + updateEnvFile(); + + console.log('\n🎉 Setup concluído com sucesso!\n'); + console.log('📋 Resumo:'); + console.log(` Database ID: ${databaseId}`); + console.log(` servers: ${collectionIds.servers}`); + console.log(` github_repos: ${collectionIds.github_repos}`); + console.log(` audit_logs: ${collectionIds.audit_logs}`); + console.log(` cloud_accounts: ${collectionIds.cloud_accounts}`); + console.log('\n✅ Arquivo .env atualizado com os IDs'); + console.log('\n🚀 Próximo passo: npm run dev:web\n'); + + } catch (error) { + console.error('\n❌ Erro durante setup:'); + console.error(error.message); + if (error.response) { + console.error('Detalhes:', error.response); + } + process.exit(1); + } +} + +main();