Service za ether API, izboljsave nastavitev, validacija, manjsi popravek obrazca za dodajanje

pull/26/head
Jurij Podgoršek 2023-11-12 22:55:24 +01:00
parent 905b8e8b04
commit 8621c8f6f3
18 changed files with 543 additions and 424 deletions

View File

@ -18,6 +18,7 @@
"composer/installers": "^2.0", "composer/installers": "^2.0",
"cweagans/composer-patches": "^1.7", "cweagans/composer-patches": "^1.7",
"drupal/cer": "^5.0@beta", "drupal/cer": "^5.0@beta",
"drupal/config_ignore": "^3.1",
"drupal/content_as_config": "^1.0", "drupal/content_as_config": "^1.0",
"drupal/core-composer-scaffold": "^10.0", "drupal/core-composer-scaffold": "^10.0",
"drupal/core-project-message": "^10.0", "drupal/core-project-message": "^10.0",

566
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
_core:
default_config_hash: IgOVnECx6lbVt6JVFnadoEEugneDf3UblPZnOzov43Q
mode: simple
ignored_config_entities:
- etherpad_api.settings

View File

@ -6,7 +6,6 @@ dependencies:
- field.field.node.concept.body - field.field.node.concept.body
- field.field.node.concept.field_media - field.field.node.concept.field_media
- field.field.node.concept.field_related_concept - field.field.node.concept.field_related_concept
- field.field.node.concept.field_tags
- node.type.concept - node.type.concept
- workflows.workflow.concept_workflow - workflows.workflow.concept_workflow
module: module:
@ -52,16 +51,6 @@ content:
size: 60 size: 60
placeholder: '' placeholder: ''
third_party_settings: { } third_party_settings: { }
field_tags:
type: entity_reference_autocomplete
weight: 4
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
langcode: langcode:
type: language_select type: language_select
weight: 3 weight: 3

View File

@ -6,7 +6,6 @@ dependencies:
- field.field.node.concept.body - field.field.node.concept.body
- field.field.node.concept.field_media - field.field.node.concept.field_media
- field.field.node.concept.field_related_concept - field.field.node.concept.field_related_concept
- field.field.node.concept.field_tags
- node.type.concept - node.type.concept
module: module:
- text - text
@ -40,14 +39,6 @@ content:
third_party_settings: { } third_party_settings: { }
weight: 103 weight: 103
region: content region: content
field_tags:
type: entity_reference_label
label: above
settings:
link: true
third_party_settings: { }
weight: 104
region: content
links: links:
settings: { } settings: { }
third_party_settings: { } third_party_settings: { }

View File

@ -7,7 +7,6 @@ dependencies:
- field.field.node.concept.body - field.field.node.concept.body
- field.field.node.concept.field_media - field.field.node.concept.field_media
- field.field.node.concept.field_related_concept - field.field.node.concept.field_related_concept
- field.field.node.concept.field_tags
- node.type.concept - node.type.concept
module: module:
- text - text
@ -33,5 +32,4 @@ content:
hidden: hidden:
field_media: true field_media: true
field_related_concept: true field_related_concept: true
field_tags: true
langcode: true langcode: true

View File

@ -10,6 +10,7 @@ module:
ckeditor5: 0 ckeditor5: 0
comment: 0 comment: 0
config: 0 config: 0
config_ignore: 0
config_translation: 0 config_translation: 0
content_as_config: 0 content_as_config: 0
content_moderation: 0 content_moderation: 0

View File

@ -1,29 +0,0 @@
uuid: f5fade64-73dc-420b-8c1f-ab4fe3355a52
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_tags
- node.type.concept
- taxonomy.vocabulary.tags
id: node.concept.field_tags
field_name: field_tags
entity_type: node
bundle: concept
label: Tags
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
handler: 'default:taxonomy_term'
handler_settings:
target_bundles:
tags: tags
sort:
field: name
direction: asc
auto_create: true
auto_create_bundle: ''
field_type: entity_reference

View File

@ -1,3 +1,3 @@
_core: _core:
default_config_hash: 2OMXCScXUOLSYID9-phjO4q36nnnaMWNUlDxEqZzG1U default_config_hash: 2OMXCScXUOLSYID9-phjO4q36nnnaMWNUlDxEqZzG1U
use_admin_theme: true use_admin_theme: false

View File

@ -1,8 +1,21 @@
<script setup="setup">
const prikazi = ref(false)
const revisionId = ref(null)
const dodajPojem = () => {
revisionId.value = crypto.randomUUID()
prikazi.value = true
console.log('NOV POJEM!', revisionId.value)
}
</script>
<template> <template>
<details> <div>
<summary class="gumb"> <div class="gumb" @click="dodajPojem()">
Dodaj pojem Dodaj pojem
</summary> </div>
<PojemForm /> <PojemForm v-if="prikazi" :revisionId="revisionId" />
</details> </div>
</template> </template>

View File

@ -5,20 +5,21 @@ import { ref } from 'vue'
const { etherpadUrl, etherpadPrefix } = useRuntimeConfig().public const { etherpadUrl, etherpadPrefix } = useRuntimeConfig().public
const props = defineProps({ const props = defineProps({
revisionId: String revisionId: String,
onLoad: Function
}) })
const embed = ref(null) const embed = ref(null)
onMounted(() => { onMounted(() => {
// Ce ni revisionId propertyja, se random generira.
const uuid = props.revisionId ? props.revisionId : crypto.randomUUID() const uuid = props.revisionId ? props.revisionId : crypto.randomUUID()
const padUrl = `${etherpadUrl}p/${etherpadPrefix}${uuid}?showChat=false&showLineNumbers=false&toc=false` const padUrl = `${etherpadUrl}p/${etherpadPrefix}${uuid}?showChat=false&showLineNumbers=false&toc=false`
embed.value.src = padUrl embed.value.src = padUrl
window.location.hash = uuid
}) })
</script> </script>
<template> <template>
<iframe ref="embed" class="etherpad-textarea" /> <iframe ref="embed" class="etherpad-textarea" @load="props.onLoad" />
</template> </template>

View File

@ -1,8 +1,12 @@
<script setup="setup"> <script setup="setup">
const { etherpadUrl, etherpadPrefix } = useRuntimeConfig().public
const store = usePojmiStore() const store = usePojmiStore()
const props = defineProps({ const props = defineProps({
naslov: String naslov: String,
revisionId: String
}) })
//const pojem = computed(() => store.pojmi[props.naslov]) //const pojem = computed(() => store.pojmi[props.naslov])
@ -21,6 +25,14 @@ const oddajPredlog = data => {
store.ustvariPojem(data) store.ustvariPojem(data)
} }
const etherNalozen = ev => {
// @TODO DRY
const padUrl = `${etherpadUrl}p/${etherpadPrefix}${props.revisionId}?showChat=false&showLineNumbers=false&toc=false`
// Izprazni pad!
console.log('etherpad nalozen!')
window.location.hash = props.revisionId
}
</script> </script>
<template> <template>
@ -31,7 +43,7 @@ const oddajPredlog = data => {
<input name="naslov" type="text" v-model="naslov"> <input name="naslov" type="text" v-model="naslov">
<label for="tekst">Besedilo</label> <label for="tekst">Besedilo</label>
<EtherpadTextarea /> <EtherpadTextarea :onLoad="etherNalozen" :revisionId="props.revisionId" />
<label for="email">E-poštni naslov</label> <label for="email">E-poštni naslov</label>
<input name="email" type="email" v-model="email"> <input name="email" type="email" v-model="email">

View File

@ -1,95 +1,36 @@
<?php <?php
use GuzzleHttp\Exception\ClientException;
/** /**
* @file * @file
* Install, update and uninstall functions for the Etherpad API module. * Install, update and uninstall functions for the Etherpad API module.
*/ */
/**
* Implements hook_install().
*/
function etherpad_api_install() {
\Drupal::messenger()->addStatus(__FUNCTION__);
}
/**
* Implements hook_uninstall().
*/
function etherpad_api_uninstall() {
\Drupal::messenger()->addStatus(__FUNCTION__);
}
/**
* Implements hook_schema().
*/
function etherpad_api_schema() {
$schema['etherpad_api_example'] = [
'description' => 'Table description.',
'fields' => [
'id' => [
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique record ID.',
],
'uid' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The {users}.uid of the user who created the record.',
],
'status' => [
'description' => 'Boolean indicating whether this record is active.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
],
'type' => [
'type' => 'varchar_ascii',
'length' => 64,
'not null' => TRUE,
'default' => '',
'description' => 'Type of the record.',
],
'created' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Timestamp when the record was created.',
],
'data' => [
'type' => 'blob',
'not null' => TRUE,
'size' => 'big',
'description' => 'The arbitrary data for the item.',
],
],
'primary key' => ['id'],
'indexes' => [
'type' => ['type'],
'uid' => ['uid'],
'status' => ['status'],
],
];
return $schema;
}
/** /**
* Implements hook_requirements(). * Implements hook_requirements().
*/ */
function etherpad_api_requirements($phase) { function etherpad_api_requirements($phase) {
$requirements = []; $requirements = [];
// Preveri ali api deluje
if ($phase == 'runtime') { if ($phase == 'runtime') {
$value = mt_rand(0, 100); $client = Drupal::service('etherpad_api.client');
$requirements['etherpad_api_status'] = [
'title' => t('Etherpad API status'), try {
'value' => t('Etherpad API value: @value', ['@value' => $value]), $client->checkToken();
'severity' => $value > 50 ? REQUIREMENT_INFO : REQUIREMENT_WARNING, } catch (ClientException $exception) {
]; $value = $exception->getCode();
$msg = $exception->getMessage();
$requirements['etherpad_api_status'] = [
'title' => t('Etherpad API status'),
'value' => t('Etherpad API not accessible (@code: @msg)', [
'@code' => $exception->getCode(),
'@msg' => $exception->getMessage()
]),
'severity' => REQUIREMENT_ERROR,
];
}
} }
return $requirements; return $requirements;

View File

@ -3,3 +3,10 @@ services:
class: Drupal\etherpad_api\PathProcessor\EtherpadAPIPathProcessor class: Drupal\etherpad_api\PathProcessor\EtherpadAPIPathProcessor
tags: tags:
- { name: path_processor_inbound, priority: 1000 } - { name: path_processor_inbound, priority: 1000 }
etherpad_api.settings:
class: Drupal\Core\Config\ImmutableConfig
factory: config.factory:get
arguments: ['etherpad_api.settings']
etherpad_api.client:
class: Drupal\etherpad_api\Client
arguments: ['@http_client', '@etherpad_api.settings']

View File

@ -0,0 +1,66 @@
<?php
namespace Drupal\etherpad_api;
use Drupal\Core\Config\ImmutableConfig;
use GuzzleHttp\ClientInterface;
/**
* Service description.
*/
class Client {
/**
* The HTTP client.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* The config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
protected $apiKey = null;
protected $baseUrl = null;
// (Minimal) Etherpad version
const API_VERSION = '1.2';
/**
* Constructs a Client object.
*
* @param \GuzzleHttp\ClientInterface $httpClient
* The HTTP client.
* @param \Drupal\Core\Config\ImmutableConfig $config
* The config.
*/
public function __construct(ClientInterface $httpClient, ImmutableConfig $config) {
$this->httpClient = $httpClient;
$this->config = $config;
$this->baseUrl = rtrim($config->get('url'), '/');
$this->apiKey = $config->get('key');
}
public function checkToken() {
return $this->request('get', '/checkToken');
}
/**
* Method description.
*/
public function request($method, $url) {
$uri = "{$this->baseUrl}/" . self::API_VERSION . $url;
if (str_contains($uri, '?')) {
$uri .= "&apikey={$this->apiKey}";
} else {
$uri .= "?apikey={$this->apiKey}";
}
return $this->httpClient->request($method, $uri);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Drupal\etherpad_api\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use GuzzleHttp\Exception\ClientException;
use Drupal\etherpad_api\Client;
/**
* Returns responses for Etherpad API routes.
*/
class EtherpadApiController extends ControllerBase {
protected $client;
public function __construct(Client $client) {
$this->client = $client;
}
public static function create(ContainerInterface $container) {
return new static($container->get('etherpad_api.client'));
}
/**
* Builds the response.
*/
public function build($components, Request $request) {
$uri = str_replace(':', '/', $components);
if ($params = $request->getQueryString()) {
$uri .= "?$params";
}
try {
return $this->client->request($request->getMethod(), $uri);
} catch (ClientException $exception) {
return new Response($exception->getMessage(), $exception->getCode());
}
}
}

View File

@ -3,47 +3,38 @@
namespace Drupal\etherpad_api\Controller; namespace Drupal\etherpad_api\Controller;
use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use GuzzleHttp\Exception\ClientException;
use Drupal\etherpad_api\Client;
/** /**
* Returns responses for Etherpad API routes. * Returns responses for Etherpad API routes.
*/ */
class EtherpadApiController extends ControllerBase { class EtherpadApiController extends ControllerBase {
protected $httpClient; protected $client;
protected $config;
public function __construct(ClientInterface $http_client, ConfigFactoryInterface $config_factory) { public function __construct(Client $client) {
$this->httpClient = $http_client; $this->client = $client;
$this->configFactory = $config_factory;
} }
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static($container->get('http_client'), $container->get('config.factory')); return new static($container->get('etherpad_api.client'));
} }
/** /**
* Builds the response. * Builds the response.
*/ */
public function build($components, Request $request) { public function build($components, Request $request) {
$baseUrl = $this->config('etherpad_api.settings')->get('url'); $uri = str_replace(':', '/', $components);
$apiKey = $this->config('etherpad_api.settings')->get('key'); if ($params = $request->getQueryString()) {
$uri .= "?$params";
$params = $request->getQueryString();
if ($params) {
$params .= "&apikey=$apiKey";
} else {
$params = "apikey=$apiKey";
} }
$uri = $baseUrl . str_replace(':', '/', $components) . "?$params";
$method = $request->getMethod();
try { try {
return $this->httpClient->request($method, $uri); return $this->client->request($request->getMethod(), $uri);
} catch (ClientException $exception) { } catch (ClientException $exception) {
return new Response($exception->getMessage(), $exception->getCode()); return new Response($exception->getMessage(), $exception->getCode());
} }

View File

@ -2,13 +2,29 @@
namespace Drupal\etherpad_api\Form; namespace Drupal\etherpad_api\Form;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
use Drupal\etherpad_api\Client;
/** /**
* Configure Etherpad API settings for this site. * Configure Etherpad API settings for this site.
*/ */
class SettingsForm extends ConfigFormBase { class SettingsForm extends ConfigFormBase {
protected $httpClient;
protected $settings;
public function __construct(ClientInterface $httpClient) {
$this->httpClient = $httpClient;
$this->settings = $this->config('etherpad_api.settings');
}
public static function create(ContainerInterface $container) {
return new static($container->get('http_client'));
}
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -31,12 +47,13 @@ class SettingsForm extends ConfigFormBase {
$form['url'] = [ $form['url'] = [
'#type' => 'textfield', '#type' => 'textfield',
'#title' => $this->t('Etherpad API URL'), '#title' => $this->t('Etherpad API URL'),
'#default_value' => $this->config('etherpad_api.settings')->get('url'), '#required' => true,
'#default_value' => $this->settings->get('url'),
]; ];
$form['key'] = [ $form['key'] = [
'#type' => 'textfield', '#type' => 'password',
'#title' => $this->t('Etherpad API key (found in instance root folder)'), '#title' => $this->t('Etherpad API key (found in instance root folder)'),
'#default_value' => $this->config('etherpad_api.settings')->get('key'), '#default_value' => $this->settings->get('key'),
]; ];
return parent::buildForm($form, $form_state); return parent::buildForm($form, $form_state);
} }
@ -45,11 +62,22 @@ class SettingsForm extends ConfigFormBase {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function validateForm(array &$form, FormStateInterface $form_state) { public function validateForm(array &$form, FormStateInterface $form_state) {
if (!$form_state->getValue('url')) { // Check API accessibility
$form_state->setErrorByName('url', $this->t('The value is required.')); $apiKey = $form_state->getValue('key');
if (!$apiKey) {
$apiKey = $this->settings->get('key');
} }
if (!$form_state->getValue('key')) { $baseUrl = $form_state->getValue('url');
$form_state->setErrorByName('key', $this->t('The value is required.'));
$url = rtrim($baseUrl, '/') . "/1.2/checkToken?apikey=$apiKey";
try {
$this->httpClient->request('get', $url);
} catch (ClientException $e) {
$form_state->setErrorByName('url', $this->t('Etherpad API not accessible (@code: @msg). The URL or the API key is wrong', [
'@value' => $e->getCode(),
'@msg' => $e->getMessage()
]));
} }
parent::validateForm($form, $form_state); parent::validateForm($form, $form_state);
} }
@ -58,10 +86,12 @@ class SettingsForm extends ConfigFormBase {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function submitForm(array &$form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('etherpad_api.settings') $this->settings->set('url', $form_state->getValue('url'));
->set('url', $form_state->getValue('url')) if ($form_state->getValue('key')) {
->set('key', $form_state->getValue('key')) $this->settings->set('key', $form_state->getValue('key'));
->save(); }
$this->settings->save();
parent::submitForm($form, $form_state); parent::submitForm($form, $form_state);
} }