mozhost-cli 2.0.0 β†’ 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mozhost.js CHANGED
@@ -5,6 +5,7 @@ const chalk = require('chalk');
5
5
  const authCommands = require('../src/commands/auth');
6
6
  const containerCommands = require('../src/commands/containers');
7
7
  const deployCommands = require('../src/commands/deploy');
8
+ const databaseCommands = require('../src/commands/databases'); // πŸ‘ˆ NOVO
8
9
  const packageJson = require('../package.json');
9
10
 
10
11
  program
@@ -12,7 +13,9 @@ program
12
13
  .description('CLI for MozHost - Hospedagem de Bots e APIs')
13
14
  .version(packageJson.version);
14
15
 
15
- // Auth commands
16
+ // ============================================
17
+ // AUTH COMMANDS
18
+ // ============================================
16
19
  program
17
20
  .command('auth')
18
21
  .description('Autenticar na MozHost')
@@ -28,7 +31,9 @@ program
28
31
  .description('Ver usuΓ‘rio atual')
29
32
  .action(authCommands.whoami);
30
33
 
31
- // Container commands
34
+ // ============================================
35
+ // CONTAINER COMMANDS
36
+ // ============================================
32
37
  program
33
38
  .command('containers')
34
39
  .alias('ls')
@@ -79,7 +84,48 @@ program
79
84
  .description('Ver URL do container')
80
85
  .action(containerCommands.url);
81
86
 
82
- // Deploy commands
87
+ // ============================================
88
+ // DATABASE COMMANDS πŸ‘ˆ NOVO
89
+ // ============================================
90
+ program
91
+ .command('db:list')
92
+ .alias('db:ls')
93
+ .description('Listar databases')
94
+ .action(databaseCommands.list);
95
+
96
+ program
97
+ .command('db:create')
98
+ .description('Criar novo database')
99
+ .requiredOption('-n, --name <name>', 'Nome do database')
100
+ .requiredOption('-t, --type <type>', 'Tipo (mysql, mariadb, postgres, mongodb, redis)')
101
+ .option('-c, --container <container>', 'Container para vincular (opcional)')
102
+ .action(databaseCommands.create);
103
+
104
+ program
105
+ .command('db:info <database>')
106
+ .description('Ver informaΓ§Γ΅es detalhadas do database')
107
+ .action(databaseCommands.info);
108
+
109
+ program
110
+ .command('db:credentials <database>')
111
+ .alias('db:creds')
112
+ .description('Ver credenciais do database')
113
+ .action(databaseCommands.credentials);
114
+
115
+ program
116
+ .command('db:link <database> <container>')
117
+ .description('Vincular database a um container')
118
+ .action(databaseCommands.link);
119
+
120
+ program
121
+ .command('db:delete <database>')
122
+ .alias('db:rm')
123
+ .description('Deletar database')
124
+ .action(databaseCommands.deleteDatabase);
125
+
126
+ // ============================================
127
+ // DEPLOY COMMANDS
128
+ // ============================================
83
129
  program
84
130
  .command('init')
85
131
  .description('Inicializar projeto para deploy')
@@ -96,11 +142,22 @@ program
96
142
  .description('Vincular diretΓ³rio atual a um container')
97
143
  .action(deployCommands.link);
98
144
 
99
- // Parse arguments
145
+ // ============================================
146
+ // PARSE & HELP
147
+ // ============================================
100
148
  program.parse(process.argv);
101
149
 
102
150
  // Show help if no command
103
151
  if (!process.argv.slice(2).length) {
104
152
  console.log(chalk.cyan.bold('\nπŸš€ MozHost CLI\n'));
105
153
  program.outputHelp();
154
+
155
+ // Mostrar exemplos ΓΊteis
156
+ console.log(chalk.gray('\nExemplos:'));
157
+ console.log(chalk.white(' mozhost auth # Autenticar'));
158
+ console.log(chalk.white(' mozhost ls # Listar containers'));
159
+ console.log(chalk.white(' mozhost create -n app -t nodejs # Criar container'));
160
+ console.log(chalk.white(' mozhost db:list # Listar databases'));
161
+ console.log(chalk.white(' mozhost db:create -n db -t mysql # Criar database'));
162
+ console.log();
106
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mozhost-cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Command-line interface for MozHost container platform",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -3,12 +3,60 @@ const ora = require('ora');
3
3
  const inquirer = require('inquirer');
4
4
  const api = require('../utils/api');
5
5
 
6
+ // ============================================
7
+ // HELPER FUNCTION - Resolver Nome ou ID
8
+ // ============================================
9
+ async function resolveContainer(identifier) {
10
+ try {
11
+ // Tenta buscar diretamente por ID
12
+ const response = await api.getContainer(identifier);
13
+ return response.container;
14
+ } catch (error) {
15
+ // Se nΓ£o encontrar (404), busca na lista por nome
16
+ if (error.response?.status === 404 || error.response?.data?.error?.includes('not found')) {
17
+ const listResponse = await api.listContainers();
18
+
19
+ // Procura por nome, ID completo ou ID parcial
20
+ const found = listResponse.containers.find(c =>
21
+ c.name === identifier ||
22
+ c.id === identifier ||
23
+ c.id.startsWith(identifier)
24
+ );
25
+
26
+ if (!found) {
27
+ // Procura containers similares para sugerir
28
+ const similar = listResponse.containers.filter(c =>
29
+ c.name.toLowerCase().includes(identifier.toLowerCase()) ||
30
+ c.id.includes(identifier)
31
+ );
32
+
33
+ if (similar.length > 0) {
34
+ console.log(chalk.yellow('\nπŸ’‘ VocΓͺ quis dizer:'));
35
+ similar.forEach(c => {
36
+ console.log(chalk.white(` β€’ ${c.name} ${chalk.gray(`(${c.id.slice(0, 8)})`)}`));
37
+ });
38
+ } else {
39
+ console.log(chalk.gray('\nπŸ’‘ Use: mozhost ls para ver todos os containers'));
40
+ }
41
+
42
+ throw new Error(`Container "${identifier}" nΓ£o encontrado`);
43
+ }
44
+
45
+ return found;
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ // ============================================
52
+ // LIST - Listar todos os containers
53
+ // ============================================
6
54
  async function list() {
7
55
  try {
8
56
  const spinner = ora('Carregando containers...').start();
9
-
57
+
10
58
  const response = await api.listContainers();
11
-
59
+
12
60
  spinner.stop();
13
61
 
14
62
  if (response.containers.length === 0) {
@@ -22,7 +70,7 @@ async function list() {
22
70
  response.containers.forEach(container => {
23
71
  const statusColor = container.status === 'running' ? chalk.green : chalk.gray;
24
72
  const statusIcon = container.status === 'running' ? '●' : 'β—‹';
25
-
73
+
26
74
  console.log(`${statusColor(statusIcon)} ${chalk.white.bold(container.name)} ${chalk.gray(`(${container.id.slice(0, 8)})`)}`);
27
75
  console.log(` ${chalk.gray('Tipo:')} ${container.type}`);
28
76
  console.log(` ${chalk.gray('Status:')} ${statusColor(container.status)}`);
@@ -51,6 +99,9 @@ async function list() {
51
99
  }
52
100
  }
53
101
 
102
+ // ============================================
103
+ // CREATE - Criar novo container
104
+ // ============================================
54
105
  async function create(options) {
55
106
  try {
56
107
  const { name, type } = options;
@@ -79,85 +130,106 @@ async function create(options) {
79
130
  console.log(chalk.white(` Porta: ${response.container.port}`));
80
131
 
81
132
  console.log(chalk.gray('\nPrΓ³ximos passos:'));
82
- console.log(chalk.white(` 1. mozhost start ${response.container.id}`));
83
- console.log(chalk.white(` 2. mozhost deploy ${response.container.id}`));
133
+ console.log(chalk.white(` 1. mozhost start ${response.container.name}`));
134
+ console.log(chalk.white(` 2. mozhost deploy ${response.container.name}`));
84
135
 
85
136
  } catch (error) {
86
137
  console.error(chalk.red('\n❌ Erro ao criar container:'));
87
-
138
+
88
139
  if (error.response?.data) {
89
140
  console.error(chalk.red(` ${error.response.data.error || error.response.data.message}`));
90
-
141
+
91
142
  if (error.response.status === 403) {
92
143
  console.log(chalk.yellow('\nπŸ’‘ Dica: VocΓͺ atingiu o limite de containers do seu plano'));
93
144
  }
94
-
145
+
95
146
  if (error.response.status === 402) {
96
147
  console.log(chalk.yellow('\nπŸ’‘ Dica: VocΓͺ nΓ£o tem coins suficientes'));
97
148
  }
98
149
  } else {
99
150
  console.error(chalk.red(` ${error.message}`));
100
151
  }
101
-
152
+
102
153
  process.exit(1);
103
154
  }
104
155
  }
105
156
 
106
- async function start(containerId) {
157
+ // ============================================
158
+ // START - Iniciar container
159
+ // ============================================
160
+ async function start(identifier) {
107
161
  try {
108
- const spinner = ora('Iniciando container...').start();
109
-
110
- const response = await api.startContainer(containerId);
162
+ const spinner = ora('Resolvendo container...').start();
163
+ const container = await resolveContainer(identifier);
111
164
 
112
- spinner.succeed(chalk.green(`βœ… Container ${response.container.name} iniciado!`));
113
-
114
- const info = await api.getContainer(containerId);
115
- console.log(chalk.cyan(`\n🌐 URL: https://${info.container.domain}`));
165
+ spinner.text = `Iniciando ${container.name}...`;
166
+ const response = await api.startContainer(container.id);
167
+
168
+ spinner.succeed(chalk.green(`βœ… Container ${container.name} iniciado!`));
169
+
170
+ console.log(chalk.cyan(`\n🌐 URL: https://${container.domain}`));
116
171
 
117
172
  } catch (error) {
118
173
  console.error(chalk.red('\n❌ Erro ao iniciar container:'));
119
- console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
174
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
120
175
  process.exit(1);
121
176
  }
122
177
  }
123
178
 
124
- async function stop(containerId) {
179
+ // ============================================
180
+ // STOP - Parar container
181
+ // ============================================
182
+ async function stop(identifier) {
125
183
  try {
126
- const spinner = ora('Parando container...').start();
184
+ const spinner = ora('Resolvendo container...').start();
185
+ const container = await resolveContainer(identifier);
127
186
 
128
- const response = await api.stopContainer(containerId);
129
-
130
- spinner.succeed(chalk.green(`βœ… Container ${response.container.name} parado!`));
187
+ spinner.text = `Parando ${container.name}...`;
188
+ await api.stopContainer(container.id);
189
+
190
+ spinner.succeed(chalk.green(`βœ… Container ${container.name} parado!`));
131
191
 
132
192
  } catch (error) {
133
193
  console.error(chalk.red('\n❌ Erro ao parar container:'));
134
- console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
194
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
135
195
  process.exit(1);
136
196
  }
137
197
  }
138
198
 
139
- async function restart(containerId) {
199
+ // ============================================
200
+ // RESTART - Reiniciar container
201
+ // ============================================
202
+ async function restart(identifier) {
140
203
  try {
141
- const spinner = ora('Reiniciando container...').start();
204
+ const spinner = ora('Resolvendo container...').start();
205
+ const container = await resolveContainer(identifier);
142
206
 
143
- const response = await api.restartContainer(containerId);
144
-
145
- spinner.succeed(chalk.green(`βœ… Container ${response.container.name} reiniciado!`));
207
+ spinner.text = `Reiniciando ${container.name}...`;
208
+ await api.restartContainer(container.id);
209
+
210
+ spinner.succeed(chalk.green(`βœ… Container ${container.name} reiniciado!`));
146
211
 
147
212
  } catch (error) {
148
213
  console.error(chalk.red('\n❌ Erro ao reiniciar container:'));
149
- console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
214
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
150
215
  process.exit(1);
151
216
  }
152
217
  }
153
218
 
154
- async function deleteContainer(containerId) {
219
+ // ============================================
220
+ // DELETE - Deletar container
221
+ // ============================================
222
+ async function deleteContainer(identifier) {
155
223
  try {
224
+ const spinner = ora('Resolvendo container...').start();
225
+ const container = await resolveContainer(identifier);
226
+ spinner.stop();
227
+
156
228
  const confirm = await inquirer.prompt([
157
229
  {
158
230
  type: 'confirm',
159
231
  name: 'confirmed',
160
- message: chalk.red('Tem certeza que deseja deletar este container?'),
232
+ message: chalk.red(`Tem certeza que deseja deletar "${container.name}"?`),
161
233
  default: false
162
234
  }
163
235
  ]);
@@ -167,70 +239,80 @@ async function deleteContainer(containerId) {
167
239
  return;
168
240
  }
169
241
 
170
- const spinner = ora('Deletando container...').start();
171
-
172
- await api.deleteContainer(containerId);
173
-
174
- spinner.succeed(chalk.green('βœ… Container deletado com sucesso!'));
242
+ const deleteSpinner = ora(`Deletando ${container.name}...`).start();
243
+ await api.deleteContainer(container.id);
244
+
245
+ deleteSpinner.succeed(chalk.green(`βœ… Container ${container.name} deletado com sucesso!`));
175
246
 
176
247
  } catch (error) {
177
248
  console.error(chalk.red('\n❌ Erro ao deletar container:'));
178
- console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
249
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
179
250
  process.exit(1);
180
251
  }
181
252
  }
182
253
 
183
- async function logs(containerId, options) {
254
+ // ============================================
255
+ // LOGS - Ver logs do container
256
+ // ============================================
257
+ async function logs(identifier, options) {
184
258
  try {
185
- const spinner = ora('Carregando logs...').start();
186
-
187
- const response = await api.getContainerLogs(containerId, options.lines);
259
+ const spinner = ora('Resolvendo container...').start();
260
+ const container = await resolveContainer(identifier);
188
261
 
262
+ spinner.text = `Carregando logs de ${container.name}...`;
263
+ const response = await api.getContainerLogs(container.id, options.lines);
264
+
189
265
  spinner.stop();
190
266
 
191
267
  if (!response.logs || response.logs.length === 0) {
192
- console.log(chalk.yellow('\n⚠️ Nenhum log encontrado'));
268
+ console.log(chalk.yellow(`\n⚠️ Nenhum log encontrado para ${container.name}`));
193
269
  return;
194
270
  }
195
271
 
196
- console.log(chalk.cyan.bold('\nπŸ“ Logs do Container\n'));
272
+ console.log(chalk.cyan.bold(`\nπŸ“ Logs de ${container.name}\n`));
197
273
  response.logs.forEach(line => {
198
274
  console.log(chalk.gray(line));
199
275
  });
200
276
 
201
277
  } catch (error) {
202
278
  console.error(chalk.red('\n❌ Erro ao buscar logs:'));
203
- console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
279
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
204
280
  process.exit(1);
205
281
  }
206
282
  }
207
283
 
208
- async function info(containerId) {
284
+ // ============================================
285
+ // INFO - InformaΓ§Γ΅es detalhadas do container
286
+ // ============================================
287
+ async function info(identifier) {
209
288
  try {
210
- const spinner = ora('Carregando informaΓ§Γ΅es...').start();
289
+ const spinner = ora('Resolvendo container...').start();
290
+ const container = await resolveContainer(identifier);
211
291
 
212
- const response = await api.getContainer(containerId);
292
+ spinner.text = `Carregando informaΓ§Γ΅es de ${container.name}...`;
213
293
 
294
+ // Busca detalhes completos (com stats se disponΓ­vel)
295
+ const response = await api.getContainer(container.id);
214
296
  spinner.stop();
215
297
 
216
- const container = response.container;
217
-
298
+ const containerData = response.container;
299
+
218
300
  console.log(chalk.cyan.bold('\nπŸ“¦ InformaΓ§Γ΅es do Container\n'));
219
- console.log(chalk.white(` ID: ${container.id}`));
220
- console.log(chalk.white(` Nome: ${container.name}`));
221
- console.log(chalk.white(` Tipo: ${container.type}`));
222
-
223
- const statusColor = container.status === 'running' ? chalk.green : chalk.gray;
224
- console.log(` Status: ${statusColor(container.status)}`);
225
-
226
- console.log(chalk.cyan(` URL: https://${container.domain}`));
227
- console.log(chalk.white(` Porta: ${container.port}`));
228
- console.log(chalk.white(` CPU Limit: ${container.cpu_limit}`));
229
- console.log(chalk.white(` RAM Limit: ${container.memory_limit_mb}MB`));
230
- console.log(chalk.white(` Storage Usado: ${container.storage_used_mb || 0}MB`));
231
- console.log(chalk.white(` Auto Restart: ${container.auto_restart ? 'Sim' : 'NΓ£o'}`));
232
- console.log(chalk.gray(` Criado em: ${new Date(container.created_at).toLocaleString('pt-BR')}`));
233
- console.log(chalk.gray(` Atualizado em: ${new Date(container.updated_at).toLocaleString('pt-BR')}`));
301
+ console.log(chalk.white(` ID: ${containerData.id}`));
302
+ console.log(chalk.white(` Nome: ${containerData.name}`));
303
+ console.log(chalk.white(` Tipo: ${containerData.type}`));
304
+
305
+ const statusColor = containerData.status === 'running' ? chalk.green : chalk.gray;
306
+ console.log(` Status: ${statusColor(containerData.status)}`);
307
+
308
+ console.log(chalk.cyan(` URL: https://${containerData.domain}`));
309
+ console.log(chalk.white(` Porta: ${containerData.port}`));
310
+ console.log(chalk.white(` CPU Limit: ${containerData.cpu_limit}`));
311
+ console.log(chalk.white(` RAM Limit: ${containerData.memory_limit_mb}MB`));
312
+ console.log(chalk.white(` Storage Usado: ${containerData.storage_used_mb || 0}MB`));
313
+ console.log(chalk.white(` Auto Restart: ${containerData.auto_restart ? 'Sim' : 'NΓ£o'}`));
314
+ console.log(chalk.gray(` Criado em: ${new Date(containerData.created_at).toLocaleString('pt-BR')}`));
315
+ console.log(chalk.gray(` Atualizado em: ${new Date(containerData.updated_at).toLocaleString('pt-BR')}`));
234
316
 
235
317
  if (response.stats) {
236
318
  console.log(chalk.cyan.bold('\nπŸ“Š EstatΓ­sticas em Tempo Real\n'));
@@ -240,22 +322,28 @@ async function info(containerId) {
240
322
 
241
323
  } catch (error) {
242
324
  console.error(chalk.red('\n❌ Erro ao buscar informaΓ§Γ΅es:'));
243
- console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
325
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
244
326
  process.exit(1);
245
327
  }
246
328
  }
247
329
 
248
- async function url(containerId) {
330
+ // ============================================
331
+ // URL - Obter URL do container
332
+ // ============================================
333
+ async function url(identifier) {
249
334
  try {
250
- const response = await api.getContainer(containerId);
251
- console.log(chalk.cyan(`https://${response.container.domain}`));
335
+ const container = await resolveContainer(identifier);
336
+ console.log(chalk.cyan(`https://${container.domain}`));
252
337
  } catch (error) {
253
338
  console.error(chalk.red('❌ Erro ao buscar URL:'));
254
- console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
339
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
255
340
  process.exit(1);
256
341
  }
257
342
  }
258
343
 
344
+ // ============================================
345
+ // EXPORTS
346
+ // ============================================
259
347
  module.exports = {
260
348
  list,
261
349
  create,
@@ -0,0 +1,389 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const inquirer = require('inquirer');
4
+ const api = require('../utils/api');
5
+
6
+ // ============================================
7
+ // HELPER FUNCTION - Resolver Nome ou ID
8
+ // ============================================
9
+ async function resolveDatabase(identifier) {
10
+ try {
11
+ // Tenta buscar diretamente por ID
12
+ const response = await api.getDatabase(identifier);
13
+ return response.database;
14
+ } catch (error) {
15
+ // Se nΓ£o encontrar (404), busca na lista por nome
16
+ if (error.response?.status === 404 || error.response?.data?.error?.includes('not found')) {
17
+ const listResponse = await api.listDatabases();
18
+
19
+ // Procura por nome, ID completo ou ID parcial
20
+ const found = listResponse.databases.find(d =>
21
+ d.name === identifier ||
22
+ d.id === identifier ||
23
+ d.id.startsWith(identifier)
24
+ );
25
+
26
+ if (!found) {
27
+ // Procura databases similares para sugerir
28
+ const similar = listResponse.databases.filter(d =>
29
+ d.name.toLowerCase().includes(identifier.toLowerCase()) ||
30
+ d.id.includes(identifier)
31
+ );
32
+
33
+ if (similar.length > 0) {
34
+ console.log(chalk.yellow('\nπŸ’‘ VocΓͺ quis dizer:'));
35
+ similar.forEach(d => {
36
+ console.log(chalk.white(` β€’ ${d.name} ${chalk.gray(`(${d.id.slice(0, 8)}) - ${d.type}`)}`));
37
+ });
38
+ } else {
39
+ console.log(chalk.gray('\nπŸ’‘ Use: mozhost db:list para ver todos os databases'));
40
+ }
41
+
42
+ throw new Error(`Database "${identifier}" nΓ£o encontrado`);
43
+ }
44
+
45
+ return found;
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ // ============================================
52
+ // LIST - Listar todos os databases
53
+ // ============================================
54
+ async function list() {
55
+ try {
56
+ const spinner = ora('Carregando databases...').start();
57
+
58
+ const response = await api.listDatabases();
59
+
60
+ spinner.stop();
61
+
62
+ if (response.databases.length === 0) {
63
+ console.log(chalk.yellow('\n⚠️ Nenhum database encontrado'));
64
+ console.log(chalk.gray(' Use: mozhost db:create --name <nome> --type <tipo>'));
65
+ return;
66
+ }
67
+
68
+ console.log(chalk.cyan.bold(`\nπŸ’Ύ Databases (${response.databases.length})\n`));
69
+
70
+ response.databases.forEach(database => {
71
+ const statusColor = database.status === 'running' ? chalk.green : chalk.gray;
72
+ const statusIcon = database.status === 'running' ? '●' : 'β—‹';
73
+
74
+ // Ícones por tipo de database
75
+ const typeIcons = {
76
+ mysql: '🐬',
77
+ mariadb: '🦭',
78
+ postgres: '🐘',
79
+ mongodb: 'πŸƒ',
80
+ redis: 'πŸ”΄'
81
+ };
82
+ const typeIcon = typeIcons[database.type] || 'πŸ’Ύ';
83
+
84
+ console.log(`${statusColor(statusIcon)} ${typeIcon} ${chalk.white.bold(database.name)} ${chalk.gray(`(${database.id.slice(0, 8)})`)}`);
85
+ console.log(` ${chalk.gray('Tipo:')} ${database.type}`);
86
+ console.log(` ${chalk.gray('Status:')} ${statusColor(database.status)}`);
87
+ console.log(` ${chalk.gray('Host:')} ${chalk.cyan(database.host)}`);
88
+ console.log(` ${chalk.gray('Porta:')} ${database.port}`);
89
+ console.log(` ${chalk.gray('Database:')} ${database.database_name}`);
90
+
91
+ if (database.containers && database.containers.length > 0) {
92
+ console.log(` ${chalk.gray('Containers:')} ${database.containers.join(', ')}`);
93
+ }
94
+ console.log();
95
+ });
96
+
97
+ if (response.total_cost !== undefined) {
98
+ console.log(chalk.yellow(`πŸ’° Custo total: ${response.total_cost} coins/dia\n`));
99
+ }
100
+
101
+ } catch (error) {
102
+ console.error(chalk.red('\n❌ Erro ao listar databases:'));
103
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ // ============================================
109
+ // CREATE - Criar novo database
110
+ // ============================================
111
+ async function create(options) {
112
+ try {
113
+ const { name, type, container } = options;
114
+
115
+ if (!name || !type) {
116
+ console.error(chalk.red('❌ Nome e tipo são obrigatórios'));
117
+ console.log(chalk.gray(' Use: mozhost db:create --name <nome> --type <tipo>'));
118
+ process.exit(1);
119
+ }
120
+
121
+ const validTypes = ['mysql', 'mariadb', 'postgres', 'mongodb', 'redis'];
122
+ if (!validTypes.includes(type)) {
123
+ console.error(chalk.red('❌ Tipo invÑlido'));
124
+ console.log(chalk.gray(' Tipos vΓ‘lidos: mysql, mariadb, postgres, mongodb, redis'));
125
+ process.exit(1);
126
+ }
127
+
128
+ console.log(chalk.cyan.bold('\nπŸ”¨ Criando database...\n'));
129
+ console.log(chalk.gray(` Nome: ${name}`));
130
+ console.log(chalk.gray(` Tipo: ${type}`));
131
+ if (container) {
132
+ console.log(chalk.gray(` Container: ${container}`));
133
+ }
134
+ console.log();
135
+
136
+ const spinner = ora('Criando database...').start();
137
+
138
+ const response = await api.createDatabase({ name, type, container });
139
+
140
+ spinner.succeed(chalk.green('βœ… Database criado com sucesso!'));
141
+
142
+ const db = response.database;
143
+
144
+ console.log(chalk.gray('\nπŸ“‹ InformaΓ§Γ΅es do Database:'));
145
+ console.log(chalk.white(` ID: ${db.id}`));
146
+ console.log(chalk.white(` Nome: ${db.name}`));
147
+ console.log(chalk.white(` Tipo: ${db.type}`));
148
+ console.log(chalk.white(` Status: ${db.status}`));
149
+ console.log(chalk.cyan(` Host: ${db.host}`));
150
+ console.log(chalk.white(` Porta: ${db.port}`));
151
+ console.log(chalk.white(` Database: ${db.database_name}`));
152
+ console.log(chalk.white(` Username: ${db.username}`));
153
+ console.log(chalk.yellow(` Password: ${db.password}`));
154
+ console.log(chalk.yellow(` πŸ’° Custo: ${db.cost} coins/dia`));
155
+
156
+ console.log(chalk.gray('\nπŸ”— Connection String:'));
157
+ console.log(chalk.cyan(` ${db.connection_string}`));
158
+
159
+ console.log(chalk.gray('\nπŸ’‘ Dicas:'));
160
+ console.log(chalk.white(` β€’ Guarde a senha em local seguro`));
161
+ console.log(chalk.white(` β€’ Use: mozhost db:credentials ${db.name} para ver novamente`));
162
+ if (!container) {
163
+ console.log(chalk.white(` β€’ Use: mozhost db:link ${db.name} <container> para vincular`));
164
+ }
165
+
166
+ } catch (error) {
167
+ console.error(chalk.red('\n❌ Erro ao criar database:'));
168
+
169
+ if (error.response?.data) {
170
+ console.error(chalk.red(` ${error.response.data.error || error.response.data.message}`));
171
+
172
+ if (error.response.status === 400 && error.response.data.error?.includes('Insufficient coins')) {
173
+ console.log(chalk.yellow('\nπŸ’‘ Dica: VocΓͺ nΓ£o tem coins suficientes (necessΓ‘rio: 5 coins)'));
174
+ }
175
+ } else {
176
+ console.error(chalk.red(` ${error.message}`));
177
+ }
178
+
179
+ process.exit(1);
180
+ }
181
+ }
182
+
183
+ // ============================================
184
+ // INFO - InformaΓ§Γ΅es detalhadas do database
185
+ // ============================================
186
+ async function info(identifier) {
187
+ try {
188
+ const spinner = ora('Resolvendo database...').start();
189
+ const database = await resolveDatabase(identifier);
190
+
191
+ spinner.text = `Carregando informaΓ§Γ΅es de ${database.name}...`;
192
+
193
+ // Busca detalhes completos
194
+ const response = await api.getDatabase(database.id);
195
+ spinner.stop();
196
+
197
+ const db = response.database;
198
+
199
+ // Ícones por tipo
200
+ const typeIcons = {
201
+ mysql: '🐬',
202
+ mariadb: '🦭',
203
+ postgres: '🐘',
204
+ mongodb: 'πŸƒ',
205
+ redis: 'πŸ”΄'
206
+ };
207
+ const typeIcon = typeIcons[db.type] || 'πŸ’Ύ';
208
+
209
+ console.log(chalk.cyan.bold(`\n${typeIcon} InformaΓ§Γ΅es do Database\n`));
210
+ console.log(chalk.white(` ID: ${db.id}`));
211
+ console.log(chalk.white(` Nome: ${db.name}`));
212
+ console.log(chalk.white(` Tipo: ${db.type}`));
213
+
214
+ const statusColor = db.status === 'running' ? chalk.green : chalk.gray;
215
+ console.log(` Status: ${statusColor(db.status)}`);
216
+
217
+ console.log(chalk.cyan(` Host: ${db.host}`));
218
+ console.log(chalk.white(` Porta: ${db.port}`));
219
+ console.log(chalk.white(` Database: ${db.database_name}`));
220
+ console.log(chalk.white(` Username: ${db.username}`));
221
+ console.log(chalk.yellow(` Password: ${db.password}`));
222
+ console.log(chalk.yellow(` πŸ’° Custo: ${db.cost} coins/dia`));
223
+
224
+ if (db.containers && db.containers.length > 0) {
225
+ console.log(chalk.gray('\nπŸ“¦ Containers vinculados:'));
226
+ db.containers.forEach(c => {
227
+ console.log(chalk.white(` β€’ ${c.name} ${chalk.gray(`(${c.id})`)}`));
228
+ });
229
+ }
230
+
231
+ console.log(chalk.gray(`\nπŸ“… Criado em: ${new Date(db.created_at).toLocaleString('pt-BR')}`));
232
+ console.log(chalk.gray(` Atualizado em: ${new Date(db.updated_at).toLocaleString('pt-BR')}`));
233
+
234
+ console.log(chalk.gray('\nπŸ”— Connection String:'));
235
+ console.log(chalk.cyan(` ${db.connection_string}`));
236
+
237
+ } catch (error) {
238
+ console.error(chalk.red('\n❌ Erro ao buscar informaçáes:'));
239
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
240
+ process.exit(1);
241
+ }
242
+ }
243
+
244
+ // ============================================
245
+ // CREDENTIALS - Ver credenciais do database
246
+ // ============================================
247
+ async function credentials(identifier) {
248
+ try {
249
+ const spinner = ora('Carregando credenciais...').start();
250
+ const database = await resolveDatabase(identifier);
251
+
252
+ const response = await api.getDatabase(database.id);
253
+ spinner.stop();
254
+
255
+ const db = response.database;
256
+
257
+ console.log(chalk.cyan.bold(`\nπŸ”‘ Credenciais de ${db.name}\n`));
258
+ console.log(chalk.white(` Host: ${chalk.cyan(db.host)}`));
259
+ console.log(chalk.white(` Porta: ${chalk.cyan(db.port)}`));
260
+ console.log(chalk.white(` Database: ${chalk.cyan(db.database_name)}`));
261
+ console.log(chalk.white(` Username: ${chalk.cyan(db.username)}`));
262
+ console.log(chalk.white(` Password: ${chalk.yellow(db.password)}`));
263
+
264
+ console.log(chalk.gray('\nπŸ”— Connection String:'));
265
+ console.log(chalk.cyan(` ${db.connection_string}`));
266
+
267
+ console.log(chalk.gray('\nπŸ’‘ Copie e cole em suas variΓ‘veis de ambiente'));
268
+
269
+ } catch (error) {
270
+ console.error(chalk.red('\n❌ Erro ao buscar credenciais:'));
271
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
272
+ process.exit(1);
273
+ }
274
+ }
275
+
276
+ // ============================================
277
+ // LINK - Vincular database a container
278
+ // ============================================
279
+ async function link(databaseIdentifier, containerIdentifier) {
280
+ try {
281
+ if (!containerIdentifier) {
282
+ console.error(chalk.red('❌ Container não especificado'));
283
+ console.log(chalk.gray(' Use: mozhost db:link <database> <container>'));
284
+ process.exit(1);
285
+ }
286
+
287
+ const spinner = ora('Resolvendo database...').start();
288
+ const database = await resolveDatabase(databaseIdentifier);
289
+
290
+ spinner.text = 'Resolvendo container...';
291
+ // Reusar a funΓ§Γ£o de containers (vocΓͺ precisarΓ‘ exportΓ‘-la ou criar similar)
292
+ let containerId;
293
+ try {
294
+ const containerResponse = await api.getContainer(containerIdentifier);
295
+ containerId = containerResponse.container.id;
296
+ } catch (error) {
297
+ if (error.response?.status === 404) {
298
+ const listResponse = await api.listContainers();
299
+ const found = listResponse.containers.find(c =>
300
+ c.name === containerIdentifier ||
301
+ c.id === containerIdentifier ||
302
+ c.id.startsWith(containerIdentifier)
303
+ );
304
+ if (!found) {
305
+ throw new Error(`Container "${containerIdentifier}" nΓ£o encontrado`);
306
+ }
307
+ containerId = found.id;
308
+ } else {
309
+ throw error;
310
+ }
311
+ }
312
+
313
+ spinner.text = 'Vinculando database ao container...';
314
+ await api.linkDatabase(database.id, containerId);
315
+
316
+ spinner.succeed(chalk.green(`βœ… Database ${database.name} vinculado com sucesso!`));
317
+
318
+ console.log(chalk.gray('\nπŸ’‘ O container agora pode acessar este database'));
319
+
320
+ } catch (error) {
321
+ console.error(chalk.red('\n❌ Erro ao vincular database:'));
322
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
323
+ process.exit(1);
324
+ }
325
+ }
326
+
327
+ // ============================================
328
+ // DELETE - Deletar database
329
+ // ============================================
330
+ async function deleteDatabase(identifier) {
331
+ try {
332
+ const spinner = ora('Resolvendo database...').start();
333
+ const database = await resolveDatabase(identifier);
334
+ spinner.stop();
335
+
336
+ console.log(chalk.yellow('\n⚠️ ATENÇÃO: Esta aΓ§Γ£o Γ© irreversΓ­vel!'));
337
+ console.log(chalk.gray(' Todos os dados serΓ£o perdidos permanentemente.\n'));
338
+
339
+ const confirm = await inquirer.prompt([
340
+ {
341
+ type: 'confirm',
342
+ name: 'confirmed',
343
+ message: chalk.red(`Tem certeza que deseja deletar "${database.name}"?`),
344
+ default: false
345
+ }
346
+ ]);
347
+
348
+ if (!confirm.confirmed) {
349
+ console.log(chalk.yellow('OperaΓ§Γ£o cancelada'));
350
+ return;
351
+ }
352
+
353
+ // ConfirmaΓ§Γ£o dupla para databases
354
+ const doubleConfirm = await inquirer.prompt([
355
+ {
356
+ type: 'input',
357
+ name: 'typed',
358
+ message: chalk.red(`Digite o nome do database "${database.name}" para confirmar:`),
359
+ }
360
+ ]);
361
+
362
+ if (doubleConfirm.typed !== database.name) {
363
+ console.log(chalk.red('❌ Nome incorreto. Operação cancelada.'));
364
+ return;
365
+ }
366
+
367
+ const deleteSpinner = ora(`Deletando ${database.name}...`).start();
368
+ await api.deleteDatabase(database.id);
369
+
370
+ deleteSpinner.succeed(chalk.green(`βœ… Database ${database.name} deletado com sucesso!`));
371
+
372
+ } catch (error) {
373
+ console.error(chalk.red('\n❌ Erro ao deletar database:'));
374
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
375
+ process.exit(1);
376
+ }
377
+ }
378
+
379
+ // ============================================
380
+ // EXPORTS
381
+ // ============================================
382
+ module.exports = {
383
+ list,
384
+ create,
385
+ info,
386
+ credentials,
387
+ link,
388
+ deleteDatabase
389
+ };
@@ -0,0 +1,78 @@
1
+ // mozhost-cli/commands/domain.js
2
+
3
+ const program = require('commander');
4
+ const axios = require('axios');
5
+ const chalk = require('chalk');
6
+
7
+ program
8
+ .command('domain add <container> <domain>')
9
+ .description('Adicionar domΓ­nio customizado')
10
+ .action(async (container, domain) => {
11
+ try {
12
+ const res = await axios.post(`${API_URL}/domains`, {
13
+ containerId: container,
14
+ domain
15
+ });
16
+
17
+ console.log(chalk.green('\nβœ… DomΓ­nio adicionado com sucesso!\n'));
18
+ console.log(chalk.yellow('πŸ“‹ Configure seu DNS:\n'));
19
+ console.log(` Tipo: ${chalk.bold('A')}`);
20
+ console.log(` Nome: ${chalk.bold('@')} (ou ${chalk.bold(domain)})`);
21
+ console.log(` Valor: ${chalk.bold(res.data.instructions.ip)}`);
22
+ console.log(` TTL: ${chalk.bold('300')}\n`);
23
+ console.log(chalk.cyan('⏳ Aguardando propagação DNS...\n'));
24
+ console.log(`Use ${chalk.bold('mozhost domain status ' + container)} para acompanhar\n`);
25
+
26
+ } catch (error) {
27
+ console.error(chalk.red('❌ Erro:'), error.response?.data?.error || error.message);
28
+ }
29
+ });
30
+
31
+ program
32
+ .command('domain status <container>')
33
+ .description('Ver status dos domΓ­nios')
34
+ .action(async (container) => {
35
+ try {
36
+ const res = await axios.get(`${API_URL}/domains/${container}`);
37
+ const domains = res.data;
38
+
39
+ if (domains.length === 0) {
40
+ console.log(chalk.yellow('Nenhum domΓ­nio customizado configurado'));
41
+ return;
42
+ }
43
+
44
+ console.log('\nβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”');
45
+
46
+ for (const d of domains) {
47
+ const statusIcon = {
48
+ pending: '⏳',
49
+ dns_pending: 'πŸ”',
50
+ ssl_generating: 'πŸ”’',
51
+ active: 'βœ…',
52
+ failed: '❌'
53
+ }[d.status];
54
+
55
+ const statusText = {
56
+ pending: 'Aguardando configuraΓ§Γ£o',
57
+ dns_pending: 'DNS detectado, gerando SSL...',
58
+ ssl_generating: 'Gerando certificado SSL...',
59
+ active: 'Ativo',
60
+ failed: 'Falhou - verifique DNS'
61
+ }[d.status];
62
+
63
+ console.log(`β”‚ ${statusIcon} ${chalk.bold(d.domain)}`);
64
+ console.log(`β”‚ Status: ${statusText}`);
65
+ if (d.ssl_expires_at && d.status === 'active') {
66
+ console.log(`β”‚ SSL vΓ‘lido atΓ©: ${new Date(d.ssl_expires_at).toLocaleDateString()}`);
67
+ }
68
+ console.log('β”‚');
69
+ }
70
+
71
+ console.log('β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜\n');
72
+
73
+ } catch (error) {
74
+ console.error(chalk.red('❌ Erro:'), error.message);
75
+ }
76
+ });
77
+
78
+ program.parse(process.argv);
package/src/utils/api.js CHANGED
@@ -170,4 +170,37 @@ class ApiClient {
170
170
  }
171
171
  }
172
172
 
173
+ // Databases
174
+ async listDatabases() {
175
+ const client = await this.getClient();
176
+ const response = await client.get('/api/databases');
177
+ return response.data;
178
+ }
179
+
180
+ async getDatabase(databaseId) {
181
+ const client = await this.getClient();
182
+ const response = await client.get(`/api/databases/${databaseId}`);
183
+ return response.data;
184
+ }
185
+
186
+ async createDatabase(data) {
187
+ const client = await this.getClient();
188
+ const response = await client.post('/api/databases', data);
189
+ return response.data;
190
+ }
191
+
192
+ async deleteDatabase(databaseId) {
193
+ const client = await this.getClient();
194
+ const response = await client.delete(`/api/databases/${databaseId}`);
195
+ return response.data;
196
+ }
197
+
198
+ async linkDatabase(databaseId, containerId) {
199
+ const client = await this.getClient();
200
+ const response = await client.post(`/api/databases/${databaseId}/link`, {
201
+ container_id: containerId
202
+ });
203
+ return response.data;
204
+ }
205
+
173
206
  module.exports = new ApiClient();