mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-01-27 06:33:18 +00:00
first copies from portal
This commit is contained in:
43
app/Console/Commands/Einundzwanzig/SyncPlebs.php
Normal file
43
app/Console/Commands/Einundzwanzig/SyncPlebs.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Einundzwanzig;
|
||||
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use swentel\nostr\Key\Key;
|
||||
|
||||
class SyncPlebs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sync:plebs';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$response = Http::get('https://portal.einundzwanzig.space/api/nostrplebs');
|
||||
|
||||
$plebs = $response->json();
|
||||
|
||||
foreach ($plebs as $pleb) {
|
||||
$npub = str($pleb)->trim();
|
||||
EinundzwanzigPleb::updateOrCreate(
|
||||
['npub' => $npub],
|
||||
['pubkey' => (new Key())->convertToHex($npub)]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
114
app/Console/Commands/Nostr/FetchEvents.php
Normal file
114
app/Console/Commands/Nostr/FetchEvents.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Nostr;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Traits\NostrEventRendererTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
class FetchEvents extends Command
|
||||
{
|
||||
use NostrEventRendererTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'fetch:events';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$plebs = \App\Models\EinundzwanzigPleb::query()
|
||||
->get();
|
||||
|
||||
$subscription = new Subscription();
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter();
|
||||
$filter1->setKinds([1]); // You can add multiple kind numbers
|
||||
$filter1->setAuthors($plebs->pluck('pubkey')->toArray()); // You can add multiple authors
|
||||
$filter1->setLimit(25); // Limit to fetch only a maximum of 25 events
|
||||
$filters = [$filter1]; // You can add multiple filters.
|
||||
|
||||
$requestMessage = new RequestMessage($subscriptionId, $filters);
|
||||
|
||||
$relays = [
|
||||
new Relay('wss://nostr.einundzwanzig.space'),
|
||||
new Relay('wss://nostr.wine'),
|
||||
new Relay('wss://nos.lol'),
|
||||
];
|
||||
$relaySet = new RelaySet();
|
||||
$relaySet->setRelays($relays);
|
||||
|
||||
$request = new Request($relaySet, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
$uniqueEvents = [];
|
||||
|
||||
foreach ($response as $relay => $events) {
|
||||
foreach ($events as $event) {
|
||||
if (!isset($uniqueEvents[$event->event->id])) {
|
||||
$uniqueEvents[$event->event->id] = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($uniqueEvents as $id => $uniqueEvent) {
|
||||
$type = $this->isReplyOrRoot($uniqueEvent->event);
|
||||
$parentEventId = $this->getParentEventId($uniqueEvent->event);
|
||||
|
||||
$event = Event::query()->updateOrCreate(
|
||||
['event_id' => $id],
|
||||
[
|
||||
'pubkey' => $uniqueEvent->event->pubkey,
|
||||
'parent_event_id' => $parentEventId,
|
||||
'json' => json_encode($uniqueEvent->event, JSON_THROW_ON_ERROR),
|
||||
'type' => $type,
|
||||
]
|
||||
);
|
||||
|
||||
$this->renderContentToHtml($event);
|
||||
}
|
||||
}
|
||||
|
||||
private function getParentEventId($event)
|
||||
{
|
||||
foreach ($event->tags as $tag) {
|
||||
if ($tag[0] === 'e') {
|
||||
if ((isset($tag[2]) && $tag[2] === '') || (isset($tag[3]) && $tag[3] === 'reply')) {
|
||||
return $tag[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function isReplyOrRoot($event)
|
||||
{
|
||||
foreach ($event->tags as $tag) {
|
||||
if ($tag[0] === 'e') {
|
||||
if ((isset($tag[3]) && $tag[3] === 'reply') || (!isset($tag[3]) && isset($tag[2]) && $tag[2] === '')) {
|
||||
return 'reply';
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'root';
|
||||
}
|
||||
}
|
||||
46
app/Console/Commands/Nostr/RenderAllEvents.php
Normal file
46
app/Console/Commands/Nostr/RenderAllEvents.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Nostr;
|
||||
|
||||
use App\Traits\NostrEventRendererTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
|
||||
class RenderAllEvents extends Command
|
||||
{
|
||||
use NostrEventRendererTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'render';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$events = \App\Models\Event::query()
|
||||
->get();
|
||||
|
||||
foreach ($events as $event) {
|
||||
$this->renderContentToHtml($event);
|
||||
}
|
||||
|
||||
Broadcast::on('events')
|
||||
->as('newEvents')
|
||||
->with([
|
||||
'test' => 'test',
|
||||
])
|
||||
->sendNow();
|
||||
}
|
||||
}
|
||||
38
app/Console/Commands/Nostr/SyncProfiles.php
Normal file
38
app/Console/Commands/Nostr/SyncProfiles.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Nostr;
|
||||
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Traits\NostrFetcherTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
class SyncProfiles extends Command
|
||||
{
|
||||
use NostrFetcherTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sync:profiles';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$plebs = EinundzwanzigPleb::query()
|
||||
->whereDoesntHave('profile')
|
||||
->get();
|
||||
$this->fetchProfile($plebs->pluck('npub')->toArray());
|
||||
}
|
||||
}
|
||||
97
app/Livewire/MeetupTable.php
Normal file
97
app/Livewire/MeetupTable.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Meetup;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use PowerComponents\LivewirePowerGrid\Button;
|
||||
use PowerComponents\LivewirePowerGrid\Column;
|
||||
use PowerComponents\LivewirePowerGrid\Exportable;
|
||||
use PowerComponents\LivewirePowerGrid\Facades\Filter;
|
||||
use PowerComponents\LivewirePowerGrid\Facades\Rule;
|
||||
use PowerComponents\LivewirePowerGrid\Footer;
|
||||
use PowerComponents\LivewirePowerGrid\Header;
|
||||
use PowerComponents\LivewirePowerGrid\Lazy;
|
||||
use PowerComponents\LivewirePowerGrid\PowerGrid;
|
||||
use PowerComponents\LivewirePowerGrid\PowerGridFields;
|
||||
use PowerComponents\LivewirePowerGrid\PowerGridComponent;
|
||||
|
||||
final class MeetupTable extends PowerGridComponent
|
||||
{
|
||||
public function setUp(): array
|
||||
{
|
||||
$this->showCheckBox();
|
||||
|
||||
return [
|
||||
Exportable::make('export')
|
||||
->striped()
|
||||
->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV),
|
||||
Footer::make()
|
||||
->showPerPage(perPage: 25)
|
||||
->showRecordCount(),
|
||||
];
|
||||
}
|
||||
|
||||
public function datasource(): Builder
|
||||
{
|
||||
return Meetup::query();
|
||||
}
|
||||
|
||||
public function fields(): PowerGridFields
|
||||
{
|
||||
return PowerGrid::fields()
|
||||
->add('name')
|
||||
->add('name_lower', fn (Meetup $model) => strtolower(e($model->name)))
|
||||
->add('created_at')
|
||||
->add('created_at_formatted', fn (Meetup $model) => Carbon::parse($model->created_at)->format('d/m/Y H:i:s'));
|
||||
}
|
||||
|
||||
public function columns(): array
|
||||
{
|
||||
return [
|
||||
Column::make('Name', 'name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
Column::action('Action')
|
||||
];
|
||||
}
|
||||
|
||||
public function filters(): array
|
||||
{
|
||||
return [
|
||||
Filter::inputText('name'),
|
||||
Filter::datepicker('created_at_formatted', 'created_at'),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Livewire\Attributes\On('edit')]
|
||||
public function edit($rowId): void
|
||||
{
|
||||
$this->js('alert('.$rowId.')');
|
||||
}
|
||||
|
||||
public function actions(Meetup $row): array
|
||||
{
|
||||
return [
|
||||
Button::add('edit')
|
||||
->slot('Edit: '.$row->id)
|
||||
->id()
|
||||
->class('pg-btn-white dark:ring-pg-primary-600 dark:border-pg-primary-600 dark:hover:bg-pg-primary-700 dark:ring-offset-pg-primary-800 dark:text-pg-primary-300 dark:bg-pg-primary-700')
|
||||
->dispatch('edit', ['rowId' => $row->id])
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
public function actionRules(Meetup $row): array
|
||||
{
|
||||
return [
|
||||
// Hide button edit for ID 1
|
||||
Rule::button('edit')
|
||||
->when(fn($row) => $row->id === 1)
|
||||
->hide(),
|
||||
];
|
||||
}
|
||||
*/
|
||||
}
|
||||
32
app/Models/Category.php
Normal file
32
app/Models/Category.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
];
|
||||
|
||||
public function courses(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Course::class);
|
||||
}
|
||||
}
|
||||
83
app/Models/City.php
Normal file
83
app/Models/City.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Akuechler\Geoly;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Spatie\Sluggable\HasSlug;
|
||||
use Spatie\Sluggable\SlugOptions;
|
||||
|
||||
class City extends Model
|
||||
{
|
||||
use HasSlug;
|
||||
use Geoly;
|
||||
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'country_id' => 'integer',
|
||||
'osm_relation' => 'json',
|
||||
'simplified_geojson' => 'json',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (! $model->created_by) {
|
||||
$model->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options for generating the slug.
|
||||
*/
|
||||
public function getSlugOptions(): SlugOptions
|
||||
{
|
||||
return SlugOptions::create()
|
||||
->generateSlugsFrom(['country.code', 'name'])
|
||||
->saveSlugsTo('slug')
|
||||
->usingLanguage(Cookie::get('lang', config('app.locale')));
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function country(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Country::class);
|
||||
}
|
||||
|
||||
public function venues(): HasMany
|
||||
{
|
||||
return $this->hasMany(Venue::class);
|
||||
}
|
||||
|
||||
public function courseEvents()
|
||||
{
|
||||
return $this->hasManyThrough(CourseEvent::class, Venue::class);
|
||||
}
|
||||
|
||||
public function meetups()
|
||||
{
|
||||
return $this->hasMany(Meetup::class);
|
||||
}
|
||||
}
|
||||
33
app/Models/Country.php
Normal file
33
app/Models/Country.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Country extends Model
|
||||
{
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'language_codes' => 'array',
|
||||
];
|
||||
|
||||
public function cities(): HasMany
|
||||
{
|
||||
return $this->hasMany(City::class);
|
||||
}
|
||||
}
|
||||
89
app/Models/Course.php
Normal file
89
app/Models/Course.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Image\Enums\Fit;
|
||||
use Spatie\Image\Manipulations;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Spatie\Tags\HasTags;
|
||||
|
||||
class Course extends Model implements HasMedia
|
||||
{
|
||||
use InteractsWithMedia;
|
||||
use HasTags;
|
||||
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'lecturer_id' => 'integer',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (! $model->created_by) {
|
||||
$model->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function registerMediaConversions(Media $media = null): void
|
||||
{
|
||||
$this
|
||||
->addMediaConversion('preview')
|
||||
->fit(Fit::Crop, 300, 300)
|
||||
->nonQueued();
|
||||
$this->addMediaConversion('thumb')
|
||||
->fit(Fit::Crop, 130, 130)
|
||||
->width(130)
|
||||
->height(130);
|
||||
}
|
||||
|
||||
public function registerMediaCollections(): void
|
||||
{
|
||||
$this->addMediaCollection('logo')
|
||||
->singleFile()
|
||||
->useFallbackUrl(asset('img/einundzwanzig.png'));
|
||||
$this->addMediaCollection('images')
|
||||
->useFallbackUrl(asset('img/einundzwanzig.png'));
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function categories(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Category::class);
|
||||
}
|
||||
|
||||
public function lecturer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Lecturer::class);
|
||||
}
|
||||
|
||||
public function courseEvents(): HasMany
|
||||
{
|
||||
return $this->hasMany(CourseEvent::class);
|
||||
}
|
||||
}
|
||||
56
app/Models/CourseEvent.php
Normal file
56
app/Models/CourseEvent.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class CourseEvent extends Model
|
||||
{
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'course_id' => 'integer',
|
||||
'venue_id' => 'integer',
|
||||
'from' => 'datetime',
|
||||
'to' => 'datetime',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (! $model->created_by) {
|
||||
$model->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function course(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Course::class);
|
||||
}
|
||||
|
||||
public function venue(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Venue::class);
|
||||
}
|
||||
}
|
||||
17
app/Models/EinundzwanzigPleb.php
Normal file
17
app/Models/EinundzwanzigPleb.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EinundzwanzigPleb extends Model
|
||||
{
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->hasOne(Profile::class, 'pubkey', 'pubkey');
|
||||
}
|
||||
|
||||
}
|
||||
17
app/Models/Event.php
Normal file
17
app/Models/Event.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Event extends Model
|
||||
{
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function renderedEvent()
|
||||
{
|
||||
return $this->hasOne(RenderedEvent::class, 'event_id', 'event_id');
|
||||
}
|
||||
|
||||
}
|
||||
99
app/Models/Lecturer.php
Normal file
99
app/Models/Lecturer.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Spatie\Image\Enums\Fit;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Spatie\Sluggable\HasSlug;
|
||||
use Spatie\Sluggable\SlugOptions;
|
||||
|
||||
class Lecturer extends Model implements HasMedia
|
||||
{
|
||||
use HasSlug;
|
||||
use InteractsWithMedia;
|
||||
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (!$model->created_by) {
|
||||
$model->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function registerMediaConversions(Media $media = null): void
|
||||
{
|
||||
$this
|
||||
->addMediaConversion('preview')
|
||||
->fit(Fit::Crop, 300, 300)
|
||||
->nonQueued();
|
||||
$this->addMediaConversion('thumb')
|
||||
->fit(Fit::Crop, 130, 130)
|
||||
->width(130)
|
||||
->height(130);
|
||||
}
|
||||
|
||||
public function registerMediaCollections(): void
|
||||
{
|
||||
$this->addMediaCollection('avatar')
|
||||
->singleFile()
|
||||
->useFallbackUrl(asset('img/einundzwanzig.png'));
|
||||
$this->addMediaCollection('images')
|
||||
->useFallbackUrl(asset('img/einundzwanzig.png'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options for generating the slug.
|
||||
*/
|
||||
public function getSlugOptions(): SlugOptions
|
||||
{
|
||||
return SlugOptions::create()
|
||||
->generateSlugsFrom(['name'])
|
||||
->saveSlugsTo('slug')
|
||||
->usingLanguage(Cookie::get('lang', config('app.locale')));
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function courses(): HasMany
|
||||
{
|
||||
return $this->hasMany(Course::class);
|
||||
}
|
||||
|
||||
public function coursesEvents(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(CourseEvent::class, Course::class);
|
||||
}
|
||||
|
||||
public function libraryItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(LibraryItem::class);
|
||||
}
|
||||
}
|
||||
136
app/Models/Meetup.php
Normal file
136
app/Models/Meetup.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Spatie\Image\Enums\Fit;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Spatie\Sluggable\HasSlug;
|
||||
use Spatie\Sluggable\SlugOptions;
|
||||
|
||||
class Meetup extends Model implements HasMedia
|
||||
{
|
||||
use InteractsWithMedia;
|
||||
use HasSlug;
|
||||
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'city_id' => 'integer',
|
||||
'github_data' => 'json',
|
||||
'simplified_geojson' => 'array',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (!$model->created_by) {
|
||||
$model->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function getSlugOptions(): SlugOptions
|
||||
{
|
||||
return SlugOptions::create()
|
||||
->generateSlugsFrom(['name'])
|
||||
->saveSlugsTo('slug')
|
||||
->usingLanguage(Cookie::get('lang', config('app.locale')));
|
||||
}
|
||||
|
||||
public function registerMediaConversions(Media $media = null): void
|
||||
{
|
||||
$this
|
||||
->addMediaConversion('preview')
|
||||
->fit(Fit::Crop, 300, 300)
|
||||
->nonQueued();
|
||||
$this->addMediaConversion('thumb')
|
||||
->fit(Fit::Crop, 130, 130)
|
||||
->width(130)
|
||||
->height(130);
|
||||
}
|
||||
|
||||
public function registerMediaCollections(): void
|
||||
{
|
||||
$this->addMediaCollection('logo')
|
||||
->singleFile()
|
||||
->useFallbackUrl(asset('img/einundzwanzig.png'));
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
|
||||
public function city(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(City::class);
|
||||
}
|
||||
|
||||
protected function logoSquare(): Attribute
|
||||
{
|
||||
$media = $this->getFirstMedia('logo');
|
||||
if ($media) {
|
||||
$path = str($media->getPath())->after('storage/app/');
|
||||
} else {
|
||||
$path = 'img/einundzwanzig.png';
|
||||
}
|
||||
|
||||
return Attribute::make(
|
||||
get: fn() => url()->route('img',
|
||||
[
|
||||
'path' => $path,
|
||||
'w' => 900,
|
||||
'h' => 900,
|
||||
'fit' => 'crop',
|
||||
'fm' => 'webp',
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
protected function nextEvent(): Attribute
|
||||
{
|
||||
$nextEvent = $this->meetupEvents()->where('start', '>=', now())->orderBy('start')->first();
|
||||
|
||||
return Attribute::make(
|
||||
get: fn() => $nextEvent ? [
|
||||
'start' => $nextEvent->start->toDateTimeString(),
|
||||
'portalLink' => url()->route('meetup.event.landing', ['country' => $this->city->country, 'meetupEvent' => $nextEvent]),
|
||||
'location' => $nextEvent->location,
|
||||
'description' => $nextEvent->description,
|
||||
'link' => $nextEvent->link,
|
||||
'attendees' => count($nextEvent->attendees ?? []),
|
||||
'nostr_note' => str($nextEvent->nostr_status)->after('Sent event ')->before(' to '),
|
||||
] : null,
|
||||
);
|
||||
}
|
||||
|
||||
public function meetupEvents(): HasMany
|
||||
{
|
||||
return $this->hasMany(MeetupEvent::class);
|
||||
}
|
||||
}
|
||||
50
app/Models/MeetupEvent.php
Normal file
50
app/Models/MeetupEvent.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MeetupEvent extends Model
|
||||
{
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'meetup_id' => 'integer',
|
||||
'start' => 'datetime',
|
||||
'attendees' => 'array',
|
||||
'might_attendees' => 'array',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (! $model->created_by) {
|
||||
$model->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function meetup(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Meetup::class);
|
||||
}
|
||||
}
|
||||
12
app/Models/Profile.php
Normal file
12
app/Models/Profile.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Profile extends Model
|
||||
{
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
}
|
||||
15
app/Models/RenderedEvent.php
Normal file
15
app/Models/RenderedEvent.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class RenderedEvent extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function event()
|
||||
{
|
||||
return $this->belongsTo(Event::class, 'event_id', 'event_id');
|
||||
}
|
||||
}
|
||||
104
app/Models/Venue.php
Normal file
104
app/Models/Venue.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Spatie\Image\Enums\Fit;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Spatie\Sluggable\HasSlug;
|
||||
use Spatie\Sluggable\SlugOptions;
|
||||
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
|
||||
|
||||
class Venue extends Model implements HasMedia
|
||||
{
|
||||
use HasSlug;
|
||||
use HasRelationships;
|
||||
use InteractsWithMedia;
|
||||
|
||||
protected $connection = 'einundzwanzig';
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'city_id' => 'integer',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (! $model->created_by) {
|
||||
$model->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function registerMediaConversions(Media $media = null): void
|
||||
{
|
||||
$this
|
||||
->addMediaConversion('preview')
|
||||
->fit(Fit::Crop, 300, 300)
|
||||
->nonQueued();
|
||||
$this->addMediaConversion('thumb')
|
||||
->fit(Fit::Crop, 130, 130)
|
||||
->width(130)
|
||||
->height(130);
|
||||
}
|
||||
|
||||
public function registerMediaCollections(): void
|
||||
{
|
||||
$this->addMediaCollection('images')
|
||||
->useFallbackUrl(asset('img/einundzwanzig.png'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options for generating the slug.
|
||||
*/
|
||||
public function getSlugOptions(): SlugOptions
|
||||
{
|
||||
return SlugOptions::create()
|
||||
->generateSlugsFrom(['city.slug', 'name'])
|
||||
->saveSlugsTo('slug')
|
||||
->usingLanguage(Cookie::get('lang', config('app.locale')));
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function city(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(City::class);
|
||||
}
|
||||
|
||||
public function lecturers()
|
||||
{
|
||||
return $this->hasManyDeepFromRelations($this->courses(), (new Course())->lecturer());
|
||||
}
|
||||
|
||||
public function courses()
|
||||
{
|
||||
return $this->hasManyDeepFromRelations($this->events(), (new CourseEvent())->course());
|
||||
}
|
||||
|
||||
public function courseEvents(): HasMany
|
||||
{
|
||||
return $this->hasMany(CourseEvent::class);
|
||||
}
|
||||
}
|
||||
29
app/Providers/FolioServiceProvider.php
Normal file
29
app/Providers/FolioServiceProvider.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Folio\Folio;
|
||||
|
||||
class FolioServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Folio::path(resource_path('views/pages'))->middleware([
|
||||
'*' => [
|
||||
//
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
28
app/Providers/VoltServiceProvider.php
Normal file
28
app/Providers/VoltServiceProvider.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
class VoltServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Volt::mount([
|
||||
config('livewire.view_path', resource_path('views/livewire')),
|
||||
resource_path('views/pages'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
95
app/Traits/NostrEventRendererTrait.php
Normal file
95
app/Traits/NostrEventRendererTrait.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Profile;
|
||||
use App\Models\RenderedEvent;
|
||||
use swentel\nostr\Key\Key;
|
||||
|
||||
trait NostrEventRendererTrait
|
||||
{
|
||||
public function renderContentToHtml(Event $event): void
|
||||
{
|
||||
$content = json_decode($event->json, true, 512, JSON_THROW_ON_ERROR)['content'];
|
||||
$profile = Profile::query()->where('pubkey', $event->pubkey)->first();
|
||||
if ($profile && $profile->name) {
|
||||
$name = $profile->name;
|
||||
} elseif ($profile && !empty($profile->display_name)) {
|
||||
$name = $profile->display_name;
|
||||
} else {
|
||||
$name = 'Anonymous';
|
||||
}
|
||||
|
||||
$content = $this->nprofile1($content);
|
||||
$content = $this->images($content);
|
||||
$content = $this->youtube($content);
|
||||
$content = $this->npub1($content);
|
||||
|
||||
RenderedEvent::query()->updateOrCreate([
|
||||
'event_id' => $event->event_id,
|
||||
], [
|
||||
'html' => $content,
|
||||
'profile_image' => $profile && $profile->picture !== '' ? $profile->picture : 'https://robohash.org/' . $profile->pubkey,
|
||||
'profile_name' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function images($content): string
|
||||
{
|
||||
// we need to find all image urls by looking for the extension
|
||||
// and replace them with the img tag
|
||||
$pattern = '/(https?:\/\/.*\.(?:png|jpg|jpeg|gif|webp))/';
|
||||
$replacement = '<div class="w-96 group aspect-h-7 aspect-w-10"><img class="pointer-events-none object-cover" src="$1" alt="image" /></div>';
|
||||
|
||||
return preg_replace($pattern, $replacement, $content);
|
||||
}
|
||||
|
||||
protected function npub1($content): string
|
||||
{
|
||||
// Pattern to match nostr:npub1 elements, optionally followed by a non-alphanumeric character
|
||||
$pattern = '/(nostr:npub1[a-zA-Z0-9]+)(\W?)/';
|
||||
// find all matches of the pattern
|
||||
preg_match_all($pattern, $content, $matches);
|
||||
// loop through all matches
|
||||
foreach ($matches[1] as $match) {
|
||||
$pubkey = (new Key)->convertToHex(str($match)->after('nostr:'));
|
||||
$profile = Profile::query()->where('pubkey', $pubkey)->first();
|
||||
if ($profile && $profile->name) {
|
||||
$name = $profile->name;
|
||||
} elseif ($profile && !empty($profile->display_name)) {
|
||||
$name = $profile->display_name;
|
||||
} else {
|
||||
$name = 'Anonymous';
|
||||
}
|
||||
// replace the match with the profile name
|
||||
$content = str_replace($match, $name, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function nprofile1($content): string
|
||||
{
|
||||
// todo: implement this
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function youtube($content): string
|
||||
{
|
||||
// Pattern to match YouTube short URLs like https://youtu.be/ddvHagjmRJY?feature=shared
|
||||
$pattern1 = '/https:\/\/youtu.be\/([a-zA-Z0-9-_]+)\??.*/';
|
||||
$replacement1 = '<iframe width="560" height="315" src="https://www.youtube.com/embed/$1" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
|
||||
|
||||
// Pattern to match YouTube long URLs like https://www.youtube.com/watch?v=tiNZoDBGhdo
|
||||
$pattern2 = '/https:\/\/www.youtube.com\/watch\?v=([a-zA-Z0-9-_]+)\??.*/';
|
||||
$replacement2 = '<iframe width="560" height="315" src="https://www.youtube.com/embed/$1" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
|
||||
|
||||
// Replace both patterns in the content
|
||||
$content = preg_replace($pattern1, $replacement1, $content);
|
||||
$content = preg_replace($pattern2, $replacement2, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
68
app/Traits/NostrFetcherTrait.php
Normal file
68
app/Traits/NostrFetcherTrait.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Profile;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Key\Key;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
trait NostrFetcherTrait
|
||||
{
|
||||
|
||||
public function fetchProfile($npubs)
|
||||
{
|
||||
$hex = collect([]);
|
||||
foreach ($npubs as $item) {
|
||||
$hex->push([
|
||||
'hex' => (new Key)->convertToHex($item),
|
||||
'npub' => $item,
|
||||
]);
|
||||
}
|
||||
|
||||
$subscription = new Subscription();
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter();
|
||||
$filter1->setKinds([0]); // You can add multiple kind numbers
|
||||
$filter1->setAuthors($hex->pluck('hex')->toArray()); // You can add multiple author ids
|
||||
$filters = [$filter1]; // You can add multiple filters.
|
||||
|
||||
$requestMessage = new RequestMessage($subscriptionId, $filters);
|
||||
|
||||
$relayUrl = 'wss://relay.nostr.band/';
|
||||
$relay = new Relay($relayUrl);
|
||||
$relay->setMessage($requestMessage);
|
||||
|
||||
$request = new Request($relay, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
foreach ($response['wss://relay.nostr.band/'] as $item) {
|
||||
try {
|
||||
$result = json_decode($item->event->content, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\JsonException $e) {
|
||||
throw new \RuntimeException('Error decoding JSON: ' . $e->getMessage());
|
||||
}
|
||||
Profile::query()->updateOrCreate(
|
||||
['pubkey' => $item->event->pubkey],
|
||||
[
|
||||
'name' => $result['name'] ?? null,
|
||||
'display_name' => $result['display_name'] ?? null,
|
||||
'picture' => $result['picture'] ?? null,
|
||||
'banner' => $result['banner'] ?? null,
|
||||
'website' => $result['website'] ?? null,
|
||||
'about' => $result['about'] ?? null,
|
||||
'nip05' => $result['nip05'] ?? null,
|
||||
'lud16' => $result['lud16'] ?? null,
|
||||
'lud06' => $result['lud06'] ?? null,
|
||||
'deleted' => $result['deleted'] ?? false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user