Skip to content
Closed
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
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
APP_ENV=local
APP_DEBUG=true
LOG_CHANNEL=single

OPENAI_API_KEY=MOCK_MODE
OPENAI_MODEL=gpt-4o-mini

MAIL_TRANSPORT=file
MAIL_FROM_ADDRESS=noreply@example.test
MAIL_FROM_NAME="AI Reply Bot"

DB_CONNECTION=none
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: CI

on:
push:
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- run: cp .env.example .env
- run: composer install --no-interaction --prefer-dist
- run: composer test
27 changes: 27 additions & 0 deletions .github/workflows/debug-artifact.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: debug-artifact

on:
push:
pull_request:

jobs:
debug:
runs-on: ubuntu-latest
env:
OPENAI_API_KEY: MOCK_MODE
MAIL_TRANSPORT: file
DB_CONNECTION: none
steps:
- uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- run: cp .env.example .env
- run: composer install --no-interaction --no-progress
- run: composer test || true
- run: php scripts/package_debug.php
- uses: actions/upload-artifact@v4
with:
name: debug-${{ github.run_number }}
path: artifacts/debug-*.zip
15 changes: 11 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
# Ignore vendor and environment files
/vendor/
# Ignore environment files
/.env
/.DS_Store

# Ignore logs and cache
logs/
# Ignore logs and artifacts
storage/logs/*
!storage/logs/.gitkeep
storage/mail/*
!storage/mail/.gitkeep
artifacts/*
!artifacts/.gitkeep
*.log
*.eml
*.error_log
/vendor/

# Ignore test output
/phpunit.xml
Expand Down
2 changes: 2 additions & 0 deletions admin/.htaccess
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

# Placeholder: you can add BasicAuth here later
6 changes: 6 additions & 0 deletions admin/assets/css/admin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

table{width:100%;border-collapse:collapse}
th,td{padding:8px 6px;border:1px solid #ddd;font-size:14px}
th{background:#f1f5f9}
body{font-family:Arial,Helvetica,sans-serif;margin:0;padding:16px;background:#f7f8fa}
h1{font-size:22px}
Empty file added admin/assets/js/admin.js
Empty file.
10 changes: 10 additions & 0 deletions admin/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

<?php
require __DIR__.'/../bootstrap.php';
use App\Repository\SubmissionRepository;

$db = $GLOBALS['container']['db'];
$stmt = $db->query('SELECT * FROM submissions ORDER BY id DESC LIMIT 100');
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
include __DIR__.'/views/submissions-table.php';
?>
13 changes: 13 additions & 0 deletions admin/views/layout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Admin - AI Responder</title>
<link rel="stylesheet" href="assets/css/admin.css">
</head>
<body>
<h1>Admin Dashboard</h1>
<?= $content ?>
</body>
</html>
22 changes: 22 additions & 0 deletions admin/views/submissions-table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

<?php ob_start(); ?>
<table>
<thead>
<tr>
<th>ID</th><th>Name</th><th>Email</th><th>Category</th><th>Product</th><th>Date</th>
</tr>
</thead>
<tbody>
<?php foreach($rows as $r): ?>
<tr>
<td><?= $r['id'] ?></td>
<td><?= htmlspecialchars($r['name']) ?></td>
<td><?= htmlspecialchars($r['email']) ?></td>
<td><?= htmlspecialchars($r['category']) ?></td>
<td><?= htmlspecialchars($r['product_name']) ?></td>
<td><?= $r['created_at'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php $content = ob_get_clean(); include __DIR__.'/layout.php'; ?>
Binary file removed ai-responder-admin.zip
Binary file not shown.
Binary file removed ai-responder-backend.zip
Binary file not shown.
Binary file removed ai-responder-public.zip
Binary file not shown.
41 changes: 41 additions & 0 deletions app/Core/Env.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
namespace App\Core;

use Dotenv\Dotenv;

class Env {
protected static bool $loaded = false;
protected static array $defaults = [
'APP_ENV' => 'local',
'APP_DEBUG' => 'true',
'LOG_CHANNEL' => 'single',
'OPENAI_API_KEY' => 'MOCK_MODE',
'OPENAI_MODEL' => 'gpt-4o-mini',
'MAIL_TRANSPORT' => 'file',
'MAIL_FROM_ADDRESS' => 'noreply@example.test',
'MAIL_FROM_NAME' => 'AI Reply Bot',
'DB_CONNECTION' => 'none',
];

public static function load(string $path): void
{
if (self::$loaded) {
return;
}
if (is_file($path)) {
Dotenv::createImmutable(dirname($path))->safeLoad();
}
foreach (self::$defaults as $key => $value) {
if (getenv($key) === false) {
$_ENV[$key] = $value;
putenv("{$key}={$value}");
}
}
self::$loaded = true;
}

public static function get(string $key, $default = null)
{
return $_ENV[$key] ?? getenv($key) ?? $default;
}
}
10 changes: 10 additions & 0 deletions app/Core/Request.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
namespace App\Core;

class Request {
public static function input($key, $default = null)
{
return $_POST[$key] ?? $default;
}
}
?>
10 changes: 10 additions & 0 deletions app/Core/Response.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

<?php namespace App\Core;
class Response {
public static function json($data){
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
}
?>
12 changes: 12 additions & 0 deletions app/Installer/EnvWriter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

<?php namespace App\Installer;
class EnvWriter {
public static function write(array $vars, $path){
$content = '';
foreach($vars as $k=>$v){
$content .= $k.'='.$v.PHP_EOL;
}
file_put_contents($path, $content);
}
}
?>
38 changes: 38 additions & 0 deletions app/Installer/Installer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

<?php namespace App\Installer;
use App\Support\Database;
use App\Core\Env;
use PDO;

class Installer {
public static function run(){
if($_GET['token'] ?? '' !== Env::get('INSTALL_TOKEN')){
die('Invalid token');
}
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$dbHost=$_POST['db_host']; $dbName=$_POST['db_name']; $dbUser=$_POST['db_user']; $dbPass=$_POST['db_pass'];
EnvWriter::write([
'APP_ENV' => 'production',
'DB_HOST' => $dbHost,
'DB_NAME' => $dbName,
'DB_USER' => $dbUser,
'DB_PASS' => $dbPass,
], __DIR__.'/../../.env');
echo 'Env written. Importing DB...<br>';
$pdo = new PDO("mysql:host={$dbHost}", $dbUser, $dbPass);
$pdo->exec("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;");
$pdo->exec("USE `{$dbName}`;");
$pdo->exec(SqlSchema::createTable());
echo 'Done!';
return;
}
echo '<form method="post">
<input name="db_host" placeholder="DB Host" required><br>
<input name="db_name" placeholder="DB Name" required><br>
<input name="db_user" placeholder="DB User" required><br>
<input name="db_pass" placeholder="DB Pass"><br>
<button>Install</button>
</form>';
}
}
?>
21 changes: 21 additions & 0 deletions app/Installer/SqlSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

<?php namespace App\Installer;
class SqlSchema {
public static function createTable(){
return <<<SQL
CREATE TABLE IF NOT EXISTS submissions (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(190),
email VARCHAR(190),
message TEXT,
tone VARCHAR(50),
purchase_code VARCHAR(100) UNIQUE,
product_name VARCHAR(190),
category VARCHAR(50),
ai_reply TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SQL;
}
}
?>
26 changes: 26 additions & 0 deletions app/Repository/SubmissionRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

<?php namespace App\Repository;
use PDO;

class SubmissionRepository {
protected $pdo;
public function __construct(PDO $pdo){
$this->pdo = $pdo;
}
public function save(array $data){
$stmt = $this->pdo->prepare('INSERT INTO submissions
(name,email,message,tone,purchase_code,product_name,category,ai_reply,created_at)
VALUES (?,?,?,?,?,?,?,?,NOW())');
$stmt->execute([
$data['name'],
$data['email'],
$data['message'],
$data['tone'],
$data['purchase_code'],
$data['product_name'],
$data['category'],
$data['ai_reply'],
]);
}
}
?>
14 changes: 14 additions & 0 deletions app/Repository/SubmissionRepositoryMock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
namespace App\Repository;

class SubmissionRepositoryMock {
public function __construct($pdo = null)
{
// no-op
}

public function save(array $data): void
{
// intentionally left blank in mock mode
}
}
17 changes: 17 additions & 0 deletions app/Services/LicenseValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
namespace App\Services;

use App\Core\Env;

class LicenseValidator {
public static function validate($code)
{
if (!$code) return [false, null];
// Simulated external API call
if ($code === 'INVALID') {
return [false, null];
}
return [true, 'Awesome Product'];
}
}
?>
25 changes: 25 additions & 0 deletions app/Services/OpenAIHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
namespace App\Services;

use App\Core\Env;

class OpenAIHandler {
public static function buildSmartPrompt(string $message, string $tone, string $productName): string
{
return "You are a courteous support agent for {$productName}. Keep the tone {$tone}. Reply to the user message below:\n" .
"User: {$message}\n\n" .
"Format:\n" .
"Reply: <your reply>\n" .
"Category: <Support|Sales|Spam>";
}

public static function query(string $prompt): array
{
$apiKey = Env::get('OPENAI_API_KEY');
// Real call would go here. For now, return dummy.
return [
'reply' => 'Thank you for reaching out. We will get back shortly.',
'category' => 'Support',
];
}
}
17 changes: 17 additions & 0 deletions app/Services/OpenAIHandlerMock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
namespace App\Services;

class OpenAIHandlerMock {
public static function buildSmartPrompt(string $message, string $tone, string $productName): string
{
return OpenAIHandler::buildSmartPrompt($message, $tone, $productName);
}

public static function query(string $prompt): array
{
return [
'reply' => "This is a mock AI reply.\n\nPrompt snippet: " . substr($prompt, 0, 80),
'category' => 'Mock',
];
}
}
15 changes: 15 additions & 0 deletions app/Support/Database.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

<?php namespace App\Support;
use App\Core\Env;
use PDO;

class Database {
public static function create(){
$dsn = 'mysql:host='.Env::get('DB_HOST').';dbname='.Env::get('DB_NAME').';charset=utf8mb4';
$pdo = new PDO($dsn, Env::get('DB_USER'), Env::get('DB_PASS'), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
return $pdo;
}
}
?>
Loading