Added achievement data

This commit is contained in:
2026-04-26 15:16:17 +10:00
parent 1821b99524
commit f6d5b97784
6 changed files with 1022 additions and 0 deletions
@@ -0,0 +1,740 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// ---------------------------------------------------------------
// 1. achievement_difficulties
// ---------------------------------------------------------------
Schema::create('achievement_difficulties', function (Blueprint $table) {
$table->id();
$table->string('internal_name')->unique();
$table->string('name');
$table->text('description');
$table->timestamps();
});
DB::table('achievement_difficulties')->insert([
[
'internal_name' => 'easy',
'name' => 'Easy',
'description' => "As easy as booking a flight somewhere new!",
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'moderate',
'name' => 'Moderate',
'description' => 'You might have to reroute your flights a little just to get the achievement!',
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'hard',
'name' => 'Hard',
'description' => "You'll need to go quite a bit out of the way to get this achievement!",
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'expensive',
'name' => 'Expensive',
'description' => 'It might be hard, but it will be easier if you have a lot of money!',
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'near_impossible',
'name' => 'Near-Impossible',
'description' => 'You will actively have to try to get this achievement and they may be very few ways to go about it.',
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'impossible',
'name' => 'Impossible',
'description' => 'This achievement is impossible to get if you start today, but was previously possible.',
'created_at' => now(),
'updated_at' => now(),
],
]);
// ---------------------------------------------------------------
// 2. achievement_categories
// ---------------------------------------------------------------
Schema::create('achievement_categories', function (Blueprint $table) {
$table->id();
$table->string('internal_name')->unique();
$table->string('name');
$table->text('description');
$table->timestamps();
});
DB::table('achievement_categories')->insert([
[
'internal_name' => 'general_flying',
'name' => 'General Flying',
'description' => 'Achievements earned through everyday flying activity.',
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'countries_and_continents',
'name' => 'Countries & Continents',
'description' => 'Achievements for visiting countries, regions, and continents.',
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'aircraft',
'name' => 'Aircraft',
'description' => 'Achievements related to specific aircraft types and manufacturers.',
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'airlines_and_alliances',
'name' => 'Airlines and Alliances',
'description' => 'Achievements focused on airlines and alliances',
'created_at' => now(),
'updated_at' => now(),
],
[
'internal_name' => 'fun_challenges',
'name' => 'Fun Challenges',
'description' => 'Quirky and creative challenges for the adventurous traveller.',
'created_at' => now(),
'updated_at' => now(),
],
]);
// ---------------------------------------------------------------
// 3. Alter achievements — add new columns & foreign keys
// ---------------------------------------------------------------
Schema::table('achievements', function (Blueprint $table) {
// Replace description with split fields
$table->string('short_description')->after('internal_name');
$table->text('long_description')->after('short_description');
// Progressive tracking
$table->boolean('progressive')->default(false)->after('long_description');
// Difficulty flavour text specific to this achievement
$table->text('difficulty_description')->nullable()->after('progressive');
// Foreign keys
$table->foreignId('achievement_category_id')
->after('difficulty_description')
->constrained('achievement_categories')
->restrictOnDelete();
$table->foreignId('achievement_difficulty_id')
->after('achievement_category_id')
->constrained('achievement_difficulties')
->restrictOnDelete();
// Drop the old monolithic description column
$table->dropColumn('description');
});
// ---------------------------------------------------------------
// 4. Alter user_achievements — add nullable progress column
// ---------------------------------------------------------------
Schema::table('user_achievements', function (Blueprint $table) {
$table->unsignedInteger('progress')->nullable()->after('achievement_id');
});
Schema::create('notifications', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained('users')
->cascadeOnDelete();
// Content
$table->string('title');
$table->text('body');
$table->string('url')->nullable();
// Achievement flag & optional link
$table->boolean('is_achievement')->default(false);
$table->foreignId('achievement_id')
->nullable()
->constrained('achievements')
->nullOnDelete();
// State
$table->timestamp('read_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
// Common query patterns
$table->index(['user_id', 'read_at']);
$table->index('expires_at');
});
$this->seedAchievements();
}
private function seedAchievements(): void
{
$difficulties = DB::table('achievement_difficulties')->pluck('id', 'internal_name');
$categories = DB::table('achievement_categories')->pluck('id', 'internal_name');
$easy = $difficulties['easy'];
$moderate = $difficulties['moderate'];
$hard = $difficulties['hard'];
$expensive = $difficulties['expensive'];
$nearImposs = $difficulties['near_impossible'];
$impossible = $difficulties['impossible'];
$generalFlying = $categories['general_flying'];
$countriesAndContinents = $categories['countries_and_continents'];
$aircraft = $categories['aircraft'];
$airlinesAndAlliances = $categories['airlines_and_alliances'];
$funChallenges = $categories['fun_challenges'];
$icon = 'standard_achievement.png';
$now = now();
$achievements = [
// -----------------------------------------------------------
// General Flying
// -----------------------------------------------------------
[
'internal_name' => 'general_flying.first_flight',
'name' => 'Off the Ground',
'short_description' => 'Log your very first flight.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'general_flying.domestic_flight',
'name' => 'Home Turf',
'short_description' => 'Take a domestic flight.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'general_flying.international_flight',
'name' => 'Passport Stamp Collector',
'short_description' => 'Take an international flight.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'general_flying.business_class',
'name' => 'Flatbed Fan',
'short_description' => 'Fly in Business Class.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $expensive,
],
[
'internal_name' => 'general_flying.first_class',
'name' => 'Caviar at 35,000 Feet',
'short_description' => 'Fly in First Class.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $expensive,
],
[
'internal_name' => 'general_flying.premium_economy',
'name' => 'Legroom Lover',
'short_description' => 'Fly in Premium Economy.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $expensive,
],
[
'internal_name' => 'general_flying.business_or_first',
'name' => 'Turn Left',
'short_description' => 'Fly in Business or First Class.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'general_flying.fly_private',
'name' => 'Wheels Up',
'short_description' => 'Fly on a private aircraft.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $expensive,
],
[
'internal_name' => 'general_flying.general_aviation',
'name' => 'Little Plane, Big Sky',
'short_description' => 'Take a general aviation flight.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'general_flying.domestic_two_countries',
'name' => 'Local Explorer',
'short_description' => 'Take domestic flights in two different countries.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'general_flying.10_flights',
'name' => 'Frequent Flyer in Training',
'short_description' => 'Log 10 flights.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'general_flying.100_flights',
'name' => 'Century in the Sky',
'short_description' => 'Log 100 flights.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'general_flying.500_flights',
'name' => 'The 500 Club',
'short_description' => 'Log 500 flights.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $hard,
],
[
'internal_name' => 'general_flying.1000_flights',
'name' => 'Skybound for Life',
'short_description' => 'Log 1,000 flights.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $generalFlying,
'achievement_difficulty_id'=> $expensive,
],
// -----------------------------------------------------------
// Countries & Continents
// -----------------------------------------------------------
[
'internal_name' => 'countries_continents.intercontinental',
'name' => 'Intercontinental',
'short_description' => 'Fly between two different continents.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'countries_continents.fly_to_africa',
'name' => 'Safari Bound',
'short_description' => 'Fly to Africa.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'countries_continents.fly_to_asia',
'name' => 'Orient Express (Air Edition)',
'short_description' => 'Fly to Asia.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'countries_continents.fly_to_oceania',
'name' => "G'Day from 35,000 Feet",
'short_description' => 'Fly to Oceania.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'countries_continents.fly_to_antarctica',
'name' => 'Penguin Spotter',
'short_description' => 'Fly to Antarctica.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => 'Very few commercial or charter services operate to Antarctica, making this extremely difficult to pull off.',
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $expensive,
],
[
'internal_name' => 'countries_continents.fly_to_europe',
'name' => 'Bonjour from Above',
'short_description' => 'Fly to Europe.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'countries_continents.fly_to_south_america',
'name' => 'Southern Cross',
'short_description' => 'Fly to South America.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'countries_continents.fly_to_north_america',
'name' => 'Stars, Stripes & Contrails',
'short_description' => 'Fly to North America.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id'=> $easy,
],
// -----------------------------------------------------------
// Aircraft
// -----------------------------------------------------------
[
'internal_name' => 'aircraft.fly_on_a_plane',
'name' => 'Up, Up and Away',
'short_description' => 'Fly on an aeroplane.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'aircraft.fly_on_a_helicopter',
'name' => 'Chop Chop',
'short_description' => 'Fly on a helicopter.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'aircraft.fly_on_a_jet',
'name' => 'Jet Setter',
'short_description' => 'Fly on a jet-powered aircraft.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'aircraft.fly_on_a_prop',
'name' => 'Propeller Head',
'short_description' => 'Fly on a propeller driven aircraft.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'aircraft.all_boeing_7x7',
'name' => 'Seven Heaven',
'short_description' => 'Fly every Boeing 7x7 aircraft family.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => 'Requires flying the 707, 717, 727, 737, 747, 757, 767, 777, and 787 — some of which are no longer in commercial service.',
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $impossible,
],
[
'internal_name' => 'aircraft.all_airbus_a3xx',
'name' => 'Airbus Aristocrat',
'short_description' => 'Fly every Airbus A3xx aircraft family.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => 'Covers the A300, A310, A318A321, A330, A340, A350, and A380 families. You are likely going to have to go to Iran to get this one.',
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $nearImposs,
],
[
'internal_name' => 'aircraft.quad_engine',
'name' => 'Four on the Floor',
'short_description' => 'Fly on a four-engine aircraft.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => 'Four-engine jets are becoming increasingly rare as twin-engine widebodies dominate long-haul routes.',
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $hard,
],
[
'internal_name' => 'aircraft.double_decker',
'name' => 'Upper Deck Club',
'short_description' => 'Fly on a double-decker aircraft.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => 'Primarily the A380 and 747, which operate on a limited and shrinking number of routes.',
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $hard,
],
[
'internal_name' => 'aircraft.single_engine',
'name' => 'Solo Spinner',
'short_description' => 'Fly on a single-engine aircraft.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'aircraft.tri_engine',
'name' => 'Triple Threat',
'short_description' => 'Fly on a tri-engine aircraft.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => 'Most tri-jets are out of service nowadays, and tri-props even rarer',
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $nearImposs,
],
[
'internal_name' => 'aircraft.smaller_manufacturer',
'name' => 'Break the Duopoly',
'short_description' => 'Fly on an aircraft from a manufacturer other than Boeing or Airbus.',
'long_description' => '',
'icon' => $icon,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $moderate,
],
// -----------------------------------------------------------
// Airlines & Alliances
// -----------------------------------------------------------
[
'internal_name' => 'airlines_alliances.all_skyteam',
'name' => 'Team Player',
'short_description' => 'Fly with every airline in the SkyTeam alliance.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $airlinesAndAlliances,
'achievement_difficulty_id'=> $hard,
],
[
'internal_name' => 'airlines_alliances.all_oneworld',
'name' => 'One World Wonder',
'short_description' => 'Fly with every airline in the Oneworld alliance.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $airlinesAndAlliances,
'achievement_difficulty_id'=> $hard,
],
[
'internal_name' => 'airlines_alliances.all_star_alliance',
'name' => 'Star Collector',
'short_description' => 'Fly with every airline in the Star Alliance.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $airlinesAndAlliances,
'achievement_difficulty_id'=> $hard,
],
[
'internal_name' => 'airlines_alliances.all_vanilla_alliance',
'name' => 'Plain Extraordinary',
'short_description' => 'Fly with every airline in the Vanilla Alliance.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $airlinesAndAlliances,
'achievement_difficulty_id'=> $hard,
],
// ---------------------------------------------------------------
// Fun Challenges
// ---------------------------------------------------------------
[
'internal_name' => 'fun_challenges.airline_alphabet',
'name' => 'Fly the Alphabet',
'short_description' => 'Fly with an airline whose IATA code starts with every letter of the alphabet.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => 'Some letters have very few airlines with a matching IATA code, requiring creative routing.',
'achievement_category_id' => $funChallenges,
'achievement_difficulty_id' => $hard,
],
[
'internal_name' => 'fun_challenges.airport_alphabet',
'name' => 'Visit the Alphabet',
'short_description' => 'Visit an airport whose IATA code starts with every letter of the alphabet.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => 'Certain letters — particularly Q, X, and Z — have extremely limited airport coverage, making this a serious challenge.',
'achievement_category_id' => $funChallenges,
'achievement_difficulty_id' => $nearImposs,
],
// ---------------------------------------------------------------
// Countries & Continents
// ---------------------------------------------------------------
[
'internal_name' => 'countries_continents.all_inhabited_continents',
'name' => 'Six of One',
'short_description' => 'Fly to all six inhabited continents.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => null,
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id' => $hard,
],
[
'internal_name' => 'countries_continents.all_continents',
'name' => 'Seven Wonders of the Skies',
'short_description' => 'Fly to all seven continents, including Antarctica.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => 'Antarctica has no commercial scheduled service — expect a specialist charter or expedition flight.',
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id' => $nearImposs,
],
[
'internal_name' => 'countries_continents.all_continent_pairs_one_way',
'name' => 'Grand Junction',
'short_description' => 'Fly between every possible pair of continents at least one way.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => 'There are 15 unique continent pairs (excluding Antarctica)',
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id' => $hard,
],
[
'internal_name' => 'countries_continents.all_continent_pairs_both_ways',
'name' => 'There and Back Again',
'short_description' => 'Fly between every possible pair of continents in both directions.',
'long_description' => '',
'icon' => $icon,
'progressive' => true,
'difficulty_description' => "30 different intercontinental flights required, good luck.",
'achievement_category_id' => $countriesAndContinents,
'achievement_difficulty_id' => $nearImposs,
],
];
DB::table('achievements')->insert($achievements);
}
public function down(): void
{
// Reverse user_achievements
Schema::table('user_achievements', function (Blueprint $table) {
$table->dropColumn('progress');
});
// Reverse achievements
Schema::table('achievements', function (Blueprint $table) {
$table->text('description')->after('internal_name');
$table->dropForeign(['achievement_difficulty_id']);
$table->dropForeign(['achievement_category_id']);
$table->dropColumn([
'short_description',
'long_description',
'progressive',
'difficulty_description',
'achievement_category_id',
'achievement_difficulty_id',
]);
});
Schema::dropIfExists('achievement_categories');
Schema::dropIfExists('achievement_difficulties');
Schema::dropIfExists('notifications');
}
};