Browse Source

Tabular data demo

imwald
Nuša Pukšič 3 months ago
parent
commit
328f0d5594
  1. 102
      assets/controllers/tabular_publish_controller.js
  2. 96
      src/Controller/TabularDataController.php
  3. 1
      src/Enum/KindsEnum.php
  4. 43
      src/Form/TabularDataType.php
  5. 0
      templates/event/_kind1450_tabular.html.twig
  6. 2
      templates/event/index.html.twig
  7. 24
      templates/tabular_data/preview.html.twig
  8. 0
      templates/tabular_data/publish.html.twig

102
assets/controllers/tabular_publish_controller.js

@ -0,0 +1,102 @@
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['publishButton', 'status'];
static values = {
publishUrl: String,
csrfToken: String,
eventData: Object
};
connect() {
console.log('Tabular data publish controller connected');
}
async publish(event) {
event.preventDefault();
if (!this.publishUrlValue) {
this.showError('Publish URL is not configured');
return;
}
if (!this.csrfTokenValue) {
this.showError('Missing CSRF token');
return;
}
if (!window.nostr) {
this.showError('Nostr extension not found');
return;
}
this.publishButtonTarget.disabled = true;
this.showStatus('Requesting signature from Nostr extension...');
try {
// Prepare the event data
const eventData = this.eventDataValue;
delete eventData.sig; // Remove sig if present
delete eventData.id; // Remove id if present
// Sign the event with Nostr extension
const signedEvent = await window.nostr.signEvent(eventData);
this.showStatus('Publishing tabular data...');
// Send to backend
await this.sendToBackend(signedEvent);
this.showSuccess('Tabular data published successfully!');
// Redirect to the event page
setTimeout(() => {
window.location.href = `/e/${signedEvent.id}`;
}, 2000);
} catch (error) {
console.error('Publishing error:', error);
this.showError(`Publishing failed: ${error.message}`);
} finally {
this.publishButtonTarget.disabled = false;
}
}
async sendToBackend(signedEvent) {
const response = await fetch(this.publishUrlValue, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': this.csrfTokenValue
},
body: JSON.stringify({
event: signedEvent
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
showStatus(message) {
if (this.hasStatusTarget) {
this.statusTarget.innerHTML = `<div class="alert alert-info">${message}</div>`;
}
}
showSuccess(message) {
if (this.hasStatusTarget) {
this.statusTarget.innerHTML = `<div class="alert alert-success">${message}</div>`;
}
}
showError(message) {
if (this.hasStatusTarget) {
this.statusTarget.innerHTML = `<div class="alert alert-danger">${message}</div>`;
}
}
}

96
src/Controller/TabularDataController.php

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Enum\KindsEnum;
use App\Form\TabularDataType;
use App\Service\NostrClient;
use swentel\nostr\Event\Event;
use swentel\nostr\Sign\Sign;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class TabularDataController extends AbstractController
{
#[Route('/tabular-data', name: 'tabular_data_publish')]
public function publish(Request $request, NostrClient $nostrClient): Response
{
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('login');
}
$form = $this->createForm(TabularDataType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// Create the event
$event = new Event();
$event->setKind(KindsEnum::TABULAR_DATA->value);
$event->setContent($data['csvContent']);
// Add tags
$tags = [
['title', $data['title']],
['m', 'text/csv'],
['M', 'text/csv; charset=utf-8'],
];
if (!empty($data['license'])) {
$tags[] = ['license', $data['license']];
}
if (!empty($data['units'])) {
// Parse units, e.g., "col=2,EH/s" -> ['unit', 'col=2', 'EH/s']
$unitParts = explode(',', $data['units'], 2);
if (count($unitParts) == 2) {
$tags[] = ['unit', trim($unitParts[0]), trim($unitParts[1])];
}
}
foreach ($tags as $tag) {
$event->addTag($tag);
}
// For now, just render the event JSON
return $this->render('tabular_data/preview.html.twig', [
'event' => $event,
'data' => $data,
]);
}
return $this->render('tabular_data/publish.html.twig', [
'form' => $form->createView(),
]);
}
#[Route('/tabular-data/publish', name: 'tabular_data_publish_event', methods: ['POST'])]
public function publishEvent(Request $request, NostrClient $nostrClient): Response
{
$data = json_decode($request->getContent(), true);
if (!$data || !isset($data['event'])) {
return $this->json(['error' => 'Invalid data'], 400);
}
$signedEvent = $data['event'];
// Validate the event
if ($signedEvent['kind'] !== KindsEnum::TABULAR_DATA->value) {
return $this->json(['error' => 'Invalid event kind'], 400);
}
// Publish the event
try {
$nostrClient->publishEvent($signedEvent, ['wss://relay.damus.io', 'wss://nos.lol']);
return $this->json(['success' => true, 'eventId' => $signedEvent['id']]);
} catch (\Exception $e) {
return $this->json(['error' => 'Publishing failed: ' . $e->getMessage()], 500);
}
}
}

1
src/Enum/KindsEnum.php

@ -21,4 +21,5 @@ enum KindsEnum: int
case HIGHLIGHTS = 9802; case HIGHLIGHTS = 9802;
case RELAY_LIST = 10002; // NIP-65, Relay list metadata case RELAY_LIST = 10002; // NIP-65, Relay list metadata
case APP_DATA = 30078; // NIP-78, Arbitrary custom app data case APP_DATA = 30078; // NIP-78, Arbitrary custom app data
case TABULAR_DATA = 1450; // NIP-XX, Tabular Data (CSV)
} }

43
src/Form/TabularDataType.php

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TabularDataType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title', TextType::class, [
'label' => 'Title',
'required' => true,
])
->add('csvContent', TextareaType::class, [
'label' => 'CSV Content',
'required' => true,
'attr' => ['rows' => 10, 'placeholder' => 'date,hashrate\n2025-10-01,795\n2025-10-02,802'],
])
->add('license', TextType::class, [
'label' => 'License (optional)',
'required' => false,
])
->add('units', TextType::class, [
'label' => 'Units (optional, e.g., col=2,EH/s)',
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}

0
templates/event/_kind1450_tabular.html.twig

2
templates/event/index.html.twig

@ -113,6 +113,8 @@
{# NIP-71 Video Events (kind 21 and 22) #} {# NIP-71 Video Events (kind 21 and 22) #}
{% elseif event.kind == 21 or event.kind == 22 %} {% elseif event.kind == 21 or event.kind == 22 %}
{% include 'event/_kind22_video.html.twig' %} {% include 'event/_kind22_video.html.twig' %}
{% elseif event.kind == 1450 %}
{% include 'event/_kind1450_tabular.html.twig' %}
{% else %} {% else %}
{# Regular event content for non-picture and non-video events #} {# Regular event content for non-picture and non-video events #}
<div class="event-content"> <div class="event-content">

24
templates/tabular_data/preview.html.twig

@ -0,0 +1,24 @@
{% extends 'layout.html.twig' %}
{% block title %}Preview Tabular Data Event{% endblock %}
{% block body %}
<div class="w-container"
data-controller="tabular-publish"
data-tabular-publish-publish-url-value="{{ path('tabular_data_publish_event') }}"
data-tabular-publish-csrf-token-value="{{ csrf_token('tabular_publish') }}"
data-tabular-publish-event-data-value="{{ event|json_encode }}">
<div class="card">
<div class="card-header">
<h1 class="card-title">Event Preview</h1>
</div>
<div class="card-body">
<p>Here is the generated event. Click "Sign and Publish" to sign with your Nostr key and publish to relays.</p>
<pre>{{ event|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>
<div data-tabular-publish-target="status"></div>
<button data-tabular-publish-target="publishButton" data-action="tabular-publish#publish" class="btn btn-primary">Sign and Publish</button>
<a href="{{ path('tabular_data_publish') }}" class="btn btn-secondary">Back</a>
</div>
</div>
</div>
{% endblock %}

0
templates/tabular_data/publish.html.twig

Loading…
Cancel
Save