Skip to content
This repository was archived by the owner on Jun 12, 2026. It is now read-only.

Commit 64149ab

Browse files
Merge pull request #59 from ericvanjohnson/main
Adding prize giveaway picker
2 parents 3f7fb30 + d9efe5c commit 64149ab

18 files changed

Lines changed: 1284 additions & 1 deletion

.env.example

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,28 @@ LOG_STACK=single
2020
LOG_DEPRECATIONS_CHANNEL=null
2121
LOG_LEVEL=debug
2222

23+
# Primary database (Laravel)
2324
DB_CONNECTION=sqlite
2425
# DB_HOST=127.0.0.1
2526
# DB_PORT=3306
2627
# DB_DATABASE=laravel
2728
# DB_USERNAME=root
2829
# DB_PASSWORD=
2930

31+
# PHP Tek TV database (shared MySQL). Host/port/user/password fall back to DB_* if unset.
32+
PHPTEK_TV_DB_DATABASE=phptek_tv
33+
# PHPTEK_TV_DB_HOST=
34+
# PHPTEK_TV_DB_PORT=
35+
# PHPTEK_TV_DB_USERNAME=
36+
# PHPTEK_TV_DB_PASSWORD=
37+
38+
# Mobile app database (shared MySQL). Host/port/user/password fall back to DB_* if unset.
39+
MOBILE_APP_DB_DATABASE=phptek_mobile_app
40+
# MOBILE_APP_DB_HOST=
41+
# MOBILE_APP_DB_PORT=
42+
# MOBILE_APP_DB_USERNAME=
43+
# MOBILE_APP_DB_PASSWORD=
44+
3045
SESSION_DRIVER=database
3146
SESSION_LIFETIME=120
3247
SESSION_ENCRYPT=false
@@ -60,9 +75,43 @@ AWS_ACCESS_KEY_ID=
6075
AWS_SECRET_ACCESS_KEY=
6176
AWS_DEFAULT_REGION=us-east-1
6277
AWS_BUCKET=
78+
AWS_URL=
79+
AWS_ENDPOINT=
6380
AWS_USE_PATH_STYLE_ENDPOINT=false
6481

82+
# DigitalOcean Spaces (S3-compatible)
83+
DO_ACCESS_KEY_ID=
84+
DO_SECRET_ACCESS_KEY=
85+
DO_REGION=
86+
DO_BUCKET=
87+
DO_URL=
88+
DO_ENDPOINT=
89+
DO_USE_PATH_STYLE_ENDPOINT=false
90+
6591
VITE_APP_NAME="${APP_NAME}"
6692

93+
# Conference configuration
6794
CONFERENCE_UUID=
6895
CONFERENCE_TIMEZONE=America/Chicago
96+
97+
# Giveaway page (/giveaway)
98+
# Set GIVEAWAY_PASSWORD to require an unlock password; leave blank for public access.
99+
GIVEAWAY_PASSWORD=
100+
# For demos: set to a number 1..N to relax eligibility to "matched at least N required vendors".
101+
# Leave blank in production for strict (full superset) eligibility.
102+
GIVEAWAY_DEMO_MIN_MATCHES=
103+
104+
# NativePHP / mobile app build
105+
NATIVEPHP_APP_ID=
106+
NATIVEPHP_APP_VERSION=
107+
NATIVEPHP_APP_VERSION_CODE=
108+
109+
# Android signing
110+
ANDROID_KEYSTORE_FILE=
111+
ANDROID_KEYSTORE_PASSWORD=
112+
ANDROID_KEY_ALIAS=
113+
ANDROID_KEY_PASSWORD=
114+
115+
# iOS signing
116+
IOS_DISTRIBUTION_CERTIFICATE_PATH=
117+
IOS_DISTRIBUTION_CERTIFICATE_PASSWORD=
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use App\Services\GiveawayEligibility;
6+
use Illuminate\Console\Command;
7+
use Illuminate\Support\Facades\DB;
8+
use Illuminate\Support\Facades\Storage;
9+
10+
class SeedGiveawayDemoCommand extends Command
11+
{
12+
protected $signature = 'giveaway:seed-demo
13+
{--count=20 : Number of fake attendees to seed (ignored with --clear)}
14+
{--matches= : How many required vendors each attendee should hit (default: all)}
15+
{--clear : Remove all rows inserted by previous demo seeds}';
16+
17+
protected $description = 'Seed fake share_intents rows so /giveaway has data to demo (non-production only)';
18+
19+
private const TRACKING_FILE = 'giveaway-demo-seeded.json';
20+
21+
public function handle(GiveawayEligibility $eligibility): int
22+
{
23+
if (app()->environment('production')) {
24+
$this->error('Refusing to run in the production environment.');
25+
26+
return self::FAILURE;
27+
}
28+
29+
return $this->option('clear')
30+
? $this->clear()
31+
: $this->seed($eligibility);
32+
}
33+
34+
private function seed(GiveawayEligibility $eligibility): int
35+
{
36+
$required = $eligibility->requiredVendorSlugs();
37+
38+
if ($required->isEmpty()) {
39+
$this->error('No required vendor slugs found. Check CONFERENCE_UUID and conference_sponsor rows on phptek_tv.');
40+
41+
return self::FAILURE;
42+
}
43+
44+
$count = (int) $this->option('count');
45+
$matchesOption = $this->option('matches');
46+
$matches = $matchesOption !== null ? (int) $matchesOption : $required->count();
47+
$matches = max(1, min($matches, $required->count()));
48+
49+
$userUuids = DB::connection('phptek_tv')
50+
->table('users')
51+
->whereNotNull('uuid')
52+
->inRandomOrder()
53+
->limit($count)
54+
->pluck('uuid');
55+
56+
if ($userUuids->isEmpty()) {
57+
$this->error('No users found on phptek_tv to seed against.');
58+
59+
return self::FAILURE;
60+
}
61+
62+
$now = now();
63+
$rows = [];
64+
65+
foreach ($userUuids as $uuid) {
66+
$slugsForUser = $required->shuffle()->take($matches);
67+
68+
foreach ($slugsForUser as $slug) {
69+
$rows[] = [
70+
'source_badge_uuid' => $uuid,
71+
'target_type' => 'vendor',
72+
'target_id' => $slug,
73+
'created_at' => $now,
74+
'updated_at' => $now,
75+
];
76+
}
77+
}
78+
79+
$connection = DB::connection('mobile_app');
80+
$insertedIds = [];
81+
82+
$connection->transaction(function () use ($connection, $rows, &$insertedIds) {
83+
foreach ($rows as $row) {
84+
$insertedIds[] = $connection->table('share_intents')->insertGetId($row);
85+
}
86+
});
87+
88+
$this->trackInserted($insertedIds);
89+
90+
$this->info(sprintf(
91+
'Seeded %d share_intents rows across %d attendees (matches=%d of %d required vendors).',
92+
count($insertedIds),
93+
$userUuids->count(),
94+
$matches,
95+
$required->count()
96+
));
97+
$this->line('Run `php artisan giveaway:seed-demo --clear` to remove them.');
98+
99+
return self::SUCCESS;
100+
}
101+
102+
private function clear(): int
103+
{
104+
$ids = $this->loadTrackedIds();
105+
106+
if (empty($ids)) {
107+
$this->info('No tracked demo rows to remove.');
108+
109+
return self::SUCCESS;
110+
}
111+
112+
$deleted = DB::connection('mobile_app')
113+
->table('share_intents')
114+
->whereIn('id', $ids)
115+
->delete();
116+
117+
Storage::disk('local')->delete(self::TRACKING_FILE);
118+
119+
$this->info("Removed {$deleted} demo share_intents rows.");
120+
121+
return self::SUCCESS;
122+
}
123+
124+
/**
125+
* @param array<int, int> $newIds
126+
*/
127+
private function trackInserted(array $newIds): void
128+
{
129+
$existing = $this->loadTrackedIds();
130+
$merged = array_values(array_unique([...$existing, ...$newIds]));
131+
132+
Storage::disk('local')->put(self::TRACKING_FILE, json_encode($merged, JSON_PRETTY_PRINT));
133+
}
134+
135+
/**
136+
* @return array<int, int>
137+
*/
138+
private function loadTrackedIds(): array
139+
{
140+
if (! Storage::disk('local')->exists(self::TRACKING_FILE)) {
141+
return [];
142+
}
143+
144+
$raw = Storage::disk('local')->get(self::TRACKING_FILE);
145+
$decoded = json_decode($raw, true);
146+
147+
return is_array($decoded) ? array_map('intval', $decoded) : [];
148+
}
149+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
class GiveawayPassword
10+
{
11+
public function handle(Request $request, Closure $next): Response
12+
{
13+
$expected = config('tek.giveaway.password');
14+
15+
if (empty($expected)) {
16+
return $next($request);
17+
}
18+
19+
if ($request->session()->get('giveaway_authed') === true) {
20+
return $next($request);
21+
}
22+
23+
return redirect()->route('giveaway.unlock');
24+
}
25+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\Models\MobileApp;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class VendorContact extends Model
8+
{
9+
protected $connection = 'mobile_app';
10+
11+
protected $table = 'vendor_contacts';
12+
13+
protected $guarded = [];
14+
}

app/Models/PhpTekTv/Conference.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\Models\PhpTekTv;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class Conference extends Model
8+
{
9+
protected $connection = 'phptek_tv';
10+
11+
protected $table = 'conferences';
12+
13+
protected $guarded = [];
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\Models\PhpTekTv;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class ShareIntent extends Model
8+
{
9+
protected $connection = 'phptek_tv';
10+
11+
protected $table = 'share_intents';
12+
13+
protected $guarded = [];
14+
}

app/Models/PhpTekTv/Sponsor.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\Models\PhpTekTv;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class Sponsor extends Model
8+
{
9+
protected $connection = 'phptek_tv';
10+
11+
protected $table = 'sponsors';
12+
13+
protected $guarded = [];
14+
}

app/Providers/AppServiceProvider.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Providers;
44

5+
use App\Services\GiveawayEligibility;
56
use Illuminate\Support\ServiceProvider;
67

78
class AppServiceProvider extends ServiceProvider
@@ -11,7 +12,7 @@ class AppServiceProvider extends ServiceProvider
1112
*/
1213
public function register(): void
1314
{
14-
//
15+
$this->app->singleton(GiveawayEligibility::class);
1516
}
1617

1718
/**

0 commit comments

Comments
 (0)