feat: adiciona script de setup automático do Appwrite

- 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
This commit is contained in:
Tiago Yamamoto 2025-12-11 20:06:21 -03:00
parent 8380a470be
commit e6d5bef07a
4 changed files with 617 additions and 2 deletions

172
SETUP_GUIDE.md Normal file
View file

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

31
package-lock.json generated
View file

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

View file

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

406
setup-appwrite.js Normal file
View file

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