diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/create-critter.php b/create-critter.php
new file mode 100644
index 0000000..f7a483d
--- /dev/null
+++ b/create-critter.php
@@ -0,0 +1,235 @@
+exec('CREATE TABLE IF NOT EXISTS critters (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ noun_id INTEGER NOT NULL REFERENCES nouns(id),
+ adjective_id INTEGER NOT NULL REFERENCES adjectives(id),
+ verb_id INTEGER NOT NULL REFERENCES verbs(id),
+ location_id INTEGER NOT NULL REFERENCES locations(id),
+ author_id INTEGER NOT NULL REFERENCES users(id),
+ image TEXT
+)');
+
+$critterColumns = $pdo->query('PRAGMA table_info(critters)')->fetchAll(PDO::FETCH_COLUMN, 1);
+if (!in_array('author_id', $critterColumns, true)) {
+ $pdo->exec('ALTER TABLE critters ADD COLUMN author_id INTEGER REFERENCES users(id)');
+}
+if (!in_array('image', $critterColumns, true)) {
+ $pdo->exec('ALTER TABLE critters ADD COLUMN image TEXT');
+}
+
+$pdo->exec('CREATE INDEX IF NOT EXISTS idx_critters_noun_id ON critters (noun_id)');
+$pdo->exec('CREATE INDEX IF NOT EXISTS idx_critters_adjective_id ON critters (adjective_id)');
+$pdo->exec('CREATE INDEX IF NOT EXISTS idx_critters_verb_id ON critters (verb_id)');
+$pdo->exec('CREATE INDEX IF NOT EXISTS idx_critters_location_id ON critters (location_id)');
+$pdo->exec('CREATE INDEX IF NOT EXISTS idx_critters_author_id ON critters (author_id)');
+
+function rowExistsById(PDO $pdo, string $table, $id): bool
+{
+ $stmt = $pdo->prepare("SELECT 1 FROM {$table} WHERE id = :id");
+ $stmt->execute(['id' => $id]);
+ return (bool) $stmt->fetch();
+}
+
+function getValueById(PDO $pdo, string $table, $id): string
+{
+ $stmt = $pdo->prepare("SELECT value FROM {$table} WHERE id = :id");
+ $stmt->execute(['id' => $id]);
+ return (string) $stmt->fetchColumn();
+}
+
+$adjectives = $pdo->query('SELECT id, value FROM adjectives ORDER BY value')->fetchAll(PDO::FETCH_ASSOC);
+$nouns = $pdo->query('SELECT id, value FROM nouns ORDER BY value')->fetchAll(PDO::FETCH_ASSOC);
+$verbs = $pdo->query('SELECT id, value FROM verbs ORDER BY value')->fetchAll(PDO::FETCH_ASSOC);
+$locations = $pdo->query('SELECT id, value FROM locations ORDER BY value')->fetchAll(PDO::FETCH_ASSOC);
+
+$errors = [];
+$selected = [
+ 'adjective_id' => '',
+ 'noun_id' => '',
+ 'verb_id' => '',
+ 'location_id' => '',
+];
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $fields = [
+ 'adjective_id' => ['table' => 'adjectives', 'label' => 'adjective'],
+ 'noun_id' => ['table' => 'nouns', 'label' => 'noun'],
+ 'verb_id' => ['table' => 'verbs', 'label' => 'verb'],
+ 'location_id' => ['table' => 'locations', 'label' => 'location'],
+ ];
+
+ foreach ($fields as $field => $info) {
+ $value = $_POST[$field] ?? '';
+ $selected[$field] = $value;
+
+ if ($value === '' || !ctype_digit((string) $value) || !rowExistsById($pdo, $info['table'], $value)) {
+ $errors[$field] = 'Please choose a valid ' . $info['label'] . '.';
+ }
+ }
+
+ if (empty($errors)) {
+ $adjectiveValue = getValueById($pdo, 'adjectives', $selected['adjective_id']);
+ $nounValue = getValueById($pdo, 'nouns', $selected['noun_id']);
+ $verbValue = getValueById($pdo, 'verbs', $selected['verb_id']);
+ $locationValue = getValueById($pdo, 'locations', $selected['location_id']);
+
+ $prompt = "A {$adjectiveValue} {$nounValue} {$verbValue} {$locationValue} in a high quality 3d animation art style. The image should be vibrant and colorful, capturing the essence of the action and setting.";
+
+ $ch = curl_init('https://api.replicate.com/v1/models/black-forest-labs/flux-schnell/predictions');
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/ca-certificates.crt');
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ 'Authorization: Bearer ' . getenv('REPLICATE_API_TOKEN'),
+ 'Content-Type: application/json',
+ 'Prefer: wait',
+ ]);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+ 'input' => [
+ 'prompt' => $prompt,
+ ],
+ ]));
+
+ $replicateResponse = curl_exec($ch);
+ $imageUrl = null;
+
+ if ($replicateResponse !== false) {
+ $prediction = json_decode($replicateResponse, true);
+
+ $pollAttempt = 0;
+ while (
+ isset($prediction['status'])
+ && !in_array($prediction['status'], ['succeeded', 'failed', 'canceled'], true)
+ && $pollAttempt < 30
+ ) {
+ sleep(2);
+
+ $pollCh = curl_init($prediction['urls']['get']);
+ curl_setopt($pollCh, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($pollCh, CURLOPT_CAINFO, '/etc/ssl/certs/ca-certificates.crt');
+ curl_setopt($pollCh, CURLOPT_HTTPHEADER, [
+ 'Authorization: Bearer ' . getenv('REPLICATE_API_TOKEN'),
+ ]);
+
+ $pollResponse = curl_exec($pollCh);
+ if ($pollResponse !== false) {
+ $prediction = json_decode($pollResponse, true);
+ }
+
+ $pollAttempt++;
+ }
+
+ if (($prediction['status'] ?? null) === 'succeeded') {
+ $output = $prediction['output'] ?? null;
+ $imageUrl = is_array($output) ? ($output[0] ?? null) : $output;
+ }
+ }
+
+ $imageFilename = null;
+
+ if ($imageUrl !== null) {
+ $imageCh = curl_init($imageUrl);
+ curl_setopt($imageCh, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($imageCh, CURLOPT_CAINFO, '/etc/ssl/certs/ca-certificates.crt');
+ $imageData = curl_exec($imageCh);
+
+ if ($imageData !== false) {
+ $imagesDir = __DIR__ . '/critter-images';
+ if (!is_dir($imagesDir)) {
+ mkdir($imagesDir, 0755, true);
+ }
+
+ $imageFilename = bin2hex(random_bytes(16)) . '.webp';
+ file_put_contents($imagesDir . '/' . $imageFilename, $imageData);
+ }
+ }
+
+ $stmt = $pdo->prepare('INSERT INTO critters (noun_id, adjective_id, verb_id, location_id, author_id, image) VALUES (:noun_id, :adjective_id, :verb_id, :location_id, :author_id, :image)');
+ $stmt->execute([
+ 'noun_id' => $selected['noun_id'],
+ 'adjective_id' => $selected['adjective_id'],
+ 'verb_id' => $selected['verb_id'],
+ 'location_id' => $selected['location_id'],
+ 'author_id' => $_SESSION['user_id'],
+ 'image' => $imageFilename,
+ ]);
+
+ header('Location: /');
+ exit;
+ }
+}
+
+function renderOptions(array $rows, $selectedId): void
+{
+ foreach ($rows as $row) {
+ $isSelected = (string) $selectedId === (string) $row['id'];
+ echo '';
+ }
+}
+?>
+
+
+
+
+ Create Critter
+
+
+
Create Your New Critter
+
+
+
diff --git a/critter-images/5ec5837834afbb3bbd83a0b8aab4e231.webp b/critter-images/5ec5837834afbb3bbd83a0b8aab4e231.webp
new file mode 100644
index 0000000..dcc2ebc
Binary files /dev/null and b/critter-images/5ec5837834afbb3bbd83a0b8aab4e231.webp differ
diff --git a/critter-images/a15b51f0de1b828ec35c2b1c852fbe33.webp b/critter-images/a15b51f0de1b828ec35c2b1c852fbe33.webp
new file mode 100644
index 0000000..b6e95c7
Binary files /dev/null and b/critter-images/a15b51f0de1b828ec35c2b1c852fbe33.webp differ
diff --git a/critter-images/a2ac9a8e63e9ff6c72b09c883ddd2344.webp b/critter-images/a2ac9a8e63e9ff6c72b09c883ddd2344.webp
new file mode 100644
index 0000000..9c95ca8
Binary files /dev/null and b/critter-images/a2ac9a8e63e9ff6c72b09c883ddd2344.webp differ
diff --git a/db.php b/db.php
index 5e4fc7e..4a4c372 100644
--- a/db.php
+++ b/db.php
@@ -7,6 +7,7 @@ function getDbConnection(): PDO
$dbPath = __DIR__ . '/../app.sqlite';
$pdo = new PDO('sqlite:' . $dbPath);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $pdo->exec('PRAGMA foreign_keys = ON');
$pdo->exec('CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
diff --git a/index.php b/index.php
index e228233..6a214fe 100644
--- a/index.php
+++ b/index.php
@@ -3,14 +3,92 @@ require __DIR__ . '/session.php';
require __DIR__ . '/db.php';
start_app_session();
+$pdo = getDbConnection();
+$tableExists = $pdo->query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'adjectives'")->fetch();
+
+if (!$tableExists) {
+ $pdo->exec('CREATE TABLE adjectives (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ value TEXT NOT NULL
+ )');
+
+ $stmt = $pdo->prepare('INSERT INTO adjectives (value) VALUES (:value)');
+ foreach (['black', 'blue', 'brown', 'crochet', 'cute', 'fluffy', 'fuzzy', 'glittery', 'glowing neon', 'gray', 'green', 'lime green', 'magenta', 'orange', 'pastel', 'pink', 'purple', 'rainbow', 'red', 'sleepy', 'soft', 'sparkly', 'stone', 'teal', 'white', 'wooden', 'yellow'] as $adjective) {
+ $stmt->execute(['value' => $adjective]);
+ }
+}
+
+$tableExists = $pdo->query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'nouns'")->fetch();
+
+if (!$tableExists) {
+ $pdo->exec('CREATE TABLE nouns (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ value TEXT NOT NULL
+ )');
+
+ $stmt = $pdo->prepare('INSERT INTO nouns (value) VALUES (:value)');
+ foreach (['beaver', 'bird', 'bunny rabbit', 'cat', 'chipmunk', 'cow', 'elephant', 'fox', 'frog', 'hamster', 'horse', 'kangaroo', 'koala', 'lemur', 'lion', 'monkey', 'moose', 'mouse', 'panda', 'penguin', 'pig', 'pika', 'puppy', 'quokka', 'raccoon', 'rat', 'sea otter', 'sheep', 'skunk', 'sloth', 'squirrel', 'teddy bear', 'tiger', 'turtle', 'wombat'] as $noun) {
+ $stmt->execute(['value' => $noun]);
+ }
+}
+
+$tableExists = $pdo->query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'verbs'")->fetch();
+
+if (!$tableExists) {
+ $pdo->exec('CREATE TABLE verbs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ value TEXT NOT NULL
+ )');
+
+ $stmt = $pdo->prepare('INSERT INTO verbs (value) VALUES (:value)');
+ foreach (['runs', 'jumps', 'waves hello', 'snuggles their blanket', 'swims', 'takes a bubble bath', 'wears a chef hat and stirs a baking bowl', 'hula hoops', 'builds with blocks', 'flies a kite', 'skateboards', 'rides their scooter', 'plays chess', 'writes with a pen and pad', 'plays guitar', 'plays the drums', 'plays the piano', 'roller skates', 'plays the saxophone', 'plays basketball', 'plays baseball', 'plays hockey', 'plays soccer', 'reads a book', 'wears pajamas', 'sleeps in bed', 'sings into a microphone', 'jumps rope', 'plays with a yo-yo', 'waters a plant with a watering can', 'sits by a campfire', 'naps in a hammock', 'plays golf', 'plays tennis', 'bakes cookies', 'does yoga'] as $verb) {
+ $stmt->execute(['value' => $verb]);
+ }
+}
+
+$tableExists = $pdo->query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'locations'")->fetch();
+
+if (!$tableExists) {
+ $pdo->exec('CREATE TABLE locations (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ value TEXT NOT NULL
+ )');
+
+ $stmt = $pdo->prepare('INSERT INTO locations (value) VALUES (:value)');
+ foreach (['at the beach', 'on a sunny island', 'in the backyard', 'inside a snow globe', 'inside a Christmas ornament', 'safely inside a hot air balloon up in the sky', 'on the moon', 'in front of a gnomes house', 'in a majestic waterfall', 'on a trampoline', 'inside an igloo', 'on a rainbow', 'underwater inside an aquarium', 'at the pumpkin patch', 'at the north pole', 'in a candy land', 'on a farm', 'in the frontyard', 'in a big grassy field', 'on top of a cloud', 'on a bridge', 'in a forest', 'at the top of a mountain', 'in a boat on a river', 'at the mall', 'on a baseball field', 'on a soccer field', 'on a basketball court', 'on a hockey rink', 'in a swimming pool', 'in a hot tub', 'in the kitchen'] as $location) {
+ $stmt->execute(['value' => $location]);
+ }
+}
+
$dayOfWeek = date('l');
-$isLoggedIn = isset($_SESSION['username']);
+$isLoggedIn = isset($_SESSION['username']) && isset($_SESSION['user_id']);
+
+$myCritters = [];
+if ($isLoggedIn) {
+ $crittersTableExists = $pdo->query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'critters'")->fetch();
+
+ if ($crittersTableExists) {
+ $stmt = $pdo->prepare('
+ SELECT a.value AS adjective, n.value AS noun, v.value AS verb, l.value AS location, c.image AS image
+ FROM critters c
+ JOIN adjectives a ON a.id = c.adjective_id
+ JOIN nouns n ON n.id = c.noun_id
+ JOIN verbs v ON v.id = c.verb_id
+ JOIN locations l ON l.id = c.location_id
+ WHERE c.author_id = :author_id
+ ORDER BY c.id DESC
+ ');
+ $stmt->execute(['author_id' => $_SESSION['user_id']]);
+ $myCritters = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+}
?>
Welcome
+
@@ -20,7 +98,26 @@ $isLoggedIn = isset($_SESSION['username']);