Add storage configuration, localization updates, and feed generation

- Added `publicDisk` configuration to `filesystems.php`.
- Expanded locale translations in `es.json` and `de.json`.
- Implemented RSS, Atom, and JSON feed views.
- Added `feed.php` configuration for feed generation.
- Introduced `ImageController` for image handling.
- Updated application routing to include `api.php`.
This commit is contained in:
HolgerHatGarKeineNode
2025-11-21 16:23:55 +01:00
parent d12ea30d5e
commit efe44cf344
31 changed files with 2493 additions and 208 deletions

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\City;
use App\Models\Lecturer;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class CityController extends Controller
{
/**
* Display a listing of the resource.
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
return City::query()
->with(['country:id,name'])
->select('id', 'name','country_id')
->orderBy('name')
->when(
$request->search,
fn(Builder $query) => $query
->where('name', 'ilike', "%{$request->search}%")
)
->when(
$request->exists('selected'),
fn(Builder $query) => $query->whereIn('id',
$request->input('selected', [])),
fn(Builder $query) => $query->limit(10)
)
->get();
}
/**
* Store a newly created resource in storage.
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
* @return \Illuminate\Http\Response
*/
public function show(Lecturer $lecturer)
{
//
}
/**
* Update the specified resource in storage.
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Lecturer $lecturer)
{
//
}
/**
* Remove the specified resource from storage.
* @return \Illuminate\Http\Response
*/
public function destroy(Lecturer $lecturer)
{
//
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Country;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class CountryController extends Controller
{
public function index(Request $request)
{
return Country::query()
->select('id', 'name', 'code')
->orderBy('name')
->when(
$request->search,
fn(Builder $query)
=> $query
->where('name', 'ilike', "%{$request->search}%")
->orWhere('code', 'ilike', "%{$request->search}%"),
)
->when(
$request->exists('selected'),
fn(Builder $query)
=> $query
->whereIn('code', $request->input('selected', []))
->orWhereIn('id',
$request->input('selected', [])),
fn(Builder $query) => $query->limit(10),
)
->get()
->map(function (Country $country) {
$country->flag = asset('vendor/blade-country-flags/4x3-'.$country->code.'.svg');
return $country;
});
}
/**
* Store a newly created resource in storage.
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
* @return \Illuminate\Http\Response
*/
public function show(Country $country)
{
//
}
/**
* Update the specified resource in storage.
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Country $country)
{
//
}
/**
* Remove the specified resource from storage.
* @return \Illuminate\Http\Response
*/
public function destroy(Country $country)
{
//
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Course;
use App\Models\Lecturer;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class CourseController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
return Course::query()
->select('id', 'name', )
->orderBy('name')
->when($request->has('user_id'),
fn(Builder $query) => $query->where('created_by', $request->user_id))
->when(
$request->search,
fn (Builder $query) => $query
->where('name', 'ilike', "%{$request->search}%")
)
->when(
$request->exists('selected'),
fn (Builder $query) => $query->whereIn('id',
$request->input('selected', [])),
fn (Builder $query) => $query->limit(10)
)
->get()
->map(function (Course $course) {
$course->image = $course->getFirstMediaUrl('logo',
'thumb');
return $course;
});
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Course $course)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Course $course)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Course $course)
{
//
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\EmailCampaign;
class EmailCampaignController extends Controller
{
public function __invoke()
{
return EmailCampaign::query()->get();
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\EmailTexts;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class EmailCampaignGeneratorController extends Controller
{
public function __construct(public $model = 'openai/gpt-4', public $maxTokens = 8191)
{
}
public function __invoke(Request $request)
{
$campaignId = $request->get('id');
$md5 = $request->get('md5');
$campaign = \App\Models\EmailCampaign::query()->find($campaignId);
$subject = $this->generateSubject($campaign);
//check if subject exists in database
$subjectExists = EmailTexts::query()->where('subject', $subject)->exists();
// loop until subject is unique
while ($subjectExists) {
$subject = $this->generateSubject($campaign);
$subjectExists = EmailTexts::query()->where('subject', $subject)->exists();
}
$text = $this->generateText($campaign);
$emailText = EmailTexts::query()->create([
'email_campaign_id' => $campaign->id,
'sender_md5' => $md5,
'subject' => $subject,
'text' => $text,
]);
$emailText->load('emailCampaign');
return $emailText;
}
public function generateSubject(\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder|array|null $campaign): string
{
$result = Http::timeout(120)->withHeaders([
'Authorization' => 'Bearer ' . config('openai.api_key'),
'HTTP-Referer' => 'http://localhost',
])->post('https://openrouter.ai/api/v1/chat/completions', [
'model' => $this->model,
'max_tokens' => 50,
'temperature' => 1,
'messages' => [
['role' => 'user', 'content' => $campaign->subject_prompt],
],
]);
if ($result->failed()) {
Log::error($result->json());
abort(500, 'OpenAI API failed');
}
return $result->json()['choices'][0]['message']['content'];
}
public function generateText(\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder|array|null $campaign): mixed
{
$result = Http::timeout(120)->withHeaders([
'Authorization' => 'Bearer ' . config('openai.api_key'),
'HTTP-Referer' => 'http://localhost',
])->post('https://openrouter.ai/api/v1/chat/completions', [
'model' => $this->model,
'max_tokens' => $this->maxTokens,
'temperature' => 1,
'messages' => [
['role' => 'user', 'content' => $campaign->text_prompt],
],
]);
if ($result->failed()) {
Log::error($result->json());
abort(500, 'OpenAI API failed');
}
return $result->json()['choices'][0]['message']['content'];
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use JoeDixon\Translation\Language;
use JoeDixon\Translation\Translation;
class LanguageController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): JsonResponse
{
$array = Language::query()
->select('id', 'name', 'language')
->orderBy('name')
->when(
$request->search,
fn(Builder $query)
=> $query
->where('name', 'ilike', "%{$request->search}%")
->orWhere('language', 'ilike', "%{$request->search}%"),
)
->when(
$request->exists('selected'),
fn(Builder $query) => $query->whereIn('language', $request->input('selected', [])),
fn(Builder $query) => $query->limit(10),
)
->get()
->map(function ($language) {
$language->translatedCount = Translation::query()
->where('language_id', $language['id'])
->whereNotNull('value')
->where('value', '<>', '')
->count();
$language->toTranslate = Translation::query()
->where('language_id', $language['id'])
->count();
return $language;
})
->toArray();
foreach ($array as $key => $item) {
$translated = $item['translatedCount'] > 0 ? $item['translatedCount'] : 1;
$itemToTranslate = $item['toTranslate'] > 0 ? $item['toTranslate'] : 1;
$array[$key]['name'] = empty($item['name']) ? $item['language'] : $item['name'];
$array[$key]['description'] = $item['language'] === 'en'
? '100% translated'
: round($translated / $itemToTranslate * 100).'% translated';
}
return response()->json($array);
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param $language
* @return \Illuminate\Http\Response
*/
public function show(Language $language)
{
//
}
/**
* Update the specified resource in storage.
*
* @param $language
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Language $language)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param $language
* @return \Illuminate\Http\Response
*/
public function destroy(Language $language)
{
//
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Lecturer;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class LecturerController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
return Lecturer::query()
->select('id', 'name', )
->orderBy('name')
// ->when($request->has('user_id'),
// fn(Builder $query) => $query->where('created_by', $request->user_id))
->when(
$request->search,
fn (Builder $query) => $query
->where('name', 'ilike', "%{$request->search}%")
)
->when(
$request->exists('selected'),
fn (Builder $query) => $query->whereIn('id',
$request->input('selected', [])),
fn (Builder $query) => $query->limit(10)
)
->get()
->map(function (Lecturer $lecturer) {
$lecturer->image = $lecturer->getFirstMediaUrl('avatar',
'thumb');
return $lecturer;
});
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @return \Illuminate\Http\Response
*/
public function show(Lecturer $lecturer)
{
//
}
/**
* Update the specified resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Lecturer $lecturer)
{
//
}
/**
* Remove the specified resource from storage.
*
* @return \Illuminate\Http\Response
*/
public function destroy(Lecturer $lecturer)
{
//
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\meetup;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class MeetupController extends Controller
{
public function index(Request $request)
{
$myMeetupIds = User::query()->find($request->input('user_id'))->meetups->pluck('id');
return Meetup::query()
->select('id', 'name', 'city_id', 'slug')
->with([
'city.country',
])
->whereIn('id', $myMeetupIds->toArray())
->orderBy('name')
->when(
$request->search,
fn(Builder $query)
=> $query
->where('name', 'like', "%{$request->search}%")
->orWhereHas('city',
fn(Builder $query) => $query->where('cities.name', 'ilike', "%{$request->search}%")),
)
->when(
$request->exists('selected'),
fn(Builder $query) => $query->whereIn('id', $request->input('selected', [])),
fn(Builder $query) => $query->limit(10),
)
->get()
->map(function (Meetup $meetup) {
$meetup->profile_image = $meetup->getFirstMediaUrl('logo', 'thumb');
return $meetup;
});
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @return \Illuminate\Http\Response
*/
public function show(meetup $meetup)
{
//
}
/**
* Update the specified resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function update(Request $request, meetup $meetup)
{
//
}
/**
* Remove the specified resource from storage.
*
* @return \Illuminate\Http\Response
*/
public function destroy(meetup $meetup)
{
//
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Lecturer;
use App\Models\Venue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class VenueController extends Controller
{
/**
* Display a listing of the resource.
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
return Venue::query()
->with(['city:id,name,country_id', 'city.country:id,name,code'])
->select('id', 'name', 'city_id')
->orderBy('name')
->when(
$request->search,
fn(Builder $query) => $query
->where('name', 'ilike', "%{$request->search}%")
)
->when(
$request->exists('selected'),
fn(Builder $query) => $query->whereIn('id',
$request->input('selected', [])),
fn(Builder $query) => $query->limit(10)
)
->get()
->map(function (Venue $venue) {
$venue->flag = asset('vendor/blade-country-flags/4x3-' . $venue->city->country->code . '.svg');
$venue->description = $venue->city->name . ', ' . $venue->street;
return $venue;
});
}
/**
* Store a newly created resource in storage.
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
* @return \Illuminate\Http\Response
*/
public function show(Lecturer $lecturer)
{
//
}
/**
* Update the specified resource in storage.
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Lecturer $lecturer)
{
//
}
/**
* Remove the specified resource from storage.
* @return \Illuminate\Http\Response
*/
public function destroy(Lecturer $lecturer)
{
//
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use League\Flysystem\FilesystemOperator;
use League\Glide\Filesystem\FileNotFoundException;
use League\Glide\Responses\ResponseFactoryInterface;
class ImageController extends Controller
{
public function __invoke(Request $request, $path)
{
$source = new \League\Flysystem\Filesystem(
new \League\Flysystem\Local\LocalFilesystemAdapter(storage_path('app'))
);
$cache = new \League\Flysystem\Filesystem(
new \League\Flysystem\Local\LocalFilesystemAdapter(storage_path('app/private/.cache'))
);
// Set image manager
$imageManager = new \Intervention\Image\ImageManager(
new \Intervention\Image\Drivers\Gd\Driver()
);
// Set manipulators
$manipulators = [
new \League\Glide\Manipulators\Orientation(),
new \League\Glide\Manipulators\Crop(),
new \League\Glide\Manipulators\Size(2000*2000),
new \League\Glide\Manipulators\Brightness(),
new \League\Glide\Manipulators\Contrast(),
new \League\Glide\Manipulators\Gamma(),
new \League\Glide\Manipulators\Sharpen(),
new \League\Glide\Manipulators\Filter(),
new \League\Glide\Manipulators\Blur(),
new \League\Glide\Manipulators\Pixelate(),
new \League\Glide\Manipulators\Background(),
new \League\Glide\Manipulators\Border(),
];
// Set API
$api = new \League\Glide\Api\Api($imageManager, $manipulators);
// Setup Glide server
$server = new \League\Glide\Server(
$source,
$cache,
$api,
);
// Set custom response factory
$server->setResponseFactory(new class implements ResponseFactoryInterface {
public function create(FilesystemOperator $cache, string $path)
{
$stream = $cache->readStream($path);
return new Response(
stream_get_contents($stream),
200,
[
'Content-Type' => $cache->mimeType($path),
'Content-Length' => $cache->fileSize($path),
'Cache-Control' => 'public, max-age=31536000',
],
);
}
});
try {
return $server->getImageResponse($path, $request->all());
} catch (FileNotFoundException $exception) {
abort(404);
}
}
}

View File

@@ -9,6 +9,7 @@ use Illuminate\Http\UploadedFile;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Laravel\Sanctum\HasApiTokens;
use ParagonIE\CipherSweet\BlindIndex;
use ParagonIE\CipherSweet\EncryptedRow;
use ParagonIE\CipherSweet\JsonFieldMap;
@@ -22,6 +23,7 @@ class User extends Authenticatable implements CipherSweetEncrypted
use HasFactory;
use Notifiable;
use HasRoles;
use HasApiTokens;
protected $guarded = [];

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ModelCreatedNotification extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
* @return void
*/
public function __construct(public $model, public string $resource)
{
//
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*/
public function via($notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*/
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('New model created: '.get_class($this->model))
->line('Model: '.get_class($this->model))
->action('Show', url('/nova/resources/'.$this->resource))
->line(' ')
->line(' ')
->line(' ')
->line($this->model->toJson());
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*/
public function toArray($notifiable): array
{
return [
//
];
}
}

46
app/Support/Carbon.php Normal file
View File

@@ -0,0 +1,46 @@
<?php
namespace App\Support;
use Carbon\CarbonImmutable;
class Carbon extends CarbonImmutable
{
public function asDate(): string
{
$dt = $this->timezone(config('app.user-timezone'))->locale('de');
return str($dt->day)->padLeft(2, '0').'. '.$dt->monthName.' '.$dt->year;
}
public function asTime(): string
{
return $this->timezone(config('app.user-timezone'))->locale('de')
->format('H:i');
}
public function asDayNameAndMonthName(): string
{
$dt = $this->timezone(config('app.user-timezone'))->locale('de');
return sprintf('%s, %s. week of %s [%s]',
$dt->dayName,
$dt->weekNumberInMonth,
$dt->monthName,
$dt->timezoneAbbreviatedName
);
}
public function asDateTime(): string
{
$dt = $this->timezone(config('app.user-timezone'))->locale('de');
return sprintf('%s.%s.%s %s (%s)',
str($dt->day)->padLeft(2, '0'),
str($dt->month)->padLeft(2, '0'),
$dt->year,
$dt->format('H:i'),
$dt->timezoneAbbreviatedName
);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Support;
use Spatie\Feed\FeedItem;
class CustomFeedItem extends FeedItem
{
protected string $content;
public function content(string $content): self
{
$this->content = $content;
return $this;
}
}

View File

@@ -7,6 +7,7 @@ use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)

View File

@@ -12,16 +12,22 @@
"php": "^8.2",
"akuechler/laravel-geoly": "^1.0",
"druc/laravel-langscanner": "^2.3",
"ezadr/lnurl-php": "^1.0",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.10.1",
"league/glide": "^3.0",
"livewire/flux": "^2.2",
"livewire/flux-pro": "^2.2",
"livewire/volt": "^1.7.0",
"outhebox/blade-flags": "^1.5",
"simplesoftwareio/simple-qrcode": "^4.2",
"spatie/icalendar-generator": "^3.1",
"spatie/laravel-ciphersweet": "^1.7",
"spatie/laravel-feed": "^4.4",
"spatie/laravel-markdown": "^2.7",
"spatie/laravel-medialibrary": "^11.13",
"spatie/laravel-model-status": "^1.18",
"spatie/laravel-permission": "^6.20",
"spatie/laravel-sluggable": "^3.7",
"spatie/laravel-tags": "^4.10",

854
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e2d34fa17a2f68c2fc315cab9b402b42",
"content-hash": "71cfd7a3c92c2083fc928354e95d456b",
"packages": [
{
"name": "akuechler/laravel-geoly",
@@ -64,6 +64,106 @@
},
"time": "2021-04-20T07:17:32+00:00"
},
{
"name": "bacon/bacon-qr-code",
"version": "2.0.8",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
"shasum": ""
},
"require": {
"dasprid/enum": "^1.0.3",
"ext-iconv": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phly/keep-a-changelog": "^2.1",
"phpunit/phpunit": "^7 | ^8 | ^9",
"spatie/phpunit-snapshot-assertions": "^4.2.9",
"squizlabs/php_codesniffer": "^3.4"
},
"suggest": {
"ext-imagick": "to generate QR code images"
},
"type": "library",
"autoload": {
"psr-4": {
"BaconQrCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "BaconQrCode is a QR code generator for PHP.",
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
},
"time": "2022-12-07T17:46:57+00:00"
},
{
"name": "bitwasp/bech32",
"version": "v0.0.1",
"source": {
"type": "git",
"url": "https://github.com/Bit-Wasp/bech32.git",
"reference": "e1ea58c848a4ec59d81b697b3dfe9cc99968d0e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Bit-Wasp/bech32/zipball/e1ea58c848a4ec59d81b697b3dfe9cc99968d0e7",
"reference": "e1ea58c848a4ec59d81b697b3dfe9cc99968d0e7",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "^5.4.0",
"squizlabs/php_codesniffer": "^2.0.0"
},
"type": "library",
"autoload": {
"files": [
"src/bech32.php"
],
"psr-4": {
"BitWasp\\Bech32\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Unlicense"
],
"authors": [
{
"name": "Thomas Kerin",
"homepage": "https://thomaskerin.io",
"role": "Author"
}
],
"description": "Pure (no dependencies) implementation of bech32",
"homepage": "https://github.com/bit-wasp/bech32",
"support": {
"issues": "https://github.com/Bit-Wasp/bech32/issues",
"source": "https://github.com/Bit-Wasp/bech32/tree/more-tests"
},
"time": "2018-02-05T22:23:47+00:00"
},
{
"name": "blade-ui-kit/blade-icons",
"version": "1.8.0",
@@ -405,6 +505,56 @@
],
"time": "2025-08-20T19:15:30+00:00"
},
{
"name": "dasprid/enum",
"version": "1.0.7",
"source": {
"type": "git",
"url": "https://github.com/DASPRiD/Enum.git",
"reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
"reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
"shasum": ""
},
"require": {
"php": ">=7.1 <9.0"
},
"require-dev": {
"phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "*"
},
"type": "library",
"autoload": {
"psr-4": {
"DASPRiD\\Enum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "PHP 7.1 enum implementation",
"keywords": [
"enum",
"map"
],
"support": {
"issues": "https://github.com/DASPRiD/Enum/issues",
"source": "https://github.com/DASPRiD/Enum/tree/1.0.7"
},
"time": "2025-09-16T12:23:56+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@@ -846,6 +996,56 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "ezadr/lnurl-php",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/eza/lnurl-php.git",
"reference": "7ab78dc33bcde58a71c10130d5b5a49ac3e620f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/eza/lnurl-php/zipball/7ab78dc33bcde58a71c10130d5b5a49ac3e620f7",
"reference": "7ab78dc33bcde58a71c10130d5b5a49ac3e620f7",
"shasum": ""
},
"require": {
"bitwasp/bech32": "v0.0.1",
"php": ">=8.0.15",
"simplito/elliptic-php": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5.20",
"squizlabs/php_codesniffer": "^2.9.2"
},
"type": "library",
"autoload": {
"files": [
"src/lnurl.php"
],
"psr-4": {
"eza\\lnurl\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"DBAD"
],
"authors": [
{
"name": "Enzo Zadrima",
"role": "Author"
}
],
"description": "PHP implementation of LNURL Spec, including LNURL Auth",
"homepage": "https://github.com/eza/lnurl-php",
"support": {
"issues": "https://github.com/eza/lnurl-php/issues",
"source": "https://github.com/eza/lnurl-php/tree/1.0.1"
},
"time": "2022-05-08T12:55:38+00:00"
},
{
"name": "fruitcake/php-cors",
"version": "v1.3.0",
@@ -1390,6 +1590,150 @@
],
"time": "2025-08-22T14:27:06+00:00"
},
{
"name": "intervention/gif",
"version": "4.2.2",
"source": {
"type": "git",
"url": "https://github.com/Intervention/gif.git",
"reference": "5999eac6a39aa760fb803bc809e8909ee67b451a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/gif/zipball/5999eac6a39aa760fb803bc809e8909ee67b451a",
"reference": "5999eac6a39aa760fb803bc809e8909ee67b451a",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
"slevomat/coding-standard": "~8.0",
"squizlabs/php_codesniffer": "^3.8"
},
"type": "library",
"autoload": {
"psr-4": {
"Intervention\\Gif\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@intervention.io",
"homepage": "https://intervention.io/"
}
],
"description": "Native PHP GIF Encoder/Decoder",
"homepage": "https://github.com/intervention/gif",
"keywords": [
"animation",
"gd",
"gif",
"image"
],
"support": {
"issues": "https://github.com/Intervention/gif/issues",
"source": "https://github.com/Intervention/gif/tree/4.2.2"
},
"funding": [
{
"url": "https://paypal.me/interventionio",
"type": "custom"
},
{
"url": "https://github.com/Intervention",
"type": "github"
},
{
"url": "https://ko-fi.com/interventionphp",
"type": "ko_fi"
}
],
"time": "2025-03-29T07:46:21+00:00"
},
{
"name": "intervention/image",
"version": "3.11.4",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "8c49eb21a6d2572532d1bc425964264f3e496846"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/8c49eb21a6d2572532d1bc425964264f3e496846",
"reference": "8c49eb21a6d2572532d1bc425964264f3e496846",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"intervention/gif": "^4.2",
"php": "^8.1"
},
"require-dev": {
"mockery/mockery": "^1.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
"slevomat/coding-standard": "~8.0",
"squizlabs/php_codesniffer": "^3.8"
},
"suggest": {
"ext-exif": "Recommended to be able to read EXIF data properly."
},
"type": "library",
"autoload": {
"psr-4": {
"Intervention\\Image\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@intervention.io",
"homepage": "https://intervention.io/"
}
],
"description": "PHP image manipulation",
"homepage": "https://image.intervention.io/",
"keywords": [
"gd",
"image",
"imagick",
"resize",
"thumbnail",
"watermark"
],
"support": {
"issues": "https://github.com/Intervention/image/issues",
"source": "https://github.com/Intervention/image/tree/3.11.4"
},
"funding": [
{
"url": "https://paypal.me/interventionio",
"type": "custom"
},
{
"url": "https://github.com/Intervention",
"type": "github"
},
{
"url": "https://ko-fi.com/interventionphp",
"type": "ko_fi"
}
],
"time": "2025-07-30T13:13:19+00:00"
},
{
"name": "laravel/framework",
"version": "v12.39.0",
@@ -1668,6 +2012,70 @@
},
"time": "2025-09-19T13:47:56+00:00"
},
{
"name": "laravel/sanctum",
"version": "v4.2.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/console": "^11.0|^12.0",
"illuminate/contracts": "^11.0|^12.0",
"illuminate/database": "^11.0|^12.0",
"illuminate/support": "^11.0|^12.0",
"php": "^8.2",
"symfony/console": "^7.0"
},
"require-dev": {
"mockery/mockery": "^1.6",
"orchestra/testbench": "^9.0|^10.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^11.3"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Sanctum\\SanctumServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Sanctum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
"keywords": [
"auth",
"laravel",
"sanctum"
],
"support": {
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2025-07-09T19:45:24+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.6",
@@ -2116,6 +2524,72 @@
},
"time": "2025-11-10T11:23:37+00:00"
},
{
"name": "league/glide",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/glide.git",
"reference": "7ef6dad6e670261fdd20b25ddc5183d1e40c28a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/glide/zipball/7ef6dad6e670261fdd20b25ddc5183d1e40c28a8",
"reference": "7ef6dad6e670261fdd20b25ddc5183d1e40c28a8",
"shasum": ""
},
"require": {
"intervention/image": "^3.9.1",
"league/flysystem": "^3.0",
"php": "^8.1",
"psr/http-message": "^1.0|^2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.48",
"mockery/mockery": "^1.6",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^10.5 || ^11.0"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\Glide\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Reinink",
"email": "jonathan@reinink.ca",
"homepage": "http://reinink.ca"
},
{
"name": "Titouan Galopin",
"email": "galopintitouan@gmail.com",
"homepage": "https://titouangalopin.com"
}
],
"description": "Wonderfully easy on-demand image manipulation library with an HTTP based API.",
"homepage": "http://glide.thephpleague.com",
"keywords": [
"ImageMagick",
"editing",
"gd",
"image",
"imagick",
"league",
"manipulation",
"processing"
],
"support": {
"issues": "https://github.com/thephpleague/glide/issues",
"source": "https://github.com/thephpleague/glide/tree/3.0.1"
},
"time": "2025-03-22T12:30:13+00:00"
},
{
"name": "league/mime-type-detection",
"version": "1.16.0",
@@ -4269,6 +4743,221 @@
},
"time": "2025-09-04T20:59:21+00:00"
},
{
"name": "simplesoftwareio/simple-qrcode",
"version": "4.2.0",
"source": {
"type": "git",
"url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git",
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/916db7948ca6772d54bb617259c768c9cdc8d537",
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537",
"shasum": ""
},
"require": {
"bacon/bacon-qr-code": "^2.0",
"ext-gd": "*",
"php": ">=7.2|^8.0"
},
"require-dev": {
"mockery/mockery": "~1",
"phpunit/phpunit": "~9"
},
"suggest": {
"ext-imagick": "Allows the generation of PNG QrCodes.",
"illuminate/support": "Allows for use within Laravel."
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"QrCode": "SimpleSoftwareIO\\QrCode\\Facades\\QrCode"
},
"providers": [
"SimpleSoftwareIO\\QrCode\\QrCodeServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"SimpleSoftwareIO\\QrCode\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Simple Software LLC",
"email": "support@simplesoftware.io"
}
],
"description": "Simple QrCode is a QR code generator made for Laravel.",
"homepage": "https://www.simplesoftware.io/#/docs/simple-qrcode",
"keywords": [
"Simple",
"generator",
"laravel",
"qrcode",
"wrapper"
],
"support": {
"issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues",
"source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/4.2.0"
},
"time": "2021-02-08T20:43:55+00:00"
},
{
"name": "simplito/bigint-wrapper-php",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/simplito/bigint-wrapper-php.git",
"reference": "cf21ec76d33f103add487b3eadbd9f5033a25930"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/simplito/bigint-wrapper-php/zipball/cf21ec76d33f103add487b3eadbd9f5033a25930",
"reference": "cf21ec76d33f103add487b3eadbd9f5033a25930",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-4": {
"BI\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Simplito Team",
"email": "s.smyczynski@simplito.com",
"homepage": "https://simplito.com"
}
],
"description": "Common interface for php_gmp and php_bcmath modules",
"support": {
"issues": "https://github.com/simplito/bigint-wrapper-php/issues",
"source": "https://github.com/simplito/bigint-wrapper-php/tree/1.0.0"
},
"time": "2018-02-27T12:38:08+00:00"
},
{
"name": "simplito/bn-php",
"version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/simplito/bn-php.git",
"reference": "83446756a81720eacc2ffb87ff97958431451fd6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/simplito/bn-php/zipball/83446756a81720eacc2ffb87ff97958431451fd6",
"reference": "83446756a81720eacc2ffb87ff97958431451fd6",
"shasum": ""
},
"require": {
"simplito/bigint-wrapper-php": "~1.0.0"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"type": "library",
"autoload": {
"psr-4": {
"BN\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Simplito Team",
"email": "s.smyczynski@simplito.com",
"homepage": "https://simplito.com"
}
],
"description": "Big number implementation compatible with bn.js",
"support": {
"issues": "https://github.com/simplito/bn-php/issues",
"source": "https://github.com/simplito/bn-php/tree/1.1.4"
},
"time": "2024-01-10T16:16:59+00:00"
},
{
"name": "simplito/elliptic-php",
"version": "1.0.12",
"source": {
"type": "git",
"url": "https://github.com/simplito/elliptic-php.git",
"reference": "be321666781be2be2c89c79c43ffcac834bc8868"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/simplito/elliptic-php/zipball/be321666781be2be2c89c79c43ffcac834bc8868",
"reference": "be321666781be2be2c89c79c43ffcac834bc8868",
"shasum": ""
},
"require": {
"ext-gmp": "*",
"simplito/bn-php": "~1.1.0"
},
"require-dev": {
"phpbench/phpbench": "@dev",
"phpunit/phpunit": "*"
},
"type": "library",
"autoload": {
"psr-4": {
"Elliptic\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Simplito Team",
"email": "s.smyczynski@simplito.com",
"homepage": "https://simplito.com"
}
],
"description": "Fast elliptic curve cryptography",
"homepage": "https://github.com/simplito/elliptic-php",
"keywords": [
"Curve25519",
"ECDSA",
"Ed25519",
"EdDSA",
"cryptography",
"curve",
"curve25519-weier",
"ecc",
"ecdh",
"elliptic",
"nistp192",
"nistp224",
"nistp256",
"nistp384",
"nistp521",
"secp256k1"
],
"support": {
"issues": "https://github.com/simplito/elliptic-php/issues",
"source": "https://github.com/simplito/elliptic-php/tree/1.0.12"
},
"time": "2024-01-09T14:57:04+00:00"
},
{
"name": "spatie/commonmark-shiki-highlighter",
"version": "2.5.1",
@@ -4661,6 +5350,98 @@
},
"time": "2025-09-18T10:35:25+00:00"
},
{
"name": "spatie/laravel-feed",
"version": "4.4.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-feed.git",
"reference": "4a28d3e75d202dcb8bc63d8653cfa4f398ebc708"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-feed/zipball/4a28d3e75d202dcb8bc63d8653cfa4f398ebc708",
"reference": "4a28d3e75d202dcb8bc63d8653cfa4f398ebc708",
"shasum": ""
},
"require": {
"illuminate/contracts": "^10.0|^11.0|^12.0",
"illuminate/http": "^10.0|^11.0|^12.0",
"illuminate/support": "^10.0|^11.0|^12.0",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.15"
},
"require-dev": {
"orchestra/testbench": "^8.0|^9.0|^10.0",
"pestphp/pest": "^2.0|^3.0",
"spatie/pest-plugin-snapshots": "^2.0",
"spatie/test-time": "^1.2"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\Feed\\FeedServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\Feed\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jolita Grazyte",
"email": "jolita@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Sebastian De Deyne",
"email": "sebastian@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Patrick Organ",
"homepage": "https://github.com/patinthehat",
"role": "Developer"
}
],
"description": "Generate rss feeds",
"homepage": "https://github.com/spatie/laravel-feed",
"keywords": [
"laravel",
"laravel-feed",
"rss",
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-feed/tree/4.4.3"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
},
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2025-11-12T10:10:11+00:00"
},
{
"name": "spatie/laravel-markdown",
"version": "2.7.1",
@@ -4847,6 +5628,77 @@
],
"time": "2025-11-13T11:36:18+00:00"
},
{
"name": "spatie/laravel-model-status",
"version": "1.18.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-model-status.git",
"reference": "16533bb34ef31a100390567e7afb48e862fe2bc9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-model-status/zipball/16533bb34ef31a100390567e7afb48e862fe2bc9",
"reference": "16533bb34ef31a100390567e7afb48e862fe2bc9",
"shasum": ""
},
"require": {
"illuminate/support": "^10.0|^11.0|^12.0",
"php": "^8.2"
},
"require-dev": {
"orchestra/testbench": "^8.0|^9.0|^10.0",
"pestphp/pest": "^2.34|^3.7"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\ModelStatus\\ModelStatusServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\ModelStatus\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Thomas Verhelst",
"email": "tvke91@gmail.com",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "A package to enable assigning statuses to Eloquent Models",
"homepage": "https://github.com/spatie/laravel-model-status",
"keywords": [
"laravel-status",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-model-status/issues",
"source": "https://github.com/spatie/laravel-model-status/tree/1.18.1"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
}
],
"time": "2025-02-21T13:20:00+00:00"
},
{
"name": "spatie/laravel-package-tools",
"version": "1.92.7",

55
config/feed.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
return [
'feeds' => [
'main' => [
/*
* Here you can specify which class and method will return
* the items that should appear in the feed. For example:
* [App\Model::class, 'getAllFeedItems']
*
* You can also pass an argument to that method. Note that their key must be the name of the parameter:
* [App\Model::class, 'getAllFeedItems', 'parameterName' => 'argument']
*/
'items' => [\App\Models\LibraryItem::class, 'getFeedItems'],
/*
* The feed will be available on this url.
*/
'url' => 'feed',
'title' => 'Einundzwanzig - Feed',
'description' => 'Toximalist infotainment for bullish bitcoiners.',
'language' => 'de',
/*
* The image to display for the feed. For Atom feeds, this is displayed as
* a banner/logo; for RSS and JSON feeds, it's displayed as an icon.
* An empty value omits the image attribute from the feed.
*/
'image' => '',
/*
* The format of the feed. Acceptable values are 'rss', 'atom', or 'json'.
*/
'format' => 'rss',
/*
* The view that will render the feed.
*/
'view' => 'feed::rss',
/*
* The mime type to be used in the <link> tag. Set to an empty string to automatically
* determine the correct value.
*/
'type' => '',
/*
* The content type for the feed response. Set to an empty string to automatically
* determine the correct value.
*/
'contentType' => '',
],
],
];

View File

@@ -47,6 +47,12 @@ return [
'report' => false,
],
'publicDisk' => [
'driver' => 'local',
'root' => public_path(),
'throw' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),

18
config/model-status.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
return [
/*
* The class name of the status model that holds all statuses.
*
* The model must be or extend `Spatie\ModelStatus\Status`.
*/
'status_model' => Spatie\ModelStatus\Status::class,
/*
* The name of the column which holds the ID of the model related to the statuses.
*
* You can change this value if you have set a different name in the migration for the statuses table.
*/
'model_primary_key_attribute' => 'model_id',
];

View File

@@ -9,6 +9,7 @@
"Abbrechen": "",
"Absagen": "",
"Aktionen": "",
"Aktiv": "",
"Aktualisiert am": "",
"All rights reserved.": "Alle Rechte vorbehalten.",
"Alle Meetups anzeigen": "",
@@ -16,7 +17,10 @@
"App": "",
"Appearance": "Darstellung",
"Are you sure you want to delete your account?": "Möchten Sie Ihr Konto wirklich löschen?",
"Auf Karte sichtbar": "",
"aus deinen Meetups entfernen?": "",
"Ausführliche Beschreibung des Kurses": "",
"Ausführliche Beschreibung und Biografie": "",
"Authentication Code": "Authentifizierungscode",
"Back": "Zurück",
"Bearbeiten": "",
@@ -24,6 +28,7 @@
"Beschreibung": "",
"Bist du sicher, dass du dieses Event löschen möchtest?": "",
"Bitcoin Meetups": "",
"Breitengrad": "",
"Cancel": "Abbrechen",
"Click here to re-send the verification email.": "Klicken Sie hier, um eine neue Verifizierungs-E-Mail zu erhalten.",
"Close": "Schließen",
@@ -40,13 +45,25 @@
"Dein Name": "",
"Delete account": "Konto löschen",
"Delete your account and all of its resources": "Löschen Sie Ihr Konto und alle zugehörigen Ressourcen",
"Der Anzeigename für diesen Kurs": "",
"Der Anzeigename für dieses Meetup": "",
"Der Dozent, der diesen Kurs leitet": "",
"Details über das Event": "",
"Details/Anmelden": "",
"Die nächstgrößte Stadt oder Ort": "",
"Disable 2FA": "2FA deaktivieren",
"Disabled": "Deaktiviert",
"Documentation": "Dokumentation",
"Don't have an account?": "Sie haben noch kein Konto?",
"Dozent": "",
"Dozent aktualisieren": "",
"Dozent auswählen": "",
"Dozent bearbeiten": "",
"Dozent erfolgreich aktualisiert!": "",
"Dozent erfolgreich erstellt!": "",
"Dozenten": "",
"Dozenten anlegen": "",
"Dozenten erstellen": "",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "",
"Du kannst es jederzeit wieder hinzufügen.": "",
"Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.": "Jeder Wiederherstellungscode kann einmal für den Zugriff auf Ihr Konto verwendet werden und wird nach der Verwendung gelöscht. Wenn Sie weitere Codes benötigen, klicken Sie oben auf „Codes neu generieren“.",
@@ -70,6 +87,8 @@
"Environment file already exists.": "Umgebungsdatei ist bereits vorhanden.",
"Environment file not found.": "Umgebungsdatei nicht gefunden.",
"errors": "Fehler",
"Ersteller des Dozenten": "",
"Ersteller des Kurses": "",
"Ersteller des Meetups": "",
"Erstellt am": "",
"Erstellt von": "",
@@ -87,6 +106,7 @@
"Forgot password": "Passwort vergessen",
"Forgot your password?": "Passwort vergessen?",
"Full name": "Vollständiger Name",
"Füge eine neue Stadt zur Datenbank hinzu.": "",
"Gemeinschaft": "",
"Gemeinschafts- oder Organisationsname": "",
"Go to page :page": "Gehe zur Seite :page",
@@ -98,24 +118,42 @@
"If you did not create an account, no further action is required.": "Wenn Sie kein Konto erstellt haben, sind keine weiteren Handlungen nötig.",
"If you did not request a password reset, no further action is required.": "Wenn Sie kein Zurücksetzen des Passworts beantragt haben, sind keine weiteren Handlungen nötig.",
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Sollten Sie Schwierigkeiten haben, die Schaltfläche \":actionText\" zu klicken, kopieren Sie den nachfolgenden Link\n in Ihre Adresszeile des Browsers.",
"Inaktiv": "",
"Invalid filename.": "Ungültiger Dateiname.",
"Invalid JSON was returned from the route.": "Von der Route wurde ein ungültiger JSON-Code zurückgegeben.",
"Ist dieser Dozent aktiv?": "",
"Kalender-Stream-URL kopieren": "",
"Karte": "",
"Kartenansicht öffnen": "",
"Keine": "",
"Keine bevorstehenden Termine": "",
"Keine Meetups zugeordnet": "",
"Kommende Veranstaltungen": "",
"Kontakt & Links": "",
"Kurs aktualisieren": "",
"Kurs bearbeiten": "",
"Kurs erfolgreich aktualisiert!": "",
"Kurs erfolgreich erstellt!": "",
"Kurs erstellen": "",
"Kurse": "",
"Kurze Berufsbezeichnung oder Rolle": "",
"Kurze Beschreibung des Meetups": "",
"Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)": "",
"Land": "",
"Land auswählen": "",
"length": "Länge",
"Letzte Änderungszeit": "",
"Light": "Hell",
"Lightning Adresse": "",
"Lightning Node ID": "",
"Lightning-Adresse für Zahlungen": "",
"Link": "",
"Link zu weiteren Informationen": "",
"Link zur Telegram-Gruppe oder zum Kanal": "",
"Links": "",
"Links & Soziale Medien": "",
"LNURL": "",
"LNURL für Lightning-Zahlungen": "",
"Location": "Standort",
"Log in": "Anmelden",
"log in": "anmelden",
@@ -127,6 +165,7 @@
"login using a recovery code": "Mit einem Wiederherstellungscode anmelden",
"login using an authentication code": "Anmelden mit einem Authentifizierungscode",
"Logout": "Abmelden",
"Längengrad": "",
"Manage your profile and account settings": "Verwalten Sie Ihr Profil und Ihre Kontoeinstellungen",
"Manage your two-factor authentication settings": "Verwalten Sie Ihre Einstellungen für die Zwei-Faktor-Authentifizierung",
"Matrix": "",
@@ -136,6 +175,8 @@
"Meetup bearbeiten": "",
"Meetup entfernen?": "",
"Meetup erfolgreich aktualisiert!": "",
"Meetup erfolgreich erstellt!": "",
"Meetup erstellen": "",
"Meetup hinzufügen...": "",
"Meetup suchen...": "",
"Meetups": "",
@@ -145,9 +186,16 @@
"Möchtest du": "",
"Name": "Name",
"Name eingeben": "",
"Neuen Dozenten erstellen": "",
"Neuen Kurs erstellen": "",
"Neuer Kurs": "",
"Neues Event erstellen": "",
"Neues Meetup erstellen": "",
"New password": "Neues Passwort",
"no location set": "",
"Node ID": "",
"Nostr": "",
"Nostr öffentlicher Schlüssel": "",
"Nostr öffentlicher Schlüssel oder Bezeichner": "",
"Not Found": "Nicht gefunden",
"Nächster Termin": "",
@@ -163,6 +211,9 @@
"Pagination Navigation": "Seiten-Navigation",
"Password": "Passwort",
"Payment Required": "Zahlung erforderlich",
"PayNym": "",
"PayNym für Bitcoin-Zahlungen": "",
"Persönliche Webseite oder Portfolio": "",
"Platform": "Plattform",
"Please click the button below to verify your email address.": "Bitte klicken Sie auf die Schaltfläche, um Ihre E-Mail-Adresse zu bestätigen.",
"Please confirm access to your account by entering one of your emergency recovery codes.": "Bitte bestätigen Sie den Zugriff auf Ihr Konto, indem Sie einen Ihrer Notfall-Wiederherstellungscodes eingeben.",
@@ -194,14 +245,22 @@
"SimpleX": "",
"Simplex": "",
"SimpleX Chat Kontaktinformationen": "",
"Soll dieses Meetup auf der Karte angezeigt werden?": "",
"Stadt": "",
"Stadt auswählen": "",
"Stadt erstellen": "",
"Stadt hinzufügen": "",
"Stadtname": "",
"Standort": "",
"Startzeit": "",
"Status": "",
"Success!": "",
"Suche dein Land...": "",
"Suche nach Dozenten...": "",
"Suche nach Kursen...": "",
"Suche nach Meetups...": "",
"Suche passende Stadt...": "",
"Suche passenden Dozenten...": "",
"System": "System",
"System-generierte ID (nur lesbar)": "",
"Systeminformationen": "",
@@ -227,6 +286,7 @@
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "Die Zwei-Faktor-Authentifizierung ist nun aktiviert. Scannen Sie den QR-Code oder geben Sie den Setup-Schlüssel in Ihrer Authentifizierungs-App ein.",
"Unauthorized": "Nicht autorisiert",
"Unbekannt": "",
"Untertitel": "",
"Update password": "Passwort aktualisieren",
"Update the appearance settings for your account": "Aktualisieren Sie die Darstellungseinstellungen für Ihr Konto",
"Update your account's appearance settings": "Aktualisieren Sie die Darstellungseinstellungen Ihres Kontos",
@@ -236,7 +296,10 @@
"Verify Email Address": "E-Mail-Adresse bestätigen",
"Vielleicht": "",
"View Recovery Codes": "Wiederherstellungscodes anzeigen",
"Vollständiger Name des Dozenten": "",
"Wallpaper": "",
"Wann dieser Dozent erstellt wurde": "",
"Wann dieser Kurs erstellt wurde": "",
"Wann dieses Meetup erstellt wurde": "",
"Wann findet das Event statt?": "",
"Webseite": "",
@@ -249,78 +312,15 @@
"Wähle dein Land...": "",
"You are receiving this email because we received a password reset request for your account.": "Sie erhalten diese E-Mail, weil wir einen Antrag auf eine Zurücksetzung Ihres Passworts bekommen haben.",
"Your email address is unverified.": "Ihre E-Mail-Adresse ist nicht verifiziert.",
"z.B. Berlin": "",
"z.B. Café Mustermann, Hauptstr. 1": "",
"Zahlungsinformationen": "",
"Zoom = STRG+Scroll": "",
"Zurück zum Meetup": "",
"Zusagen": "",
"Zusätzliche Informationen": "",
"Öffnen/RSVP": "",
"Über uns": "",
"no location set": "",
"Kurse": "",
"Dozenten": "",
"Kurs erfolgreich erstellt!": "",
"Neuen Kurs erstellen": "",
"Der Anzeigename für diesen Kurs": "",
"Dozent": "",
"Dozent auswählen": "",
"Suche passenden Dozenten...": "",
"Der Dozent, der diesen Kurs leitet": "",
"Ausführliche Beschreibung des Kurses": "",
"Kurs erstellen": "",
"Kurs erfolgreich aktualisiert!": "",
"Kurs bearbeiten": "",
"Ersteller des Kurses": "",
"Wann dieser Kurs erstellt wurde": "",
"Kurs aktualisieren": "",
"Suche nach Kursen...": "",
"Neuer Kurs": "",
"Über den Kurs": "",
"Über den Dozenten": "",
"Details/Anmelden": "",
"Dozent erfolgreich erstellt!": "",
"Neuen Dozenten erstellen": "",
"Vollständiger Name des Dozenten": "",
"Untertitel": "",
"Kurze Berufsbezeichnung oder Rolle": "",
"Status": "",
"Ist dieser Dozent aktiv?": "",
"Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)": "",
"Ausführliche Beschreibung und Biografie": "",
"Persönliche Webseite oder Portfolio": "",
"Nostr öffentlicher Schlüssel": "",
"Zahlungsinformationen": "",
"Lightning Adresse": "",
"Lightning-Adresse für Zahlungen": "",
"LNURL": "",
"LNURL für Lightning-Zahlungen": "",
"Node ID": "",
"Lightning Node ID": "",
"PayNym": "",
"PayNym für Bitcoin-Zahlungen": "",
"Dozenten erstellen": "",
"Dozent erfolgreich aktualisiert!": "",
"Dozent bearbeiten": "",
"Ersteller des Dozenten": "",
"Wann dieser Dozent erstellt wurde": "",
"Dozent aktualisieren": "",
"Suche nach Dozenten...": "",
"Dozenten anlegen": "",
"Aktiv": "",
"Inaktiv": "",
"Meetup erfolgreich erstellt!": "",
"Neues Meetup erstellen": "",
"Stadt hinzufügen": "",
"Auf Karte sichtbar": "",
"Soll dieses Meetup auf der Karte angezeigt werden?": "",
"Keine": "",
"Meetup erstellen": "",
"Füge eine neue Stadt zur Datenbank hinzu.": "",
"Stadtname": "",
"z.B. Berlin": "",
"Land auswählen": "",
"Breitengrad": "",
"Längengrad": "",
"Stadt erstellen": "",
"Kalender-Stream-URL kopieren": ""
"Über den Kurs": "",
"Über uns": ""
}

View File

@@ -9,6 +9,7 @@
"Abbrechen": "Cancel",
"Absagen": "Cancel",
"Aktionen": "Actions",
"Aktiv": "Active",
"Aktualisiert am": "Updated at",
"All rights reserved.": "All rights reserved.",
"Alle Meetups anzeigen": "Show all meetups",
@@ -16,7 +17,10 @@
"App": "App",
"Appearance": "Appearance",
"Are you sure you want to delete your account?": "Are you sure you want to delete your account?",
"Auf Karte sichtbar": "Visible on map",
"aus deinen Meetups entfernen?": "remove from your meetups?",
"Ausführliche Beschreibung des Kurses": "Detailed description of the course",
"Ausführliche Beschreibung und Biografie": "Detailed description and biography",
"Authentication Code": "Authentication Code",
"Back": "Back",
"Bearbeiten": "Edit",
@@ -24,6 +28,7 @@
"Beschreibung": "Description",
"Bist du sicher, dass du dieses Event löschen möchtest?": "Are you sure you want to delete this event?",
"Bitcoin Meetups": "Bitcoin Meetups",
"Breitengrad": "Latitude",
"Cancel": "Cancel",
"Click here to re-send the verification email.": "Click here to re-send the verification email.",
"Close": "Close",
@@ -40,13 +45,25 @@
"Dein Name": "Your name",
"Delete account": "Delete account",
"Delete your account and all of its resources": "Delete your account and all of its resources",
"Der Anzeigename für diesen Kurs": "The display name for this course",
"Der Anzeigename für dieses Meetup": "The display name for this meetup",
"Der Dozent, der diesen Kurs leitet": "The lecturer who leads this course",
"Details über das Event": "Details about the event",
"Details/Anmelden": "Details/Register",
"Die nächstgrößte Stadt oder Ort": "The nearest major city or location",
"Disable 2FA": "Disable 2FA",
"Disabled": "Disabled",
"Documentation": "Documentation",
"Don't have an account?": "Don't have an account?",
"Dozent": "Lecturer",
"Dozent aktualisieren": "Update lecturer",
"Dozent auswählen": "Select lecturer",
"Dozent bearbeiten": "Edit lecturer",
"Dozent erfolgreich aktualisiert!": "Lecturer successfully updated!",
"Dozent erfolgreich erstellt!": "Lecturer successfully created!",
"Dozenten": "Lecturers",
"Dozenten anlegen": "Create lecturers",
"Dozenten erstellen": "Create lecturer",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "You are not logged in and therefore need to type your name yourself.",
"Du kannst es jederzeit wieder hinzufügen.": "You can add it again anytime.",
"Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.": "Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.",
@@ -70,6 +87,8 @@
"Environment file already exists.": "Environment file already exists.",
"Environment file not found.": "Environment file not found.",
"errors": "errors",
"Ersteller des Dozenten": "Creator of the lecturer",
"Ersteller des Kurses": "Creator of the course",
"Ersteller des Meetups": "Creator of the meetup",
"Erstellt am": "Created at",
"Erstellt von": "Created by",
@@ -87,6 +106,7 @@
"Forgot password": "Forgot password",
"Forgot your password?": "Forgot your password?",
"Full name": "Full name",
"Füge eine neue Stadt zur Datenbank hinzu.": "Add a new city to the database.",
"Gemeinschaft": "Community",
"Gemeinschafts- oder Organisationsname": "Community or organization name",
"Go to page :page": "Go to page :page",
@@ -98,24 +118,42 @@
"If you did not create an account, no further action is required.": "If you did not create an account, no further action is required.",
"If you did not request a password reset, no further action is required.": "If you did not request a password reset, no further action is required.",
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:",
"Inaktiv": "Inactive",
"Invalid filename.": "Invalid filename.",
"Invalid JSON was returned from the route.": "Invalid JSON was returned from the route.",
"Ist dieser Dozent aktiv?": "Is this lecturer active?",
"Kalender-Stream-URL kopieren": "Copy calendar stream URL",
"Karte": "Map",
"Kartenansicht öffnen": "Open map view",
"Keine": "None",
"Keine bevorstehenden Termine": "No upcoming dates",
"Keine Meetups zugeordnet": "No meetups assigned",
"Kommende Veranstaltungen": "Upcoming events",
"Kontakt & Links": "Contact & Links",
"Kurs aktualisieren": "Update course",
"Kurs bearbeiten": "Edit course",
"Kurs erfolgreich aktualisiert!": "Course successfully updated!",
"Kurs erfolgreich erstellt!": "Course successfully created!",
"Kurs erstellen": "Create course",
"Kurse": "Courses",
"Kurze Berufsbezeichnung oder Rolle": "Brief job title or role",
"Kurze Beschreibung des Meetups": "Brief description of the meetup",
"Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)": "Brief introduction (shown on course pages)",
"Land": "Country",
"Land auswählen": "Select country",
"length": "length",
"Letzte Änderungszeit": "Last modification time",
"Light": "Light",
"Lightning Adresse": "Lightning Address",
"Lightning Node ID": "Lightning Node ID",
"Lightning-Adresse für Zahlungen": "Lightning address for payments",
"Link": "Link",
"Link zu weiteren Informationen": "Link to further information",
"Link zur Telegram-Gruppe oder zum Kanal": "Link to Telegram group or channel",
"Links": "Links",
"Links & Soziale Medien": "Links & Social Media",
"LNURL": "LNURL",
"LNURL für Lightning-Zahlungen": "LNURL for Lightning payments",
"Location": "Location",
"Log in": "Log in",
"log in": "log in",
@@ -127,6 +165,7 @@
"login using a recovery code": "login using a recovery code",
"login using an authentication code": "login using an authentication code",
"Logout": "Logout",
"Längengrad": "Longitude",
"Manage your profile and account settings": "Manage your profile and account settings",
"Manage your two-factor authentication settings": "Manage your two-factor authentication settings",
"Matrix": "",
@@ -136,6 +175,8 @@
"Meetup bearbeiten": "Edit meetup",
"Meetup entfernen?": "Remove meetup?",
"Meetup erfolgreich aktualisiert!": "Meetup successfully updated!",
"Meetup erfolgreich erstellt!": "Meetup successfully created!",
"Meetup erstellen": "Create meetup",
"Meetup hinzufügen...": "Add meetup...",
"Meetup suchen...": "Search meetup...",
"Meetups": "Meetups",
@@ -145,9 +186,16 @@
"Möchtest du": "Do you want to",
"Name": "Name",
"Name eingeben": "Enter name",
"Neuen Dozenten erstellen": "Create new lecturer",
"Neuen Kurs erstellen": "Create new course",
"Neuer Kurs": "New course",
"Neues Event erstellen": "Create new event",
"Neues Meetup erstellen": "Create new meetup",
"New password": "New password",
"no location set": "no location set",
"Node ID": "Node ID",
"Nostr": "Nostr",
"Nostr öffentlicher Schlüssel": "Nostr public key",
"Nostr öffentlicher Schlüssel oder Bezeichner": "Nostr public key or identifier",
"Not Found": "Not Found",
"Nächster Termin": "Next date",
@@ -163,6 +211,9 @@
"Pagination Navigation": "Pagination Navigation",
"Password": "Password",
"Payment Required": "Payment Required",
"PayNym": "PayNym",
"PayNym für Bitcoin-Zahlungen": "PayNym for Bitcoin payments",
"Persönliche Webseite oder Portfolio": "Personal website or portfolio",
"Platform": "Platform",
"Please click the button below to verify your email address.": "Please click the button below to verify your email address.",
"Please confirm access to your account by entering one of your emergency recovery codes.": "Please confirm access to your account by entering one of your emergency recovery codes.",
@@ -194,14 +245,22 @@
"SimpleX": "SimpleX",
"Simplex": "",
"SimpleX Chat Kontaktinformationen": "SimpleX Chat contact information",
"Soll dieses Meetup auf der Karte angezeigt werden?": "Should this meetup be shown on the map?",
"Stadt": "City",
"Stadt auswählen": "Select city",
"Stadt erstellen": "Create city",
"Stadt hinzufügen": "Add city",
"Stadtname": "City name",
"Standort": "Location",
"Startzeit": "Start time",
"Status": "Status",
"Success!": "Success!",
"Suche dein Land...": "Search your country...",
"Suche nach Dozenten...": "Search for lecturers...",
"Suche nach Kursen...": "Search for courses...",
"Suche nach Meetups...": "Search for meetups...",
"Suche passende Stadt...": "Search matching city...",
"Suche passenden Dozenten...": "Search matching lecturer...",
"System": "System",
"System-generierte ID (nur lesbar)": "System generated ID (read-only)",
"Systeminformationen": "System information",
@@ -227,6 +286,7 @@
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.",
"Unauthorized": "Unauthorized",
"Unbekannt": "Unknown",
"Untertitel": "Subtitle",
"Update password": "Update password",
"Update the appearance settings for your account": "Update the appearance settings for your account",
"Update your account's appearance settings": "Update your account's appearance settings",
@@ -236,7 +296,10 @@
"Verify Email Address": "Verify Email Address",
"Vielleicht": "Maybe",
"View Recovery Codes": "View Recovery Codes",
"Vollständiger Name des Dozenten": "Full name of the lecturer",
"Wallpaper": "Wallpaper",
"Wann dieser Dozent erstellt wurde": "When this lecturer was created",
"Wann dieser Kurs erstellt wurde": "When this course was created",
"Wann dieses Meetup erstellt wurde": "When this meetup was created",
"Wann findet das Event statt?": "When does the event take place?",
"Webseite": "Website",
@@ -249,78 +312,15 @@
"Wähle dein Land...": "Choose your country...",
"You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account.",
"Your email address is unverified.": "Your email address is unverified.",
"z.B. Berlin": "e.g. Berlin",
"z.B. Café Mustermann, Hauptstr. 1": "e.g. Cafe Smith, Main St 1",
"Zahlungsinformationen": "Payment information",
"Zoom = STRG+Scroll": "Zoom = CTRL+Scroll",
"Zurück zum Meetup": "Back to meetup",
"Zusagen": "Commitments",
"Zusätzliche Informationen": "Additional information",
"Öffnen/RSVP": "Open/RSVP",
"Über uns": "About us",
"no location set": "no location set",
"Kurse": "Courses",
"Dozenten": "Lecturers",
"Kurs erfolgreich erstellt!": "Course successfully created!",
"Neuen Kurs erstellen": "Create new course",
"Der Anzeigename für diesen Kurs": "The display name for this course",
"Dozent": "Lecturer",
"Dozent auswählen": "Select lecturer",
"Suche passenden Dozenten...": "Search matching lecturer...",
"Der Dozent, der diesen Kurs leitet": "The lecturer who leads this course",
"Ausführliche Beschreibung des Kurses": "Detailed description of the course",
"Kurs erstellen": "Create course",
"Kurs erfolgreich aktualisiert!": "Course successfully updated!",
"Kurs bearbeiten": "Edit course",
"Ersteller des Kurses": "Creator of the course",
"Wann dieser Kurs erstellt wurde": "When this course was created",
"Kurs aktualisieren": "Update course",
"Suche nach Kursen...": "Search for courses...",
"Neuer Kurs": "New course",
"Über den Kurs": "About the course",
"Über den Dozenten": "About the lecturer",
"Details/Anmelden": "Details/Register",
"Dozent erfolgreich erstellt!": "Lecturer successfully created!",
"Neuen Dozenten erstellen": "Create new lecturer",
"Vollständiger Name des Dozenten": "Full name of the lecturer",
"Untertitel": "Subtitle",
"Kurze Berufsbezeichnung oder Rolle": "Brief job title or role",
"Status": "Status",
"Ist dieser Dozent aktiv?": "Is this lecturer active?",
"Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)": "Brief introduction (shown on course pages)",
"Ausführliche Beschreibung und Biografie": "Detailed description and biography",
"Persönliche Webseite oder Portfolio": "Personal website or portfolio",
"Nostr öffentlicher Schlüssel": "Nostr public key",
"Zahlungsinformationen": "Payment information",
"Lightning Adresse": "Lightning Address",
"Lightning-Adresse für Zahlungen": "Lightning address for payments",
"LNURL": "LNURL",
"LNURL für Lightning-Zahlungen": "LNURL for Lightning payments",
"Node ID": "Node ID",
"Lightning Node ID": "Lightning Node ID",
"PayNym": "PayNym",
"PayNym für Bitcoin-Zahlungen": "PayNym for Bitcoin payments",
"Dozenten erstellen": "Create lecturer",
"Dozent erfolgreich aktualisiert!": "Lecturer successfully updated!",
"Dozent bearbeiten": "Edit lecturer",
"Ersteller des Dozenten": "Creator of the lecturer",
"Wann dieser Dozent erstellt wurde": "When this lecturer was created",
"Dozent aktualisieren": "Update lecturer",
"Suche nach Dozenten...": "Search for lecturers...",
"Dozenten anlegen": "Create lecturers",
"Aktiv": "Active",
"Inaktiv": "Inactive",
"Meetup erfolgreich erstellt!": "Meetup successfully created!",
"Neues Meetup erstellen": "Create new meetup",
"Stadt hinzufügen": "Add city",
"Auf Karte sichtbar": "Visible on map",
"Soll dieses Meetup auf der Karte angezeigt werden?": "Should this meetup be shown on the map?",
"Keine": "None",
"Meetup erstellen": "Create meetup",
"Füge eine neue Stadt zur Datenbank hinzu.": "Add a new city to the database.",
"Stadtname": "City name",
"z.B. Berlin": "e.g. Berlin",
"Land auswählen": "Select country",
"Breitengrad": "Latitude",
"Längengrad": "Longitude",
"Stadt erstellen": "Create city",
"Kalender-Stream-URL kopieren": "Copy calendar stream URL"
"Über den Kurs": "About the course",
"Über uns": "About us"
}

View File

@@ -9,6 +9,7 @@
"Abbrechen": "Cancelar",
"Absagen": "Cancelar",
"Aktionen": "Acciones",
"Aktiv": "Activo",
"Aktualisiert am": "Actualizado el",
"All rights reserved.": "Todos los derechos reservados.",
"Alle Meetups anzeigen": "Mostrar todos los encuentros",
@@ -16,7 +17,10 @@
"App": "Aplicación",
"Appearance": "Apariencia",
"Are you sure you want to delete your account?": "¿Está seguro que desea eliminar su cuenta?",
"Auf Karte sichtbar": "Visible en el mapa",
"aus deinen Meetups entfernen?": "eliminar de tus encuentros?",
"Ausführliche Beschreibung des Kurses": "Descripción detallada del curso",
"Ausführliche Beschreibung und Biografie": "Descripción detallada y biografía",
"Authentication Code": "Código de autenticación",
"Back": "Atrás",
"Bearbeiten": "Editar",
@@ -24,6 +28,7 @@
"Beschreibung": "Descripción",
"Bist du sicher, dass du dieses Event löschen möchtest?": "¿Estás seguro de que quieres eliminar este evento?",
"Bitcoin Meetups": "Encuentros Bitcoin",
"Breitengrad": "Latitud",
"Cancel": "Cancelar",
"Click here to re-send the verification email.": "Haga clic aquí para reenviar el correo de verificación.",
"Close": "Cerrar",
@@ -40,13 +45,25 @@
"Dein Name": "Tu nombre",
"Delete account": "Eliminar cuenta",
"Delete your account and all of its resources": "Elimine su cuenta y todos sus recursos",
"Der Anzeigename für diesen Kurs": "El nombre para mostrar de este curso",
"Der Anzeigename für dieses Meetup": "El nombre para mostrar de este encuentro",
"Der Dozent, der diesen Kurs leitet": "El profesor que imparte este curso",
"Details über das Event": "Detalles sobre el evento",
"Details/Anmelden": "Detalles/Inscribirse",
"Die nächstgrößte Stadt oder Ort": "La ciudad o lugar más cercano",
"Disable 2FA": "Desactivar 2FA",
"Disabled": "Desactivado",
"Documentation": "Documentación",
"Don't have an account?": "¿No tiene una cuenta?",
"Dozent": "Profesor",
"Dozent aktualisieren": "Actualizar profesor",
"Dozent auswählen": "Seleccionar profesor",
"Dozent bearbeiten": "Editar profesor",
"Dozent erfolgreich aktualisiert!": "¡Profesor actualizado exitosamente!",
"Dozent erfolgreich erstellt!": "¡Profesor creado exitosamente!",
"Dozenten": "Profesores",
"Dozenten anlegen": "Crear profesores",
"Dozenten erstellen": "Crear profesor",
"Du bist nicht eingloggt und musst deshalb den Namen selbst eintippen.": "No has iniciado sesión, por lo que debes escribir el nombre tú mismo.",
"Du kannst es jederzeit wieder hinzufügen.": "Puedes volver a añadirlo en cualquier momento.",
"Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate Codes above.": "Cada código de recuperación se puede usar una vez para acceder a su cuenta y se eliminará después de usarlo. Si necesita más, haga clic arriba en \"Regenerar códigos\".",
@@ -70,6 +87,8 @@
"Environment file already exists.": "El archivo de entorno ya existe.",
"Environment file not found.": "Archivo de entorno no encontrado.",
"errors": "errores",
"Ersteller des Dozenten": "Creador del profesor",
"Ersteller des Kurses": "Creador del curso",
"Ersteller des Meetups": "Creador del encuentro",
"Erstellt am": "Creado el",
"Erstellt von": "Creado por",
@@ -87,6 +106,7 @@
"Forgot password": "Olvido de contraseña",
"Forgot your password?": "¿Olvidó su contraseña?",
"Full name": "Nombre completo",
"Füge eine neue Stadt zur Datenbank hinzu.": "Añade una nueva ciudad a la base de datos.",
"Gemeinschaft": "Comunidad",
"Gemeinschafts- oder Organisationsname": "Nombre de la comunidad u organización",
"Go to page :page": "Ir a la página :page",
@@ -98,23 +118,41 @@
"If you did not create an account, no further action is required.": "Si no ha creado una cuenta, no se requiere ninguna acción adicional.",
"If you did not request a password reset, no further action is required.": "Si no ha solicitado el restablecimiento de contraseña, omita este mensaje de correo electrónico.",
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si está teniendo problemas al hacer clic en el botón \":actionText\", copie y pegue la URL de abajo\nen su navegador web:",
"Inaktiv": "Inactivo",
"Invalid filename.": "Nombre de archivo no válido.",
"Invalid JSON was returned from the route.": "Se devolvió un JSON no válido desde la ruta.",
"Ist dieser Dozent aktiv?": "¿Está activo este profesor?",
"Kalender-Stream-URL kopieren": "Copiar URL del flujo del calendario",
"Karte": "Mapa",
"Kartenansicht öffnen": "Abrir vista de mapa",
"Keine": "Ninguno",
"Keine bevorstehenden Termine": "No hay eventos próximos",
"Keine Meetups zugeordnet": "No hay encuentros asignados",
"Kommende Veranstaltungen": "Próximos eventos",
"Kontakt & Links": "Contacto y enlaces",
"Kurs aktualisieren": "Actualizar curso",
"Kurs bearbeiten": "Editar curso",
"Kurs erfolgreich aktualisiert!": "¡Curso actualizado exitosamente!",
"Kurs erfolgreich erstellt!": "¡Curso creado exitosamente!",
"Kurs erstellen": "Crear curso",
"Kurse": "Cursos",
"Kurze Berufsbezeichnung oder Rolle": "Breve título profesional o rol",
"Kurze Beschreibung des Meetups": "Breve descripción del encuentro",
"Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)": "Breve presentación (se muestra en las páginas del curso)",
"Land": "País",
"Land auswählen": "Seleccionar país",
"Letzte Änderungszeit": "Última hora de modificación",
"Light": "Claro",
"Lightning Adresse": "Dirección Lightning",
"Lightning Node ID": "ID del nodo Lightning",
"Lightning-Adresse für Zahlungen": "Dirección Lightning para pagos",
"Link": "Enlace",
"Link zu weiteren Informationen": "Enlace para más información",
"Link zur Telegram-Gruppe oder zum Kanal": "Enlace al grupo o canal de Telegram",
"Links": "Enlaces",
"Links & Soziale Medien": "Enlaces y redes sociales",
"LNURL": "LNURL",
"LNURL für Lightning-Zahlungen": "LNURL para pagos Lightning",
"Location": "Ubicación",
"Log in": "Iniciar sesión",
"log in": "iniciar sesión",
@@ -126,6 +164,7 @@
"login using a recovery code": "iniciar sesión usando un código de recuperación",
"login using an authentication code": "iniciar sesión usando un código de autenticación",
"Logout": "Finalizar sesión",
"Längengrad": "Longitud",
"Manage your profile and account settings": "Administre su perfil y la configuración de su cuenta",
"Manage your two-factor authentication settings": "Administre su configuración de autenticación de dos factores",
"Matrix": "",
@@ -135,6 +174,8 @@
"Meetup bearbeiten": "Editar encuentro",
"Meetup entfernen?": "¿Eliminar encuentro?",
"Meetup erfolgreich aktualisiert!": "¡Encuentro actualizado con éxito!",
"Meetup erfolgreich erstellt!": "¡Encuentro creado exitosamente!",
"Meetup erstellen": "Crear encuentro",
"Meetup hinzufügen...": "Añadir encuentro...",
"Meetup suchen...": "Buscar encuentro...",
"Meetups": "Encuentros",
@@ -144,9 +185,16 @@
"Möchtest du": "¿Quieres",
"Name": "Nombre",
"Name eingeben": "Introducir nombre",
"Neuen Dozenten erstellen": "Crear nuevo profesor",
"Neuen Kurs erstellen": "Crear nuevo curso",
"Neuer Kurs": "Nuevo curso",
"Neues Event erstellen": "Crear nuevo evento",
"Neues Meetup erstellen": "Crear nuevo encuentro",
"New password": "Nueva contraseña",
"no location set": "sin ubicación establecida",
"Node ID": "ID del nodo",
"Nostr": "Nostr",
"Nostr öffentlicher Schlüssel": "Clave pública de Nostr",
"Nostr öffentlicher Schlüssel oder Bezeichner": "Clave pública o identificador de Nostr",
"Not Found": "No encontrado",
"Nächster Termin": "Próxima cita",
@@ -162,6 +210,9 @@
"Pagination Navigation": "Navegación por los enlaces de paginación",
"Password": "Contraseña",
"Payment Required": "Pago requerido",
"PayNym": "PayNym",
"PayNym für Bitcoin-Zahlungen": "PayNym para pagos Bitcoin",
"Persönliche Webseite oder Portfolio": "Sitio web personal o portafolio",
"Platform": "Plataforma",
"Please click the button below to verify your email address.": "Por favor, haga clic en el botón de abajo para verificar su dirección de correo electrónico.",
"Please confirm access to your account by entering one of your emergency recovery codes.": "Confirme el acceso a su cuenta ingresando uno de sus códigos de recuperación de emergencia.",
@@ -193,14 +244,22 @@
"SimpleX": "SimpleX",
"Simplex": "",
"SimpleX Chat Kontaktinformationen": "Información de contacto de SimpleX Chat",
"Soll dieses Meetup auf der Karte angezeigt werden?": "¿Debe mostrarse este encuentro en el mapa?",
"Stadt": "Ciudad",
"Stadt auswählen": "Seleccionar ciudad",
"Stadt erstellen": "Crear ciudad",
"Stadt hinzufügen": "Añadir ciudad",
"Stadtname": "Nombre de la ciudad",
"Standort": "Ubicación",
"Startzeit": "Hora de inicio",
"Status": "Estado",
"Success!": "¡Éxito!",
"Suche dein Land...": "Busca tu país...",
"Suche nach Dozenten...": "Buscar profesores...",
"Suche nach Kursen...": "Buscar cursos...",
"Suche nach Meetups...": "Buscar encuentros...",
"Suche passende Stadt...": "Buscar ciudad correspondiente...",
"Suche passenden Dozenten...": "Buscar profesor adecuado...",
"System": "Sistema",
"System-generierte ID (nur lesbar)": "ID generado por el sistema (solo lectura)",
"Systeminformationen": "Información del sistema",
@@ -226,6 +285,7 @@
"Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.": "La autenticación de dos factores ya está habilitada. Escanea el código QR o introduce la clave de configuración en tu app de autenticación.",
"Unauthorized": "No autorizado",
"Unbekannt": "Desconocido",
"Untertitel": "Subtítulo",
"Update password": "Actualizar contraseña",
"Update the appearance settings for your account": "Actualice la configuración de apariencia de su cuenta",
"Update your account's appearance settings": "Actualice la configuración de apariencia de su cuenta",
@@ -235,7 +295,10 @@
"Verify Email Address": "Confirme su correo electrónico",
"Vielleicht": "Quizás",
"View Recovery Codes": "Ver códigos de recuperación",
"Vollständiger Name des Dozenten": "Nombre completo del profesor",
"Wallpaper": "Fondo de pantalla",
"Wann dieser Dozent erstellt wurde": "Cuando se creó este profesor",
"Wann dieser Kurs erstellt wurde": "Cuando se creó este curso",
"Wann dieses Meetup erstellt wurde": "Cuando se creó este encuentro",
"Wann findet das Event statt?": "¿Cuándo tendrá lugar el evento?",
"Webseite": "Sitio web",
@@ -248,78 +311,15 @@
"Wähle dein Land...": "Elige tu país...",
"You are receiving this email because we received a password reset request for your account.": "Ha recibido este mensaje porque se solicitó un restablecimiento de contraseña para su cuenta.",
"Your email address is unverified.": "Su dirección de correo electrónico no está verificada.",
"z.B. Berlin": "p.ej. Berlín",
"z.B. Café Mustermann, Hauptstr. 1": "p.ej. Café Ejemplo, Calle Principal 1",
"Zahlungsinformationen": "Información de pago",
"Zoom = STRG+Scroll": "Zoom = CTRL+Scroll",
"Zurück zum Meetup": "Volver al encuentro",
"Zusagen": "Confirmaciones",
"Zusätzliche Informationen": "Información adicional",
"Öffnen/RSVP": "Abrir/RSVP",
"Über uns": "Sobre nosotros",
"no location set": "sin ubicación establecida",
"Kurse": "Cursos",
"Dozenten": "Profesores",
"Kurs erfolgreich erstellt!": "¡Curso creado exitosamente!",
"Neuen Kurs erstellen": "Crear nuevo curso",
"Der Anzeigename für diesen Kurs": "El nombre para mostrar de este curso",
"Dozent": "Profesor",
"Dozent auswählen": "Seleccionar profesor",
"Suche passenden Dozenten...": "Buscar profesor adecuado...",
"Der Dozent, der diesen Kurs leitet": "El profesor que imparte este curso",
"Ausführliche Beschreibung des Kurses": "Descripción detallada del curso",
"Kurs erstellen": "Crear curso",
"Kurs erfolgreich aktualisiert!": "¡Curso actualizado exitosamente!",
"Kurs bearbeiten": "Editar curso",
"Ersteller des Kurses": "Creador del curso",
"Wann dieser Kurs erstellt wurde": "Cuando se creó este curso",
"Kurs aktualisieren": "Actualizar curso",
"Suche nach Kursen...": "Buscar cursos...",
"Neuer Kurs": "Nuevo curso",
"Über den Kurs": "Sobre el curso",
"Über den Dozenten": "Sobre el profesor",
"Details/Anmelden": "Detalles/Inscribirse",
"Dozent erfolgreich erstellt!": "¡Profesor creado exitosamente!",
"Neuen Dozenten erstellen": "Crear nuevo profesor",
"Vollständiger Name des Dozenten": "Nombre completo del profesor",
"Untertitel": "Subtítulo",
"Kurze Berufsbezeichnung oder Rolle": "Breve título profesional o rol",
"Status": "Estado",
"Ist dieser Dozent aktiv?": "¿Está activo este profesor?",
"Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)": "Breve presentación (se muestra en las páginas del curso)",
"Ausführliche Beschreibung und Biografie": "Descripción detallada y biografía",
"Persönliche Webseite oder Portfolio": "Sitio web personal o portafolio",
"Nostr öffentlicher Schlüssel": "Clave pública de Nostr",
"Zahlungsinformationen": "Información de pago",
"Lightning Adresse": "Dirección Lightning",
"Lightning-Adresse für Zahlungen": "Dirección Lightning para pagos",
"LNURL": "LNURL",
"LNURL für Lightning-Zahlungen": "LNURL para pagos Lightning",
"Node ID": "ID del nodo",
"Lightning Node ID": "ID del nodo Lightning",
"PayNym": "PayNym",
"PayNym für Bitcoin-Zahlungen": "PayNym para pagos Bitcoin",
"Dozenten erstellen": "Crear profesor",
"Dozent erfolgreich aktualisiert!": "¡Profesor actualizado exitosamente!",
"Dozent bearbeiten": "Editar profesor",
"Ersteller des Dozenten": "Creador del profesor",
"Wann dieser Dozent erstellt wurde": "Cuando se creó este profesor",
"Dozent aktualisieren": "Actualizar profesor",
"Suche nach Dozenten...": "Buscar profesores...",
"Dozenten anlegen": "Crear profesores",
"Aktiv": "Activo",
"Inaktiv": "Inactivo",
"Meetup erfolgreich erstellt!": "¡Encuentro creado exitosamente!",
"Neues Meetup erstellen": "Crear nuevo encuentro",
"Stadt hinzufügen": "Añadir ciudad",
"Auf Karte sichtbar": "Visible en el mapa",
"Soll dieses Meetup auf der Karte angezeigt werden?": "¿Debe mostrarse este encuentro en el mapa?",
"Keine": "Ninguno",
"Meetup erstellen": "Crear encuentro",
"Füge eine neue Stadt zur Datenbank hinzu.": "Añade una nueva ciudad a la base de datos.",
"Stadtname": "Nombre de la ciudad",
"z.B. Berlin": "p.ej. Berlín",
"Land auswählen": "Seleccionar país",
"Breitengrad": "Latitud",
"Längengrad": "Longitud",
"Stadt erstellen": "Crear ciudad",
"Kalender-Stream-URL kopieren": "Copiar URL del flujo del calendario"
"Über den Kurs": "Sobre el curso",
"Über uns": "Sobre nosotros"
}

View File

@@ -1,17 +1,22 @@
<?php
use App\Models\LoginKey;
use App\Models\User;
use App\Notifications\ModelCreatedNotification;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Attributes\Renderless;
use Livewire\Attributes\Validate;
use Livewire\Volt\Component;
use Flux\Flux;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use eza\lnurl;
new #[Layout('components.layouts.auth')]
class extends Component {
@@ -23,6 +28,30 @@ class extends Component {
public bool $remember = false;
public ?string $k1 = null;
public ?string $url = null;
public ?string $lnurl = null;
public ?string $qrCode = null;
public function mount(): void
{
// Nur beim ersten Mount initialisieren
if ($this->k1 === null) {
$this->k1 = bin2hex(str()->random(32));
if (app()->environment('local')) {
$this->url = 'https://mmy4dp8eab.sharedwithexpose.com/api/lnurl-auth-callback?tag=login&k1='.$this->k1.'&action=login';
} else {
$this->url = url('/api/lnurl-auth-callback?tag=login&k1='.$this->k1.'&action=login');
}
$this->lnurl = lnurl\encodeUrl($this->url);
$this->qrCode = base64_encode(QrCode::format('png')
->size(300)
->merge('/public/android-chrome-192x192.png', .3)
->errorCorrection('H')
->generate($this->lnurl));
}
}
#[On('nostrLoggedIn')]
public function loginListener($pubkey): void
{
@@ -81,6 +110,26 @@ class extends Component {
{
return Str::transliterate(Str::lower($this->email).'|'.request()->ip());
}
public function checkAuth()
{
$loginKey = LoginKey::query()
->where('k1', $this->k1)
->whereDate('created_at', '>=', now()->subMinutes(5))
->first();
if ($loginKey) {
$user = User::find($loginKey->user_id);
\App\Models\User::find(1)
->notify(new ModelCreatedNotification($user, 'users'));
auth()->login($user);
return to_route('dashboard', ['country' => 'de']);
}
return true;
}
}; ?>
<div class="flex min-h-screen" x-data="nostrLogin">
@@ -172,7 +221,43 @@ class extends Component {
{{--<flux:checkbox wire:model="remember" label="Remember me for 30 days" />--}}
<!-- Submit Button -->
<flux:button variant="primary" @click="openNostrLogin" class="w-full cursor-pointer">{{ __('Log in mit Nostr') }}</flux:button>
<flux:button variant="primary" @click="openNostrLogin"
class="w-full cursor-pointer">{{ __('Log in mit Nostr') }}</flux:button>
<div class="text-center text-2xl text-gray-80 dark:text-gray-2000 mt-6">
Login with lightning
</div>
<div class="flex justify-center" wire:key="qrcode">
<a href="lightning:{{ $this->lnurl }}">
<div class="bg-white p-4">
<img src="{{ 'data:image/png;base64, '. $this->qrCode }}" alt="qrcode">
</div>
</a>
</div>
<div class="flex justify-between w-full">
<div x-copy-to-clipboard="'{{ $this->lnurl }}'">
<flux:button icon="clipboard" class="cursor-pointer">
{{ __('Copy') }}
</flux:button>
</div>
<div>
<flux:button
primary
:href="'lightning:'.$this->lnurl"
>
{{ __('Click to connect') }}
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512"
height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"
d="M461.81 53.81a4.4 4.4 0 00-3.3-3.39c-54.38-13.3-180 34.09-248.13 102.17a294.9 294.9 0 00-33.09 39.08c-21-1.9-42-.3-59.88 7.5-50.49 22.2-65.18 80.18-69.28 105.07a9 9 0 009.8 10.4l81.07-8.9a180.29 180.29 0 001.1 18.3 18.15 18.15 0 005.3 11.09l31.39 31.39a18.15 18.15 0 0011.1 5.3 179.91 179.91 0 0018.19 1.1l-8.89 81a9 9 0 0010.39 9.79c24.9-4 83-18.69 105.07-69.17 7.8-17.9 9.4-38.79 7.6-59.69a293.91 293.91 0 0039.19-33.09c68.38-68 115.47-190.86 102.37-247.95zM298.66 213.67a42.7 42.7 0 1160.38 0 42.65 42.65 0 01-60.38 0z"></path>
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"
d="M109.64 352a45.06 45.06 0 00-26.35 12.84C65.67 382.52 64 448 64 448s65.52-1.67 83.15-19.31A44.73 44.73 0 00160 402.32"></path>
</svg>
</flux:button>
</div>
</div>
</div>
<!-- Sign up Link -->
@@ -204,4 +289,6 @@ class extends Component {
</div>
</div>
</div>
<div wire:poll.4s="checkAuth" wire:key="checkAuth"></div>
</div>

View File

@@ -52,6 +52,10 @@ class extends Component {
</x-slot>
{{ __('Kartenansicht öffnen') }}
</flux:button>
<flux:button :href="route('dashboard', ['country' => 'de'])" class="cursor-pointer w-full" icon="arrow-right-start-on-rectangle">
{{ __('Login') }}
</flux:button>
</div>
</div>
</div>

View File

@@ -0,0 +1,48 @@
<?=
/* Using an echo tag here so the `<? ... ?>` won't get parsed as short tags */
'<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL
?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ $meta['language'] }}">
@foreach($meta as $key => $metaItem)
@if($key === 'link')
<{{ $key }} href="{{ url($metaItem) }}" rel="self"></{{ $key }}>
@elseif($key === 'title')
<{{ $key }}>{!! \Spatie\Feed\Helpers\Cdata::out($metaItem) !!}</{{ $key }}>
@elseif($key === 'description')
<subtitle>{{ $metaItem }}</subtitle>
@elseif($key === 'language')
@elseif($key === 'image')
@if(!empty($metaItem))
<logo>{!! $metaItem !!}</logo>
@else
@endif
@else
<{{ $key }}>{{ $metaItem }}</{{ $key }}>
@endif
@endforeach
@foreach($items as $item)
<entry>
<title>{!! \Spatie\Feed\Helpers\Cdata::out($item->title) !!}</title>
<link rel="alternate" href="{{ url($item->link) }}" />
<id>{{ url($item->id) }}</id>
<author>
<name>{!! \Spatie\Feed\Helpers\Cdata::out($item->authorName) !!}</name>
@if(!empty($item->authorEmail))
<email>{!! \Spatie\Feed\Helpers\Cdata::out($item->authorEmail) !!}</email>
@endif
</author>
<summary type="html">
{!! \Spatie\Feed\Helpers\Cdata::out($item->summary) !!}
</summary>
@if($item->__isset('enclosure'))
<link href="{{ url($item->enclosure) }}" length="{{ $item->enclosureLength }}" type="{{ $item->enclosureType }}" />
@endif
@foreach($item->category as $category)
<category term="{{ $category }}" />
@endforeach
<updated>{{ $item->timestamp() }}</updated>
</entry>
@endforeach
</feed>

View File

@@ -0,0 +1,46 @@
{
"version": "https://jsonfeed.org/version/1.1",
"title": "{{ $meta['title'] }}",
@if(!empty($meta['description']))
"description": "{{ $meta['description'] }}",
@endif
"home_page_url": "{{ config('app.url') }}",
"feed_url": "{{ url($meta['link']) }}",
"language": "{{ $meta['language'] }}",
@if(!empty($meta['image']))
"icon": "{{ $meta['image'] }}",
@endif
"authors": [@foreach($items->unique('authorName') as $item){
"name": "{{ $item->authorName }}"
}@if(! $loop->last),@endif
@endforeach
],
"items": [@foreach($items as $item){
"id": "{{ url($item->id) }}",
"title": {!! json_encode($item->title) !!},
"url": "{{ url($item->link) }}",
"content_html": {!! json_encode($item->summary) !!},
"summary": {!! json_encode($item->summary) !!},
"date_published": "{{ $item->timestamp() }}",
"date_modified": "{{ $item->timestamp() }}",
"authors": [{ "name": {!! json_encode($item->authorName) !!} }],
@if($item->__isset('image'))
"image": "{{ url($item->image) }}",
@endif
@if($item->__isset('enclosure'))
"attachments": [
{
"url": "{{ url($item->enclosure) }}",
"mime_type": "{{ $item->enclosureType }}",
"size_in_bytes": {{ $item->enclosureLength }}
}
],
@endif
"tags": [ {!! implode(',', array_map(fn($c) => '"'.$c.'"', $item->category)) !!} ]
}@if(! $loop->last),
@endif
@endforeach
]
}

View File

@@ -0,0 +1,3 @@
@foreach($feeds as $name => $feed)
<link rel="alternate" type="{{ \Spatie\Feed\Helpers\FeedContentType::forLink($feed['format'] ?? 'atom') }}" href="{{ route("feeds.{$name}") }}" title="{{ $feed['title'] }}">
@endforeach

View File

@@ -0,0 +1,42 @@
<?=
/* Using an echo tag here so the `<? ... ?>` won't get parsed as short tags */
'<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL
?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<atom:link href="{{ url($meta['link']) }}" rel="self" type="application/rss+xml"/>
<title>{!! \Spatie\Feed\Helpers\Cdata::out($meta['title'] ) !!}</title>
<link>{!! \Spatie\Feed\Helpers\Cdata::out(url($meta['link']) ) !!}</link>
@if(!empty($meta['image']))
<image>
<url>{{ $meta['image'] }}</url>
<title>{!! \Spatie\Feed\Helpers\Cdata::out($meta['title'] ) !!}</title>
<link>{!! \Spatie\Feed\Helpers\Cdata::out(url($meta['link']) ) !!}</link>
</image>
@endif
<description>{!! \Spatie\Feed\Helpers\Cdata::out($meta['description'] ) !!}</description>
<language>{{ $meta['language'] }}</language>
<pubDate>{{ $meta['updated'] }}</pubDate>
@foreach($items as $item)
<item>
<title>{!! \Spatie\Feed\Helpers\Cdata::out($item->title) !!}</title>
<link>{{ url($item->link) }}</link>
<description>{!! \Spatie\Feed\Helpers\Cdata::out($item->summary) !!}</description>
<author>{!! \Spatie\Feed\Helpers\Cdata::out($item->authorName.(empty($item->authorEmail)?'':' <'.$item->authorEmail.'>')) !!}</author>
<guid isPermaLink="true">{{ url($item->id) }}</guid>
<pubDate>{{ $item->timestamp() }}</pubDate>
<content:encoded>
{{ app(Spatie\LaravelMarkdown\MarkdownRenderer::class)->toHtml($item->content) }}
</content:encoded>
@if($item->__isset('enclosure'))
<enclosure url="{{ url($item->enclosure) }}" length="{{ url($item->enclosureLength) }}" type="{{ $item->enclosureType }}"/>
<media:content url="{{ url($item->enclosure) }}" medium="image" type="{{ $item->enclosureType }}"/>
@endif
@foreach($item->category as $category)
<category>{{ $category }}</category>
@endforeach
</item>
@endforeach
</channel>
</rss>

230
routes/api.php Normal file
View File

@@ -0,0 +1,230 @@
<?php
use App\Models\LoginKey;
use App\Models\User;
use eza\lnurl;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware([])
->as('api.')
->group(function () {
Route::resource('countries', \App\Http\Controllers\Api\CountryController::class);
Route::resource('meetup', \App\Http\Controllers\Api\MeetupController::class);
Route::resource('lecturers', \App\Http\Controllers\Api\LecturerController::class);
Route::resource('courses', \App\Http\Controllers\Api\CourseController::class);
Route::resource('cities', \App\Http\Controllers\Api\CityController::class);
Route::resource('venues', \App\Http\Controllers\Api\VenueController::class);
Route::get('nostrplebs', function () {
return User::query()
->select([
'email',
'public_key',
'lightning_address',
'lnurl',
'node_id',
'paynym',
'lnbits',
'nostr',
'id',
])
->whereNotNull('nostr')
->where('nostr', 'like', 'npub1%')
->orderByDesc('id')
->get()
->unique('nostr')
->pluck('nostr');
});
Route::get('bindles', function () {
return \App\Models\LibraryItem::query()
->where('type', 'bindle')
->with([
'media',
])
->orderByDesc('id')
->get()
->map(fn($item)
=> [
'id' => $item->id,
'name' => $item->name,
'link' => strtok($item->value, "?"),
'image' => $item->getFirstMediaUrl('main'),
]);
});
Route::get('meetups', function (Request $request) {
return \App\Models\Meetup::query()
->where('visible_on_map', true)
->with([
'meetupEvents',
'city.country',
'media',
])
->get()
->map(fn($meetup)
=> [
'name' => $meetup->name,
'portalLink' => url()->route(
'meetups.landingpage',
['country' => $meetup->city->country, 'meetup' => $meetup],
),
'url' => $meetup->telegram_link ?? $meetup->webpage,
'top' => $meetup->github_data['top'] ?? null,
'left' => $meetup->github_data['left'] ?? null,
'country' => str($meetup->city->country->code)->upper(),
'state' => $meetup->github_data['state'] ?? null,
'city' => $meetup->city->name,
'longitude' => (float)$meetup->city->longitude,
'latitude' => (float)$meetup->city->latitude,
'twitter_username' => $meetup->twitter_username,
'website' => $meetup->webpage,
'simplex' => $meetup->simplex,
'signal' => $meetup->signal,
'nostr' => $meetup->nostr,
'next_event' => $meetup->nextEvent,
'intro' => $request->has('withIntro') ? $meetup->intro : null,
'logo' => $request->has('withLogos') ? $meetup->getFirstMediaUrl('logo') : null,
]);
});
Route::get('meetup-events/{date?}', function ($date = null) {
if ($date) {
$date = \Carbon\Carbon::parse($date);
}
$events = \App\Models\MeetupEvent::query()
->with([
'meetup.city.country',
'meetup.media',
])
->when(
$date,
fn($query)
=> $query
->where('start', '>=', $date)
->where('start', '<=', $date->copy()->endOfMonth()),
)
->get();
return $events->map(fn($event)
=> [
'start' => $event->start->format('Y-m-d H:i'),
'location' => $event->location,
'description' => $event->description,
'link' => $event->link,
'meetup.name' => $event->meetup->name,
'meetup.portalLink' => url()->route(
'meetups.landingpage',
[
'country' => $event->meetup->city->country,
'meetup' => $event->meetup,
],
),
'meetup.url' => $event->meetup->telegram_link ?? $event->meetup->webpage,
'meetup.country' => str($event->meetup->city->country->code)->upper(),
'meetup.city' => $event->meetup->city->name,
'meetup.longitude' => (float)$event->meetup->city->longitude,
'meetup.latitude' => (float)$event->meetup->city->latitude,
'meetup.twitter_username' => $event->meetup->twitter_username,
'meetup.website' => $event->meetup->webpage,
'meetup.simplex' => $event->meetup->simplex,
'meetup.signal' => $event->meetup->signal,
'meetup.nostr' => $event->meetup->nostr,
'meetup.logo' => $event->meetup->getFirstMediaUrl('logo'),
],
);
});
Route::get('btc-map-communities', function () {
return response()->json(
\App\Models\Meetup::query()
->with([
'media',
'city.country',
])
->where('community', '=', 'einundzwanzig')
->when(
app()->environment('production'),
fn($query)
=> $query->whereHas(
'city',
fn($query)
=> $query
->whereNotNull('cities.simplified_geojson')
->whereNotNull('cities.population')
->whereNotNull('cities.population_date'),
),
)
->get()
->map(fn($meetup)
=> [
'id' => $meetup->slug,
'tags' => [
'type' => 'community',
'name' => $meetup->name,
'continent' => 'europe',
'icon:square' => $meetup->logoSquare,
//'contact:email' => null,
'contact:twitter' => $meetup->twitter_username ? 'https://twitter.com/' . $meetup->twitter_username : null,
'contact:website' => $meetup->webpage,
'contact:telegram' => $meetup->telegram_link,
'contact:nostr' => $meetup->nostr,
//'tips:lightning_address' => null,
'organization' => 'einundzwanzig',
'language' => $meetup->city->country->language_codes[0] ?? 'de',
'geo_json' => $meetup->city->simplified_geojson,
'population' => $meetup->city->population,
'population:date' => $meetup->city->population_date,
],
])
->toArray(),
200,
['Content-Type' => 'application/json;charset=UTF-8', 'Charset' => 'utf-8'],
JSON_UNESCAPED_SLASHES,
);
});
});
Route::get('/lnurl-auth-callback', function (Request $request) {
if (lnurl\auth($request->k1, $request->sig, $request->key)) {
// find User by $wallet_public_key
if ($user = User::query()
->where('change', $request->k1)
->where('change_time', '>', now()->subMinutes(5))
->first()) {
$user->public_key = $request->key;
$user->change = null;
$user->change_time = null;
$user->save();
} else {
$user = User::query()
->whereBlind('public_key', 'public_key_index', $request->key)
->first();
}
if (!$user) {
$fakeName = str()->random(10);
// create User
$user = User::create([
'public_key' => $request->key,
'is_lecturer' => true,
'name' => $fakeName,
'email' => str($request->key)->substr(-12) . '@portal.einundzwanzig.space',
'lnbits' => [
'read_key' => null,
'url' => null,
'wallet_id' => null,
],
]);
}
// check if $k1 is in the database, if not, add it
$loginKey = LoginKey::where('k1', $request->k1)
->first();
if (!$loginKey) {
LoginKey::create([
'k1' => $request->k1,
'user_id' => $user->id,
]);
}
return response()->json(['status' => 'OK']);
}
return response()->json(['status' => 'ERROR', 'reason' => 'Signature was NOT VERIFIED']);
})
->name('auth.ln.callback');

View File

@@ -5,6 +5,14 @@ use Livewire\Volt\Volt;
Route::redirect('/', 'welcome');
Route::get('/img/{path}', \App\Http\Controllers\ImageController::class)
->where('path', '.*')
->name('img');
Route::get('/img-public/{path}', \App\Http\Controllers\ImageController::class)
->where('path', '.*')
->name('imgPublic');
Volt::route('welcome', 'welcome')->name('welcome');
Route::get('stream-calendar', \App\Http\Controllers\DownloadMeetupCalendar::class)