Files
FlightsAPI/database/migrations/2026_04_26_030253_build_achievement_system.php
T
2026-04-26 20:00:11 +10:00

913 lines
43 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use App\Models\Airline;
use App\Models\Alliance;
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');
$table->unsignedInteger('threshold')->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();
$this->createAlliances();
}
public function createAlliances(): void
{
Schema::create('alliances', function (Blueprint $table) {
$table->id();
$table->string('internal_name')->unique();
$table->string('name');
$table->timestamps();
});
Schema::table('airlines', function (Blueprint $table) {
$table->foreignId('alliance_id')
->nullable()
->constrained('alliances')
->nullOnDelete();
});
DB::table('alliances')->insert([
['internal_name' => 'skyteam', 'name' => 'SkyTeam', 'created_at' => now(), 'updated_at' => now()],
['internal_name' => 'oneworld', 'name' => 'Oneworld', 'created_at' => now(), 'updated_at' => now()],
['internal_name' => 'star_alliance', 'name' => 'Star Alliance', 'created_at' => now(), 'updated_at' => now()],
['internal_name' => 'vanilla_alliance','name' => 'Vanilla Alliance', 'created_at' => now(), 'updated_at' => now()],
]);
Airline::whereInternalName('xiamen-airlines')->update(['internal_name' => 'xiamen-air', 'name' => 'XiamenAir']);
$skyteam = Alliance::where('internal_name', 'skyteam')->first();
$skyteamMembers = [
'aerolineas-argentinas',
'aeromexico',
'air-europa',
'air-france',
'china-airlines',
'china-eastern',
'delta',
'garuda-indonesia',
'kenya-airways',
'klm',
'korean-air',
'middle-east-airlines',
'saudia',
'sas',
'tarom',
'vietnam-airlines',
'virgin-atlantic',
'xiamen-air'
];
$star = Alliance::where('internal_name', 'star_alliance')->first();
$starMembers = [
'aegean-airlines',
'air-canada',
'air-china',
'air-india',
'air-new-zealand',
'all-nippon-airways',
'asiana',
'austrian',
'avianca',
'brussels-airlines',
'copa-airlines',
'croatia-airlines',
'egyptair',
'ethiopian-airlines',
'eva-air',
'ita-airways',
'lot-polish-airlines',
'lufthansa',
'shenzhen-airlines',
'singapore-airlines',
'swiss',
'tap-portugal',
'thai-airways-international',
'turkish-airlines',
'united-airlines',
];
$oneworld = Alliance::where('internal_name', 'oneworld')->first();
$oneworldMembers = [
'alaska-airlines',
'american-airlines',
'british-airways',
'cathay-pacific',
'fiji-airways',
'finnair',
'hawaiian-airlines',
'iberia',
'japan-airlines',
'malaysia-airlines',
'oman-air',
'qantas',
'qatar-airways',
'royal-air-maroc',
'royal-jordanian',
'srilankan'
];
$vanilla = Alliance::where('internal_name', 'vanilla_alliance')->first();
$vanillaMembers = [
'air-austral',
'air-madagascar',
'air-seychelles',
'air-mauritius'
];
Airline::whereIn('internal_name', $skyteamMembers)->update(['alliance_id' => $skyteam->id]);
Airline::whereIn('internal_name', $starMembers)->update(['alliance_id' => $star->id]);
Airline::whereIn('internal_name', $oneworldMembers)->update(['alliance_id' => $oneworld->id]);
Airline::whereIn('internal_name', $vanillaMembers)->update(['alliance_id' => $vanilla->id]);
}
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';
$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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => 10,
'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,
'threshold' => 100,
'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,
'threshold' => 500,
'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,
'threshold' => 1000,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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.',
'threshold' => 9,
'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,
'threshold' => 10,
'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,
'threshold' => null,
'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,
'threshold' => null,
'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,
'threshold' => null,
'progressive' => false,
'difficulty_description' => null,
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $moderate,
],
[
'internal_name' => 'aircraft.twin_engine',
'name' => 'Twinsies',
'short_description' => 'Fly on a twin-engine aircraft.',
'long_description' => '',
'icon' => $icon,
'threshold' => null,
'progressive' => false,
'difficulty_description' => 'Most planes in service today meet this criteria!',
'achievement_category_id' => $aircraft,
'achievement_difficulty_id'=> $easy,
],
[
'internal_name' => 'aircraft.tri_engine',
'name' => 'Triple Threat',
'short_description' => 'Fly on a tri-engine aircraft.',
'long_description' => '',
'icon' => $icon,
'threshold' => null,
'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 a scheduled flight on an aircraft from a manufacturer other than Boeing or Airbus.',
'long_description' => '',
'icon' => $icon,
'threshold' => null,
'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,
'threshold' => 18,
'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,
'threshold' => 16,
'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,
'threshold' => 26,
'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,
'threshold' => 4,
'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,
'threshold' => 26,
'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,
'threshold' => 26,
'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,
'threshold' => 6,
'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,
'threshold' => 7,
'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,
'threshold' => 21,
'difficulty_description' => 'There are 21 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,
'threshold' => 36,
'difficulty_description' => "36 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');
}
};