Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ BACKUP_DIR=
BACKUP_KEEP_DAYS=
MYSQLDUMP_BIN=

# e-mail transacional (recuperacao de senha, confirmacao de e-mail). Vazio = nenhum e-mail sai e o log avisa
# (o provedor de producao ainda nao foi escolhido). Em desenvolvimento use MAIL_DRIVER=log: cada e-mail vira uma
# linha json em MAIL_LOG_FILE (vazio = storage/mail.log), com os links. Nunca use "log" em producao.
MAIL_DRIVER=
MAIL_LOG_FILE=

# so para o docker compose: senha do root do MySQL do container e porta publicada no host (vazio = 8080).
# No compose, DB_PASS acima e a senha do usuario "fidelityx" do banco e nao pode ficar vazia.
DB_ROOT_PASS=
Expand Down
22 changes: 18 additions & 4 deletions CLAUDE.md

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions bin/create-admin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
// cria um administrador do FidelityX (task 30), que entra em /index.php?url=admin/login.
//
// php bin/create-admin.php email@dominio.com "Nome"
//
// a senha e pedida no terminal (ou lida da entrada: echo "senha" | php bin/create-admin.php ...),
// nunca vai como argumento (ficaria no historico do shell).
// usa o banco do .env / variaveis de ambiente (DB_NAME).

require __DIR__ . '/../vendor/autoload.php';

use App\Database;
use App\Models\AdminModel;
use App\Support\Env;

if (PHP_SAPI !== 'cli') {
exit(1);
}

Env::load(dirname(__DIR__));
date_default_timezone_set(app_timezone());

$email = trim((string)($argv[1] ?? ''));
$name = trim((string)($argv[2] ?? ''));
if ($email === '' || $name === '') {
fwrite(STDERR, "uso: php bin/create-admin.php email@dominio.com \"Nome\"\n");
exit(1);
}

fwrite(STDOUT, 'senha: ');
$password = rtrim((string)fgets(STDIN), "\r\n");

$problem = AdminModel::problem($email, $name, $password);
if ($problem !== null) {
fwrite(STDERR, "erro: $problem\n");
exit(1);
}

try {
if (Env::missing() !== []) {
throw new RuntimeException('variaveis obrigatorias sem valor: ' . implode(', ', Env::missing()));
}
$id = (new AdminModel(Database::connect()))->create($email, $name, $password);
fwrite(STDOUT, "\nadmin criado (id $id). entre em /index.php?url=admin/login\n");
} catch (PDOException $e) {
fwrite(STDERR, (($e->errorInfo[1] ?? null) === 1062 ? 'erro: ja existe admin com esse e-mail' : 'erro no banco: ' . $e->getMessage()) . "\n");
exit(1);
} catch (Throwable $e) {
fwrite(STDERR, 'erro: ' . $e->getMessage() . "\n");
exit(1);
}
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@
}
},
"scripts": {
"test": "phpunit"
"test": "phpunit",
"analyse": "phpstan analyse --memory-limit=1G --no-progress"
},
"config": {
"platform": {
"php": "8.1.0"
}
},
"require-dev": {
"phpunit/phpunit": "^10.5"
"phpunit/phpunit": "^10.5",
"phpstan/phpstan": "^2.1"
}
}
66 changes: 65 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions database/migrations/008_password_resets.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- fidelityx - migration 008 (recuperacao de senha por e-mail, task 21)
-- para bancos criados antes desta versao do schema.sql. rodar uma unica vez:
-- php bin/migrate.php
-- (ou, a mao: mysql -u root -p fidelityx < database/migrations/008_password_resets.sql)

USE fidelityx;

-- link de redefinicao de senha: o token vai so no e-mail; aqui fica o hash sha-256 dele
-- (quem ler o banco nao consegue usar). vale 1 hora e uma vez so.
CREATE TABLE IF NOT EXISTS password_resets (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

merchant_id BIGINT UNSIGNED NOT NULL,
token_hash CHAR(64) NOT NULL,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP NULL, -- usado ou cancelado (pedido novo ou senha trocada)

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

UNIQUE KEY uq_password_resets_token (token_hash),
KEY idx_password_resets_merchant (merchant_id),

CONSTRAINT fk_password_resets_merchant
FOREIGN KEY (merchant_id) REFERENCES merchants(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
32 changes: 32 additions & 0 deletions database/migrations/009_email_verifications.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- fidelityx - migration 009 (confirmacao do e-mail do lojista, task 50)
-- para bancos criados antes desta versao do schema.sql. rodar uma unica vez:
-- php bin/migrate.php
-- (ou, a mao: mysql -u root -p fidelityx < database/migrations/009_email_verifications.sql)

USE fidelityx;

-- conta nova so usa o painel depois de confirmar o e-mail (NULL = ainda nao confirmou)
ALTER TABLE merchants ADD COLUMN email_verified_at TIMESTAMP NULL AFTER email;

-- contas que ja existiam contam como confirmadas: nao da pra travar quem ja usa o sistema
UPDATE merchants SET email_verified_at = created_at WHERE email_verified_at IS NULL;

-- link de confirmacao: como o de nova senha, so o hash do token fica aqui. vale 24 horas e uma vez.
CREATE TABLE IF NOT EXISTS email_verifications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

merchant_id BIGINT UNSIGNED NOT NULL,
token_hash CHAR(64) NOT NULL,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP NULL, -- usado ou cancelado (reenvio)

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

UNIQUE KEY uq_email_verifications_token (token_hash),
KEY idx_email_verifications_merchant (merchant_id),

CONSTRAINT fk_email_verifications_merchant
FOREIGN KEY (merchant_id) REFERENCES merchants(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
21 changes: 21 additions & 0 deletions database/migrations/010_admins.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- fidelityx - migration 010 (painel administrativo, task 30)
-- para bancos criados antes desta versao do schema.sql. rodar uma unica vez:
-- php bin/migrate.php
-- (ou, a mao: mysql -u root -p fidelityx < database/migrations/010_admins.sql)

USE fidelityx;

-- administradores do FidelityX (nao sao lojistas): login proprio em admin/login, conta criada
-- pela linha de comando (php bin/create-admin.php). veem as lojas, nunca os clientes delas.
CREATE TABLE IF NOT EXISTS admins (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

email VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

UNIQUE KEY uq_admins_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
69 changes: 68 additions & 1 deletion database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ CREATE TABLE IF NOT EXISTS merchants (
owner_name VARCHAR(255) NOT NULL,
store_name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
email_verified_at TIMESTAMP NULL, -- conta nova so usa o painel depois de confirmar o e-mail (task 50)
phone VARCHAR(30) NOT NULL,
category VARCHAR(30) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
Expand Down Expand Up @@ -182,6 +183,69 @@ CREATE TABLE IF NOT EXISTS rate_limit_hits (
KEY idx_rate_limit_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- =========================
-- password_resets // links de redefinicao de senha (task 21)
-- =========================
-- o token vai so no e-mail; aqui fica o hash sha-256 dele. vale 1 hora e uma vez so.
CREATE TABLE IF NOT EXISTS password_resets (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

merchant_id BIGINT UNSIGNED NOT NULL,
token_hash CHAR(64) NOT NULL,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP NULL, -- usado ou cancelado (pedido novo ou senha trocada)

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

UNIQUE KEY uq_password_resets_token (token_hash),
KEY idx_password_resets_merchant (merchant_id),

CONSTRAINT fk_password_resets_merchant
FOREIGN KEY (merchant_id) REFERENCES merchants(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- =========================
-- email_verifications // links de confirmacao do e-mail do lojista (task 50)
-- =========================
-- como o de nova senha: so o hash do token fica aqui. vale 24 horas e uma vez.
CREATE TABLE IF NOT EXISTS email_verifications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

merchant_id BIGINT UNSIGNED NOT NULL,
token_hash CHAR(64) NOT NULL,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP NULL, -- usado ou cancelado (reenvio)

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

UNIQUE KEY uq_email_verifications_token (token_hash),
KEY idx_email_verifications_merchant (merchant_id),

CONSTRAINT fk_email_verifications_merchant
FOREIGN KEY (merchant_id) REFERENCES merchants(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- =========================
-- admins // administradores do FidelityX (task 30)
-- =========================
-- login proprio em admin/login, conta criada por php bin/create-admin.php. veem as lojas, nunca os clientes.
CREATE TABLE IF NOT EXISTS admins (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

email VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

UNIQUE KEY uq_admins_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- =========================
-- schema_migrations // quais migrations de database/migrations ja rodaram neste banco
-- =========================
Expand All @@ -200,4 +264,7 @@ INSERT IGNORE INTO schema_migrations (version) VALUES
('004_estorno'),
('005_regra_pontos'),
('006_points_log_extrato'),
('007_customers_so_telefone');
('007_customers_so_telefone'),
('008_password_resets'),
('009_email_verifications'),
('010_admins');
Loading
Loading