kopia lustrzana https://github.com/nextcloud/social
Merge branch 'master' into fix/security/filter-mimetype
commit
f47ce64200
|
@ -41,7 +41,7 @@
|
|||
<field>
|
||||
<name>summary</name>
|
||||
<type>text</type>
|
||||
<length>500</length>
|
||||
<length>3000</length>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
|
||||
|
@ -198,7 +198,7 @@
|
|||
<field>
|
||||
<name>summary</name>
|
||||
<type>text</type>
|
||||
<length>255</length>
|
||||
<length>3000</length>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
|
||||
|
@ -362,7 +362,7 @@
|
|||
<field>
|
||||
<name>summary</name>
|
||||
<type>text</type>
|
||||
<length>500</length>
|
||||
<length>3000</length>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<name>Social</name>
|
||||
<summary>🎉 Nextcloud becomes part of the federated social networks!</summary>
|
||||
<description><![CDATA[test]]></description>
|
||||
<version>0.0.50</version>
|
||||
<version>0.0.51</version>
|
||||
<licence>agpl</licence>
|
||||
<author mail="maxence@artificial-owl.com">Maxence Lange</author>
|
||||
<author mail="jus@bitgrid.net">Julius Härtl</author>
|
||||
|
@ -28,12 +28,14 @@
|
|||
|
||||
<background-jobs>
|
||||
<job>OCA\Social\Cron\Cache</job>
|
||||
<job>OCA\Social\Cron\Queue</job>
|
||||
</background-jobs>
|
||||
|
||||
<commands>
|
||||
<command>OCA\Social\Command\CacheRefresh</command>
|
||||
<command>OCA\Social\Command\QueueStatus</command>
|
||||
<command>OCA\Social\Command\NoteCreate</command>
|
||||
<command>OCA\Social\Command\QueueStatus</command>
|
||||
<command>OCA\Social\Command\QueueProcess</command>
|
||||
</commands>
|
||||
|
||||
</info>
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/daita/my-small-php-tools.git",
|
||||
"reference": "12090dc3ae29d2eb49d5274ca3f6ebfb76ce5997"
|
||||
"reference": "56cff24fdde14d21e3903428c5ee629c839866af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/12090dc3ae29d2eb49d5274ca3f6ebfb76ce5997",
|
||||
"reference": "12090dc3ae29d2eb49d5274ca3f6ebfb76ce5997",
|
||||
"url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/56cff24fdde14d21e3903428c5ee629c839866af",
|
||||
"reference": "56cff24fdde14d21e3903428c5ee629c839866af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -40,7 +40,7 @@
|
|||
}
|
||||
],
|
||||
"description": "My small PHP Tools",
|
||||
"time": "2018-11-28T10:47:43+00:00"
|
||||
"time": "2018-11-28T13:07:27+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* Nextcloud - Social Support
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Maxence Lange <maxence@artificial-owl.com>
|
||||
* @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
namespace OCA\Social\Command;
|
||||
|
||||
|
||||
use Exception;
|
||||
use OC\Core\Command\Base;
|
||||
use OCA\Social\Exceptions\ActorDoesNotExistException;
|
||||
use OCA\Social\Exceptions\RequestException;
|
||||
use OCA\Social\Exceptions\SocialAppConfigException;
|
||||
use OCA\Social\Service\ActivityService;
|
||||
use OCA\Social\Service\ConfigService;
|
||||
use OCA\Social\Service\MiscService;
|
||||
use OCA\Social\Service\QueueService;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
|
||||
class QueueProcess extends Base {
|
||||
|
||||
|
||||
/** @var ActivityService */
|
||||
private $activityService;
|
||||
|
||||
/** @var QueueService */
|
||||
private $queueService;
|
||||
|
||||
/** @var ConfigService */
|
||||
private $configService;
|
||||
|
||||
/** @var MiscService */
|
||||
private $miscService;
|
||||
|
||||
|
||||
/**
|
||||
* NoteCreate constructor.
|
||||
*
|
||||
* @param ActivityService $activityService
|
||||
* @param QueueService $queueService
|
||||
* @param ConfigService $configService
|
||||
* @param MiscService $miscService
|
||||
*/
|
||||
public function __construct(
|
||||
ActivityService $activityService, QueueService $queueService, ConfigService $configService,
|
||||
MiscService $miscService
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->activityService = $activityService;
|
||||
$this->queueService = $queueService;
|
||||
$this->configService = $configService;
|
||||
$this->miscService = $miscService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
$this->setName('social:queue:process')
|
||||
->setDescription('Process the request queue');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$requests = $this->queueService->getRequestStandby($total = 0);
|
||||
|
||||
$output->writeLn('found a total of ' . $total . ' requests in the queue');
|
||||
if ($total === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeLn(sizeof($requests) . ' are processable at this time');
|
||||
if (sizeof($requests) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activityService->manageInit();
|
||||
foreach ($requests as $request) {
|
||||
$output->write('.');
|
||||
try {
|
||||
$this->activityService->manageRequest($request);
|
||||
} catch (RequestException $e) {
|
||||
} catch (SocialAppConfigException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeLn('done');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -32,9 +32,10 @@ namespace OCA\Social\Controller;
|
|||
|
||||
use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
|
||||
use Exception;
|
||||
use OC\AppFramework\Http;
|
||||
use OCA\Social\AppInfo\Application;
|
||||
use OCA\Social\Db\NotesRequest;
|
||||
use OCA\Social\Exceptions\SignatureException;
|
||||
use OCA\Social\Exceptions\SignatureIsGoneException;
|
||||
use OCA\Social\Exceptions\UnknownItemException;
|
||||
use OCA\Social\Service\ActivityPub\FollowService;
|
||||
use OCA\Social\Service\ActivityService;
|
||||
|
@ -124,7 +125,6 @@ class ActivityPubController extends Controller {
|
|||
* @param string $username
|
||||
*
|
||||
* @return Response
|
||||
* @throws \OC\User\NoUserException
|
||||
*/
|
||||
public function actor(string $username): Response {
|
||||
if (!$this->checkSourceActivityStreams()) {
|
||||
|
@ -184,6 +184,8 @@ class ActivityPubController extends Controller {
|
|||
}
|
||||
|
||||
return $this->success([]);
|
||||
} catch (SignatureIsGoneException $e) {
|
||||
return $this->fail($e, [], Http::STATUS_GONE);
|
||||
} catch (Exception $e) {
|
||||
return $this->fail($e);
|
||||
}
|
||||
|
@ -207,10 +209,13 @@ class ActivityPubController extends Controller {
|
|||
public function inbox(string $username): Response {
|
||||
|
||||
try {
|
||||
$actor = $this->actorService->getActor($username);
|
||||
|
||||
$this->activityService->checkRequest($this->request);
|
||||
$body = file_get_contents('php://input');
|
||||
|
||||
// TODO - check the recipient <-> username
|
||||
// $actor = $this->actorService->getActor($username);
|
||||
|
||||
$this->miscService->log('Inbox: ' . $body);
|
||||
|
||||
$activity = $this->importService->import($body);
|
||||
|
@ -220,6 +225,8 @@ class ActivityPubController extends Controller {
|
|||
}
|
||||
|
||||
return $this->success([]);
|
||||
} catch (SignatureIsGoneException $e) {
|
||||
return $this->fail($e, [], Http::STATUS_GONE);
|
||||
} catch (Exception $e) {
|
||||
return $this->fail($e);
|
||||
}
|
||||
|
|
|
@ -140,9 +140,14 @@ class LocalController extends Controller {
|
|||
$post->setType($this->get('type', $data, NoteService::TYPE_PUBLIC));
|
||||
|
||||
/** @var ACore $activity */
|
||||
$this->postService->createPost($post, $activity);
|
||||
$token = $this->postService->createPost($post, $activity);
|
||||
|
||||
return $this->directSuccess($activity->getObject());
|
||||
return $this->success(
|
||||
[
|
||||
'post' => $activity->getObject(),
|
||||
'token' => $token
|
||||
]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return $this->fail($e);
|
||||
}
|
||||
|
@ -415,7 +420,6 @@ class LocalController extends Controller {
|
|||
$cached = [];
|
||||
foreach ($documents as $id) {
|
||||
try {
|
||||
|
||||
$document = $this->documentService->cacheRemoteDocument($id);
|
||||
$cached[] = $document;
|
||||
} catch (Exception $e) {
|
||||
|
|
|
@ -40,6 +40,7 @@ use OCA\Social\AppInfo\Application;
|
|||
use OCA\Social\Exceptions\AccountAlreadyExistsException;
|
||||
use OCA\Social\Exceptions\SocialAppConfigException;
|
||||
use OCA\Social\Service\ActivityPub\DocumentService;
|
||||
use OCA\Social\Service\ActivityPub\PersonService;
|
||||
use OCA\Social\Service\ActorService;
|
||||
use OCA\Social\Service\ConfigService;
|
||||
use OCA\Social\Service\MiscService;
|
||||
|
@ -85,6 +86,9 @@ class NavigationController extends Controller {
|
|||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
/** @var PersonService */
|
||||
private $personService;
|
||||
|
||||
/**
|
||||
* NavigationController constructor.
|
||||
*
|
||||
|
@ -95,12 +99,14 @@ class NavigationController extends Controller {
|
|||
* @param ActorService $actorService
|
||||
* @param DocumentService $documentService
|
||||
* @param ConfigService $configService
|
||||
* @param PersonService $personService
|
||||
* @param MiscService $miscService
|
||||
* @param IL10N $l10n
|
||||
*/
|
||||
public function __construct(
|
||||
IRequest $request, $userId, IConfig $config, IURLGenerator $urlGenerator,
|
||||
ActorService $actorService, DocumentService $documentService, ConfigService $configService,
|
||||
PersonService $personService,
|
||||
MiscService $miscService, IL10N $l10n
|
||||
) {
|
||||
parent::__construct(Application::APP_NAME, $request);
|
||||
|
@ -112,6 +118,7 @@ class NavigationController extends Controller {
|
|||
$this->actorService = $actorService;
|
||||
$this->documentService = $documentService;
|
||||
$this->configService = $configService;
|
||||
$this->personService = $personService;
|
||||
$this->miscService = $miscService;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
@ -125,7 +132,6 @@ class NavigationController extends Controller {
|
|||
* @NoSubAdminRequired
|
||||
*
|
||||
* @return TemplateResponse
|
||||
* @throws NoUserException
|
||||
*/
|
||||
public function navigate($path = ''): TemplateResponse {
|
||||
$data = [
|
||||
|
@ -162,6 +168,10 @@ class NavigationController extends Controller {
|
|||
$data['serverData']['firstrun'] = true;
|
||||
} catch (AccountAlreadyExistsException $e) {
|
||||
// we do nothing
|
||||
} catch (NoUserException $e) {
|
||||
// well, should not happens
|
||||
} catch (SocialAppConfigException $e) {
|
||||
// neither.
|
||||
}
|
||||
|
||||
return new TemplateResponse(Application::APP_NAME, 'main', $data);
|
||||
|
@ -231,9 +241,13 @@ class NavigationController extends Controller {
|
|||
* @param $username
|
||||
*
|
||||
* @return RedirectResponse|PublicTemplateResponse
|
||||
* @throws NoUserException
|
||||
*/
|
||||
public function public($username) {
|
||||
// Redirect to external instances
|
||||
if (preg_match('/@[\w._-]+@[\w._-]+/', $username) === 1) {
|
||||
$actor = $this->personService->getFromAccount(substr($username, 1));
|
||||
return new RedirectResponse($actor->getUrl());
|
||||
}
|
||||
if (\OC::$server->getUserSession()
|
||||
->isLoggedIn()) {
|
||||
return $this->navigate();
|
||||
|
|
|
@ -98,10 +98,10 @@ class QueueController extends Controller {
|
|||
$this->async();
|
||||
|
||||
$requests = $this->queueService->getRequestFromToken($token, RequestQueue::STATUS_STANDBY);
|
||||
$this->activityService->manageInit();
|
||||
foreach ($requests as $request) {
|
||||
try {
|
||||
$this->activityService->manageRequest($request);
|
||||
} catch (ActorDoesNotExistException $e) {
|
||||
} catch (RequestException $e) {
|
||||
} catch (SocialAppConfigException $e) {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* Nextcloud - Social Support
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Maxence Lange <maxence@artificial-owl.com>
|
||||
* @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
namespace OCA\Social\Cron;
|
||||
|
||||
|
||||
use Exception;
|
||||
use OC\BackgroundJob\TimedJob;
|
||||
use OCA\Social\AppInfo\Application;
|
||||
use OCA\Social\Exceptions\ActorDoesNotExistException;
|
||||
use OCA\Social\Exceptions\RequestException;
|
||||
use OCA\Social\Exceptions\SocialAppConfigException;
|
||||
use OCA\Social\Service\ActivityPub\DocumentService;
|
||||
use OCA\Social\Service\ActivityPub\PersonService;
|
||||
use OCA\Social\Service\ActivityService;
|
||||
use OCA\Social\Service\ActorService;
|
||||
use OCA\Social\Service\CacheService;
|
||||
use OCA\Social\Service\ConfigService;
|
||||
use OCA\Social\Service\MiscService;
|
||||
use OCA\Social\Service\QueueService;
|
||||
use OCP\AppFramework\QueryException;
|
||||
|
||||
|
||||
/**
|
||||
* Class Queue
|
||||
*
|
||||
* @package OCA\Social\Cron
|
||||
*/
|
||||
class Queue extends TimedJob {
|
||||
|
||||
|
||||
/** @var ActivityService */
|
||||
private $activityService;
|
||||
|
||||
/** @var QueueService */
|
||||
private $queueService;
|
||||
|
||||
/** @var MiscService */
|
||||
private $miscService;
|
||||
|
||||
|
||||
/**
|
||||
* Cache constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->setInterval(12 * 60); // 12 minutes
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $argument
|
||||
*
|
||||
* @throws QueryException
|
||||
*/
|
||||
protected function run($argument) {
|
||||
$app = new Application();
|
||||
$c = $app->getContainer();
|
||||
|
||||
$this->queueService = $c->query(QueueService::class);
|
||||
$this->activityService = $c->query(ActivityService::class);
|
||||
$this->miscService = $c->query(MiscService::class);
|
||||
|
||||
$this->manageQueue();
|
||||
}
|
||||
|
||||
|
||||
private function manageQueue() {
|
||||
$requests = $this->queueService->getRequestStandby($total = 0);
|
||||
$this->activityService->manageInit();
|
||||
|
||||
foreach ($requests as $request) {
|
||||
try {
|
||||
$this->activityService->manageRequest($request);
|
||||
} catch (RequestException $e) {
|
||||
} catch (SocialAppConfigException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -60,32 +60,28 @@ class ActorsRequest extends ActorsRequestBuilder {
|
|||
* @param Person $actor
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
* @throws SocialAppConfigException
|
||||
*/
|
||||
public function create(Person $actor): string {
|
||||
|
||||
$id = $this->configService->getUrlSocial() . '@' . $actor->getPreferredUsername();
|
||||
|
||||
try {
|
||||
$qb = $this->getActorsInsertSql();
|
||||
$qb = $this->getActorsInsertSql();
|
||||
|
||||
$qb->setValue('id', $qb->createNamedParameter($id))
|
||||
$qb->setValue('id', $qb->createNamedParameter($id))
|
||||
// ->setValue('type', $qb->createNamedParameter($actor->getType()))
|
||||
->setValue('user_id', $qb->createNamedParameter($actor->getUserId()))
|
||||
->setValue('name', $qb->createNamedParameter($actor->getName()))
|
||||
->setValue('summary', $qb->createNamedParameter($actor->getSummary()))
|
||||
->setValue(
|
||||
'preferred_username', $qb->createNamedParameter($actor->getPreferredUsername())
|
||||
)
|
||||
->setValue('public_key', $qb->createNamedParameter($actor->getPublicKey()))
|
||||
->setValue('private_key', $qb->createNamedParameter($actor->getPrivateKey()));
|
||||
->setValue('user_id', $qb->createNamedParameter($actor->getUserId()))
|
||||
->setValue('name', $qb->createNamedParameter($actor->getName()))
|
||||
->setValue('summary', $qb->createNamedParameter($actor->getSummary()))
|
||||
->setValue(
|
||||
'preferred_username', $qb->createNamedParameter($actor->getPreferredUsername())
|
||||
)
|
||||
->setValue('public_key', $qb->createNamedParameter($actor->getPublicKey()))
|
||||
->setValue('private_key', $qb->createNamedParameter($actor->getPrivateKey()));
|
||||
|
||||
$qb->execute();
|
||||
$qb->execute();
|
||||
|
||||
return $id;
|
||||
} catch (\Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ class CacheDocumentsRequest extends CacheDocumentsRequestBuilder {
|
|||
$qb = $this->getCacheDocumentsSelectSql();
|
||||
$this->limitToDBFieldEmpty($qb, 'local_copy');
|
||||
$this->limitToCaching($qb, self::CACHING_TIMEOUT);
|
||||
$this->limitToDBFieldInt($qb, 'error', 0);
|
||||
|
||||
$documents = [];
|
||||
$cursor = $qb->execute();
|
||||
|
|
|
@ -361,14 +361,6 @@ class CoreRequestBuilder {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param IQueryBuilder $qb
|
||||
*/
|
||||
protected function orderByPriority(IQueryBuilder &$qb) {
|
||||
$qb->orderBy('priority', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param IQueryBuilder $qb
|
||||
* @param string $field
|
||||
|
|
|
@ -57,18 +57,6 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
|
|||
public function multiple(array $queues) {
|
||||
foreach ($queues as $queue) {
|
||||
$this->create($queue);
|
||||
// $qb->values(
|
||||
// [
|
||||
// 'source' => $qb->createNamedParameter($queue->getSource()),
|
||||
// 'activity' => $qb->createNamedParameter($queue->getActivity()),
|
||||
// 'instance' => $qb->createNamedParameter(
|
||||
// json_encode($queue->getInstance(), JSON_UNESCAPED_SLASHES)
|
||||
// ),
|
||||
// 'status' => $qb->createNamedParameter($queue->getStatus()),
|
||||
// 'tries' => $qb->createNamedParameter($queue->getTries()),
|
||||
// 'last' => $qb->createNamedParameter($queue->getLast())
|
||||
// ]
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,14 +80,34 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
|
|||
)
|
||||
->setValue('priority', $qb->createNamedParameter($queue->getPriority()))
|
||||
->setValue('status', $qb->createNamedParameter($queue->getStatus()))
|
||||
->setValue('tries', $qb->createNamedParameter($queue->getTries()))
|
||||
->setValue('last', $qb->createNamedParameter($queue->getLast()));
|
||||
->setValue('tries', $qb->createNamedParameter($queue->getTries()));
|
||||
$qb->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return Actor from database based on the username
|
||||
* return Queue from database based on the status=0
|
||||
*
|
||||
* @return RequestQueue[]
|
||||
*/
|
||||
public function getStandby(): array {
|
||||
$qb = $this->getQueueSelectSql();
|
||||
$this->limitToStatus($qb, RequestQueue::STATUS_STANDBY);
|
||||
$qb->orderBy('id', 'asc');
|
||||
|
||||
$requests = [];
|
||||
$cursor = $qb->execute();
|
||||
while ($data = $cursor->fetch()) {
|
||||
$requests[] = $this->parseQueueSelectSql($data);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $requests;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return Queue from database based on the token
|
||||
*
|
||||
* @param string $token
|
||||
* @param int $status
|
||||
|
@ -114,7 +122,7 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
|
|||
$this->limitToStatus($qb, $status);
|
||||
}
|
||||
|
||||
$this->orderByPriority($qb);
|
||||
$qb->orderBy('priority', 'desc');
|
||||
|
||||
$requests = [];
|
||||
$cursor = $qb->execute();
|
||||
|
@ -180,9 +188,11 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
|
|||
*/
|
||||
public function setAsFailure(RequestQueue &$queue) {
|
||||
$qb = $this->getQueueUpdateSql();
|
||||
$qb->set('status', $qb->createNamedParameter(RequestQueue::STATUS_STANDBY));
|
||||
// TODO - increment tries++
|
||||
// ->set('tries', 'tries+1');
|
||||
$func = $qb->func();
|
||||
$expr = $qb->expr();
|
||||
|
||||
$qb->set('status', $qb->createNamedParameter(RequestQueue::STATUS_STANDBY))
|
||||
->set('tries', $func->add('tries', $expr->literal(1)));
|
||||
$this->limitToId($qb, $queue->getId());
|
||||
$this->limitToStatus($qb, RequestQueue::STATUS_RUNNING);
|
||||
|
||||
|
@ -195,5 +205,13 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
|
|||
$queue->setStatus(RequestQueue::STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
public function delete(RequestQueue $queue) {
|
||||
$qb = $this->getQueueDeleteSql();
|
||||
$this->limitToId($qb, $queue->getId());
|
||||
|
||||
$qb->execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\Social\Exceptions;
|
||||
|
||||
class Request410Exception extends \Exception {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\Social\Exceptions;
|
||||
|
||||
class SignatureIsGoneException extends \Exception {
|
||||
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ namespace OCA\Social\Model;
|
|||
|
||||
|
||||
use daita\MySmallPhpTools\Traits\TArrayTools;
|
||||
use DateTime;
|
||||
use JsonSerializable;
|
||||
|
||||
|
||||
|
@ -299,9 +300,15 @@ class RequestQueue implements JsonSerializable {
|
|||
$this->setActivity($this->get('activity', $data, ''));
|
||||
$this->setStatus($this->getInt('status', $data, 0));
|
||||
$this->setTries($this->getInt('tries', $data, 0));
|
||||
$this->setLast($this->getInt('last', $data, 0));
|
||||
}
|
||||
|
||||
$last = $this->get('last', $data, '');
|
||||
if ($last === '') {
|
||||
$this->setLast(0);
|
||||
} else {
|
||||
$dTime = new DateTime($last);
|
||||
$this->setLast($dTime->getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
|
|
|
@ -38,6 +38,7 @@ use OCA\Social\Db\CacheDocumentsRequest;
|
|||
use OCA\Social\Exceptions\CacheActorDoesNotExistException;
|
||||
use OCA\Social\Exceptions\CacheDocumentDoesNotExistException;
|
||||
use OCA\Social\Exceptions\InvalidResourceException;
|
||||
use OCA\Social\Exceptions\Request410Exception;
|
||||
use OCA\Social\Exceptions\RequestException;
|
||||
use OCA\Social\Exceptions\SocialAppConfigException;
|
||||
use OCA\Social\Exceptions\UrlCloudException;
|
||||
|
@ -124,6 +125,7 @@ class PersonService implements ICoreService {
|
|||
* @throws RequestException
|
||||
* @throws SocialAppConfigException
|
||||
* @throws UrlCloudException
|
||||
* @throws Request410Exception
|
||||
*/
|
||||
public function getFromId(string $id, bool $refresh = false): Person {
|
||||
|
||||
|
|
|
@ -43,8 +43,10 @@ use OCA\Social\Exceptions\EmptyQueueException;
|
|||
use OCA\Social\Exceptions\InvalidResourceException;
|
||||
use OCA\Social\Exceptions\NoHighPriorityRequestException;
|
||||
use OCA\Social\Exceptions\QueueStatusException;
|
||||
use OCA\Social\Exceptions\Request410Exception;
|
||||
use OCA\Social\Exceptions\RequestException;
|
||||
use OCA\Social\Exceptions\SignatureException;
|
||||
use OCA\Social\Exceptions\SignatureIsGoneException;
|
||||
use OCA\Social\Exceptions\SocialAppConfigException;
|
||||
use OCA\Social\Exceptions\UrlCloudException;
|
||||
use OCA\Social\Model\ActivityPub\ACore;
|
||||
|
@ -105,6 +107,10 @@ class ActivityService {
|
|||
private $miscService;
|
||||
|
||||
|
||||
/** @var array */
|
||||
private $failInstances;
|
||||
|
||||
|
||||
/**
|
||||
* ActivityService constructor.
|
||||
*
|
||||
|
@ -231,10 +237,12 @@ class ActivityService {
|
|||
$author = $this->getAuthorFromItem($activity);
|
||||
$instancePaths = $this->generateInstancePaths($activity);
|
||||
$token = $this->queueService->generateRequestQueue($instancePaths, $activity, $author);
|
||||
$this->manageInit();
|
||||
|
||||
try {
|
||||
$directRequest = $this->queueService->getPriorityRequest($token);
|
||||
$this->manageRequest($directRequest);
|
||||
} catch (RequestException $e) {
|
||||
} catch (NoHighPriorityRequestException $e) {
|
||||
} catch (EmptyQueueException $e) {
|
||||
return '';
|
||||
|
@ -246,14 +254,23 @@ class ActivityService {
|
|||
}
|
||||
|
||||
|
||||
public function manageInit() {
|
||||
$this->failInstances = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param RequestQueue $queue
|
||||
*
|
||||
* @throws ActorDoesNotExistException
|
||||
* @throws RequestException
|
||||
* @throws SocialAppConfigException
|
||||
*/
|
||||
public function manageRequest(RequestQueue $queue) {
|
||||
$host = $queue->getInstance()
|
||||
->getAddress();
|
||||
if (in_array($host, $this->failInstances)) {
|
||||
throw new RequestException();
|
||||
}
|
||||
|
||||
try {
|
||||
$this->queueService->initRequest($queue);
|
||||
|
@ -261,15 +278,22 @@ class ActivityService {
|
|||
return;
|
||||
}
|
||||
|
||||
$result = $this->generateRequest(
|
||||
$queue->getInstance(), $queue->getActivity(), $queue->getAuthor()
|
||||
);
|
||||
try {
|
||||
$result = $this->generateRequest(
|
||||
$queue->getInstance(), $queue->getActivity(), $queue->getAuthor()
|
||||
);
|
||||
} catch (ActorDoesNotExistException $e) {
|
||||
$this->queueService->deleteRequest($queue);
|
||||
} catch (Request410Exception $e) {
|
||||
$this->queueService->deleteRequest($queue);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->getint('_code', $result, 500) === 202) {
|
||||
$this->queueService->endRequest($queue, true);
|
||||
} else {
|
||||
$this->queueService->endRequest($queue, false);
|
||||
$this->failInstances[] = $host;
|
||||
}
|
||||
} catch (QueueStatusException $e) {
|
||||
}
|
||||
|
@ -339,6 +363,7 @@ class ActivityService {
|
|||
*
|
||||
* @return Request[]
|
||||
* @throws ActorDoesNotExistException
|
||||
* @throws Request410Exception
|
||||
* @throws RequestException
|
||||
* @throws SocialAppConfigException
|
||||
*/
|
||||
|
@ -385,6 +410,9 @@ class ActivityService {
|
|||
* @throws MalformedArrayException
|
||||
* @throws RequestException
|
||||
* @throws SignatureException
|
||||
* @throws SocialAppConfigException
|
||||
* @throws UrlCloudException
|
||||
* @throws SignatureIsGoneException
|
||||
*/
|
||||
public function checkRequest(IRequest $request) {
|
||||
$dTime = new DateTime($request->getHeader('date'));
|
||||
|
@ -394,7 +422,12 @@ class ActivityService {
|
|||
throw new SignatureException('object is too old');
|
||||
}
|
||||
|
||||
$this->checkSignature($request);
|
||||
try {
|
||||
$this->checkSignature($request);
|
||||
} catch (Request410Exception $e) {
|
||||
throw new SignatureIsGoneException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -429,9 +462,12 @@ class ActivityService {
|
|||
* @param IRequest $request
|
||||
*
|
||||
* @throws InvalidResourceException
|
||||
* @throws MalformedArrayException
|
||||
* @throws Request410Exception
|
||||
* @throws RequestException
|
||||
* @throws SignatureException
|
||||
* @throws MalformedArrayException
|
||||
* @throws SocialAppConfigException
|
||||
* @throws UrlCloudException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkSignature(IRequest $request) {
|
||||
|
@ -508,6 +544,7 @@ class ActivityService {
|
|||
* @throws RequestException
|
||||
* @throws SocialAppConfigException
|
||||
* @throws UrlCloudException
|
||||
* @throws Request410Exception
|
||||
*/
|
||||
private function retrieveKey($keyId): string {
|
||||
$actor = $this->personService->getFromId($keyId);
|
||||
|
|
|
@ -142,7 +142,7 @@ class ActorService {
|
|||
*
|
||||
* @throws AccountAlreadyExistsException
|
||||
* @throws NoUserException
|
||||
* @throws Exception
|
||||
* @throws SocialAppConfigException
|
||||
*/
|
||||
public function createActor(string $userId, string $username) {
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ namespace OCA\Social\Service;
|
|||
use daita\MySmallPhpTools\Model\Request;
|
||||
use daita\MySmallPhpTools\Traits\TArrayTools;
|
||||
use daita\MySmallPhpTools\Traits\TPathTools;
|
||||
use Exception;
|
||||
use OCA\Social\Exceptions\Request410Exception;
|
||||
use OCA\Social\Exceptions\RequestException;
|
||||
use OCA\Social\Exceptions\SocialAppConfigException;
|
||||
|
||||
|
@ -71,6 +73,7 @@ class CurlService {
|
|||
*
|
||||
* @return array
|
||||
* @throws RequestException
|
||||
* @throws Request410Exception
|
||||
*/
|
||||
public function request(Request $request): array {
|
||||
$curl = $this->initRequest($request);
|
||||
|
@ -85,6 +88,7 @@ class CurlService {
|
|||
$this->parseRequestResultCode301($code);
|
||||
// $this->parseRequestResultCode401($code);
|
||||
$this->parseRequestResultCode404($code, $request);
|
||||
$this->parseRequestResultCode410($code);
|
||||
// $this->parseRequestResultCode503($code);
|
||||
// $this->parseRequestResultCode500($code);
|
||||
// $this->parseRequestResult($result);
|
||||
|
@ -130,7 +134,7 @@ class CurlService {
|
|||
$request->setAddress($host);
|
||||
try {
|
||||
$this->request($request);
|
||||
} catch (RequestException $e) {
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,11 +238,24 @@ class CurlService {
|
|||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @throws RequestException
|
||||
* @throws Request410Exception
|
||||
*/
|
||||
private function parseRequestResultCode404(int $code, Request $request) {
|
||||
if ($code === 404) {
|
||||
throw new RequestException('404 Not Found - ' . json_encode($request));
|
||||
throw new Request410Exception('404 Not Found - ' . json_encode($request));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @throws Request410Exception
|
||||
*/
|
||||
private function parseRequestResultCode410(int $code) {
|
||||
if ($code === 410) {
|
||||
throw new Request410Exception();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ use daita\MySmallPhpTools\Model\Request;
|
|||
use daita\MySmallPhpTools\Traits\TArrayTools;
|
||||
use daita\MySmallPhpTools\Traits\TPathTools;
|
||||
use OCA\Social\Exceptions\InvalidResourceException;
|
||||
use OCA\Social\Exceptions\Request410Exception;
|
||||
use OCA\Social\Exceptions\RequestException;
|
||||
use OCA\Social\Model\ActivityPub\ACore;
|
||||
use OCA\Social\Model\Instance;
|
||||
|
@ -79,12 +80,12 @@ class InstanceService {
|
|||
* @return mixed
|
||||
* @throws RequestException
|
||||
* @throws InvalidResourceException
|
||||
* @throws Request410Exception
|
||||
*/
|
||||
public function retrieveAccount(string $account) {
|
||||
$account = $this->withoutBeginAt($account);
|
||||
|
||||
if (strstr(substr($account, 0, -3), '@') === false)
|
||||
{
|
||||
if (strstr(substr($account, 0, -3), '@') === false) {
|
||||
throw new InvalidResourceException();
|
||||
}
|
||||
list($username, $host) = explode('@', $account);
|
||||
|
@ -113,6 +114,7 @@ class InstanceService {
|
|||
*
|
||||
* @return mixed
|
||||
* @throws RequestException
|
||||
* @throws Request410Exception
|
||||
*/
|
||||
public function retrieveObject($id) {
|
||||
$url = parse_url($id);
|
||||
|
|
|
@ -147,6 +147,22 @@ class QueueService {
|
|||
}
|
||||
|
||||
|
||||
public function getRequestStandby(int &$total = 0): array {
|
||||
$requests = $this->requestQueueRequest->getStandby();
|
||||
$total = sizeof($requests);
|
||||
|
||||
$result = [];
|
||||
foreach ($requests as $request) {
|
||||
$delay = floor(pow($request->getTries(), 4) / 3);
|
||||
if ($request->getLast() < (time() - $delay)) {
|
||||
$result[] = $request;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
* @param int $status
|
||||
|
@ -186,6 +202,12 @@ class QueueService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestQueue $queue
|
||||
*/
|
||||
public function deleteRequest(RequestQueue $queue) {
|
||||
$this->requestQueueRequest->delete($queue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
37
src/App.vue
37
src/App.vue
|
@ -4,7 +4,8 @@
|
|||
<app-navigation :menu="menu" />
|
||||
</div>
|
||||
<div id="app-content">
|
||||
<router-view :key="$route.fullPath" />
|
||||
<Search v-if="searchTerm != ''" :term="searchTerm" />
|
||||
<router-view v-if="searchTerm === ''" :key="$route.fullPath" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="setup">
|
||||
|
@ -30,6 +31,7 @@
|
|||
.app-social {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.setup {
|
||||
margin: auto;
|
||||
width: 700px;
|
||||
|
@ -38,6 +40,16 @@
|
|||
.setup input[type=url] {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#app-content .social__wrapper {
|
||||
margin: 15px calc(50% - 350px - 75px);
|
||||
}
|
||||
|
||||
#social-spacer a:hover,
|
||||
#social-spacer a:focus {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
@ -50,6 +62,7 @@ import {
|
|||
import axios from 'nextcloud-axios'
|
||||
import TimelineEntry from './components/TimelineEntry'
|
||||
import ProfileInfo from './components/ProfileInfo'
|
||||
import Search from './components/Search'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
@ -59,13 +72,15 @@ export default {
|
|||
TimelineEntry,
|
||||
Multiselect,
|
||||
Avatar,
|
||||
ProfileInfo
|
||||
ProfileInfo,
|
||||
Search
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
infoHidden: false,
|
||||
state: [],
|
||||
cloudAddress: ''
|
||||
cloudAddress: '',
|
||||
searchTerm: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -116,8 +131,7 @@ export default {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: 'social-spacer',
|
||||
classes: []
|
||||
id: 'social-spacer'
|
||||
},
|
||||
{
|
||||
id: 'social-local',
|
||||
|
@ -146,12 +160,19 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
this.searchTerm = ''
|
||||
}
|
||||
},
|
||||
beforeMount: function() {
|
||||
// importing server data into the store
|
||||
const serverDataElmt = document.getElementById('serverData')
|
||||
if (serverDataElmt !== null) {
|
||||
this.$store.commit('setServerData', JSON.parse(document.getElementById('serverData').dataset.server))
|
||||
}
|
||||
|
||||
this.search = new OCA.Search(this.search, this.resetSearch)
|
||||
},
|
||||
methods: {
|
||||
hideInfo() {
|
||||
|
@ -162,6 +183,12 @@ export default {
|
|||
this.$store.commit('setServerDataEntry', 'setup', false)
|
||||
this.$store.commit('setServerDataEntry', 'cloudAddress', this.cloudAddress)
|
||||
})
|
||||
},
|
||||
search(term) {
|
||||
this.searchTerm = term
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchTerm = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,11 +58,11 @@
|
|||
</emoji-picker>
|
||||
|
||||
<div class="options">
|
||||
<input :value="t('social', 'Post')" :disabled="post.length < 1" class="submit primary"
|
||||
<input :value="currentVisibilityPostLabel" :disabled="post.length < 1" class="submit primary"
|
||||
type="submit" title="" data-original-title="Post">
|
||||
<div>
|
||||
<button :class="currentVisibilityIconClass" @click.prevent="togglePopoverMenu" />
|
||||
<div :class="{open: menuOpened}" class="popovermenu">
|
||||
<div :class="{open: menuOpened}" class="popovermenu menu-center">
|
||||
<PopoverMenu :menu="visibilityPopover" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -154,6 +154,7 @@
|
|||
padding: 5px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: 44px;
|
||||
}
|
||||
.emoji-picker > div {
|
||||
overflow: hidden;
|
||||
|
@ -167,6 +168,9 @@
|
|||
margin: 3px;
|
||||
width: 16px;
|
||||
}
|
||||
.popovermenu {
|
||||
top: 55px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
/* Tribute-specific styles TODO: properly scope component css */
|
||||
|
@ -289,7 +293,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'public',
|
||||
type: localStorage.getItem('social.lastPostType') || 'followers',
|
||||
post: '',
|
||||
canType: true,
|
||||
search: '',
|
||||
|
@ -308,18 +312,19 @@ export default {
|
|||
+ '<a href="' + item.original.url + '" target="_blank"><img src="' + item.original.avatar + '" />@' + item.original.value + '</a></span>'
|
||||
},
|
||||
values: (text, cb) => {
|
||||
let users = []
|
||||
|
||||
if (text.length < 1) {
|
||||
cb([])
|
||||
cb(users)
|
||||
}
|
||||
this.remoteSearch(text).then((result) => {
|
||||
let users = []
|
||||
if (result.data.result.exact) {
|
||||
let user = result.data.result.exact
|
||||
users.push({
|
||||
key: user.preferredUsername,
|
||||
value: user.account,
|
||||
url: user.url,
|
||||
avatar: 'http://localhost:8000/index.php/avatar/admin/32?v=0' // TODO: use real avatar from server
|
||||
avatar: user.local ? OC.generateUrl(`/avatar/${user.preferredUsername}/32`) : ''// TODO: use real avatar from server
|
||||
})
|
||||
}
|
||||
for (var i in result.data.result.accounts) {
|
||||
|
@ -328,7 +333,7 @@ export default {
|
|||
key: user.preferredUsername,
|
||||
value: user.account,
|
||||
url: user.url,
|
||||
avatar: 'http://localhost:8000/index.php/avatar/admin/32?v=0' // TODO: use real avatar from server
|
||||
avatar: user.local ? OC.generateUrl(`/avatar/${user.preferredUsername}/32`) : ''// TODO: use real avatar from server
|
||||
})
|
||||
}
|
||||
cb(users)
|
||||
|
@ -360,20 +365,40 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
currentVisibilityPostLabel() {
|
||||
return this.visibilityPostLabel(this.type)
|
||||
},
|
||||
visibilityPostLabel() {
|
||||
return (type) => {
|
||||
if (typeof type === 'undefined') {
|
||||
type = this.type
|
||||
}
|
||||
switch (type) {
|
||||
case 'public':
|
||||
return t('social', 'Post publicly')
|
||||
case 'followers':
|
||||
return t('social', 'Post to followers')
|
||||
case 'direct':
|
||||
return t('social', 'Post to recipients')
|
||||
case 'unlisted':
|
||||
return t('social', 'Post unlisted')
|
||||
}
|
||||
}
|
||||
},
|
||||
visibilityPopover() {
|
||||
return [
|
||||
{
|
||||
action: () => { this.switchType('public') },
|
||||
icon: this.visibilityIconClass('public'),
|
||||
text: t('social', 'Public'),
|
||||
longtext: t('social', 'Post to public timelines')
|
||||
},
|
||||
{
|
||||
action: () => { this.switchType('direct') },
|
||||
icon: this.visibilityIconClass('direct'),
|
||||
text: t('social', 'Direct'),
|
||||
longtext: t('social', 'Post to mentioned users only')
|
||||
},
|
||||
{
|
||||
action: () => { this.switchType('unlisted') },
|
||||
icon: this.visibilityIconClass('unlisted'),
|
||||
text: t('social', 'Unlisted'),
|
||||
longtext: t('social', 'Do not post to public timelines')
|
||||
},
|
||||
{
|
||||
action: () => { this.switchType('followers') },
|
||||
icon: this.visibilityIconClass('followers'),
|
||||
|
@ -381,10 +406,10 @@ export default {
|
|||
longtext: t('social', 'Post to followers only')
|
||||
},
|
||||
{
|
||||
action: () => { this.switchType('unlisted') },
|
||||
icon: this.visibilityIconClass('unlisted'),
|
||||
text: t('social', 'Unlisted'),
|
||||
longtext: t('social', 'Do not post to public timelines')
|
||||
action: () => { this.switchType('public') },
|
||||
icon: this.visibilityIconClass('public'),
|
||||
text: t('social', 'Public'),
|
||||
longtext: t('social', 'Post to public timelines')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -400,6 +425,7 @@ export default {
|
|||
switchType(type) {
|
||||
this.type = type
|
||||
this.menuOpened = false
|
||||
localStorage.setItem('social.lastPostType', type)
|
||||
},
|
||||
getPostData() {
|
||||
let element = this.$refs.composerInput.cloneNode(true)
|
||||
|
|
|
@ -33,13 +33,13 @@
|
|||
|
||||
<ul class="user-profile--sections">
|
||||
<li>
|
||||
<router-link :to="{ name: 'profile', params: { account: $route.params.account }}" class="icon-category-monitoring">{{ accountInfo.posts }} posts</router-link>
|
||||
<router-link :to="{ name: 'profile', params: { account: uid } }" class="icon-category-monitoring">{{ accountInfo.posts }} posts</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'following', params: { account: $route.params.account }}" class="icon-category-social">{{ accountInfo.following }} following</router-link>
|
||||
<router-link :to="{ name: 'profile.following', params: { account: uid } }" class="icon-category-social">{{ accountInfo.following }} following</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'followers', params: { account: $route.params.account }}" class="icon-category-social">{{ accountInfo.followers }} followers</router-link>
|
||||
<router-link :to="{ name: 'profile.followers', params: { account: uid } }" class="icon-category-social">{{ accountInfo.followers }} followers</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="social__wrapper">
|
||||
<div v-if="results.length < 1" id="emptycontent" :class="{'icon-loading': loading}"
|
||||
class="">
|
||||
<div class="icon-search" />
|
||||
<h2>{{ t('social', 'No accounts found') }}</h2>
|
||||
<p>No accounts found for {{ term }}</p>
|
||||
</div>
|
||||
<div v-if="match || results.length > 0">
|
||||
<h3>{{ t('social', 'Search') }} {{ term }}</h3>
|
||||
<UserEntry :item="match" />
|
||||
<UserEntry v-for="result in results" :key="result.id" :item="result" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import UserEntry from './UserEntry'
|
||||
import axios from 'nextcloud-axios'
|
||||
|
||||
export default {
|
||||
name: 'Search',
|
||||
components: {
|
||||
UserEntry
|
||||
},
|
||||
props: {
|
||||
term: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
loading: false,
|
||||
match: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
term(val) {
|
||||
this.loading = true
|
||||
this.accountSearch(val).then((response) => {
|
||||
this.results = response.data.result.accounts
|
||||
this.loading = false
|
||||
})
|
||||
const re = /@((\w+)(@[\w.]+)?)/g
|
||||
if (val.match(re)) {
|
||||
this.remoteSearch(val).then((response) => {
|
||||
this.match = response.data.result.account
|
||||
}).catch((e) => { this.match = null })
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
accountSearch(term) {
|
||||
this.loading = true
|
||||
return axios.get(OC.generateUrl('apps/social/api/v1/accounts/search?search=' + term))
|
||||
},
|
||||
remoteSearch(term) {
|
||||
return axios.get(OC.generateUrl('apps/social/api/v1/account/info?account=' + term))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -11,6 +11,10 @@
|
|||
<span class="post-author">{{ item.actor_info.preferredUsername }}</span>
|
||||
<span class="post-author-id">{{ item.actor_info.account }}</span>
|
||||
</router-link>
|
||||
<a v-else-if="item.local" :href="item.id">
|
||||
<span class="post-author">{{ item.actor_info.preferredUsername }}</span>
|
||||
<span class="post-author-id">{{ item.actor_info.account }}</span>
|
||||
</a>
|
||||
<a v-else :href="item.actor_info.url">
|
||||
<span class="post-author">{{ item.actor_info.preferredUsername }}</span>
|
||||
<span class="post-author-id">{{ item.actor_info.account }}</span>
|
||||
|
@ -44,7 +48,13 @@ export default {
|
|||
formatedMessage: function() {
|
||||
let message = this.item.content
|
||||
message = message.replace(/(?:\r\n|\r|\n)/g, '<br />')
|
||||
message = message.linkify()
|
||||
message = message.linkify({
|
||||
formatHref: {
|
||||
email: function(href) {
|
||||
return OC.generateUrl('/apps/social/@' + (href.indexOf('mailto:') === 0 ? href.substring(7) : href))
|
||||
}
|
||||
}
|
||||
})
|
||||
message = this.$twemoji.parse(message)
|
||||
return message
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="user-entry">
|
||||
<div class="entry-content">
|
||||
<div class="user-avatar">
|
||||
<avatar v-if="item.local" :size="32" :user="item.preferredUsername" />
|
||||
<avatar v-else url="" />
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<router-link v-if="item.local" :to="{ name: 'profile', params: { account: item.account }}">
|
||||
<span class="post-author">{{ item.preferredUsername }}</span>
|
||||
</router-link>
|
||||
<a v-else href="{{ item.id }}" target="_blank"
|
||||
rel="noreferrer">{{ item.preferredUsername }}</a>
|
||||
<p class="user-description">{{ item.account }}</p>
|
||||
</div>
|
||||
<button v-if="item.following" class="icon-checkmark-color">Following</button>
|
||||
<button v-else class="primary">Follow</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Avatar } from 'nextcloud-vue'
|
||||
|
||||
export default {
|
||||
name: 'UserEntry',
|
||||
components: {
|
||||
Avatar
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, default: () => {} }
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.user-entry {
|
||||
padding: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
margin: 5px;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.entry-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.user-description {
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
|
@ -53,29 +53,32 @@ export default new Router({
|
|||
},
|
||||
{
|
||||
path: '/:index(index.php/)?apps/social/account/@:account',
|
||||
alias: './timeline',
|
||||
components: {
|
||||
default: Profile
|
||||
default: Profile,
|
||||
details: ProfileTimeline
|
||||
},
|
||||
props: true,
|
||||
name: 'profile',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'profile',
|
||||
components: {
|
||||
details: ProfileTimeline
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'followers',
|
||||
name: 'profile.followers',
|
||||
components: {
|
||||
default: Profile,
|
||||
details: ProfileFollowers
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'following',
|
||||
name: 'profile.following',
|
||||
|
||||
components: {
|
||||
default: Profile,
|
||||
details: ProfileFollowers
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ const actions = {
|
|||
},
|
||||
post(context, post) {
|
||||
return axios.post(OC.generateUrl('apps/social/api/v1/post'), { data: post }).then((response) => {
|
||||
context.commit('addPost', { data: response.data })
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Post created with token ' + response.data.result.token)
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to create a post')
|
||||
console.error('Failed to create a post', error)
|
||||
|
|
|
@ -21,31 +21,49 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div class="social__timeline">
|
||||
<ul>
|
||||
<li>User List</li>
|
||||
</ul>
|
||||
<div class="social__followers">
|
||||
<user-entry v-for="user in users" :item="user" :key="user.id" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.social__timeline {
|
||||
.social__followers {
|
||||
max-width: 700px;
|
||||
margin: 15px auto;
|
||||
display: flex;
|
||||
}
|
||||
.user-entry {
|
||||
width: 50%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import TimelineEntry from './../components/TimelineEntry'
|
||||
import UserEntry from '../components/UserEntry'
|
||||
|
||||
export default {
|
||||
name: 'ProfileFollowers',
|
||||
components: {
|
||||
TimelineEntry
|
||||
UserEntry
|
||||
},
|
||||
computed: {
|
||||
timeline: function() {
|
||||
return this.$store.getters.getTimeline
|
||||
},
|
||||
users() {
|
||||
return [
|
||||
{
|
||||
id: 'admin',
|
||||
displayName: 'Administrator',
|
||||
description: 'My social account',
|
||||
following: true
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
displayName: 'Administrator',
|
||||
description: 'My social account',
|
||||
following: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="social__wrapper">
|
||||
{{ type }}
|
||||
<div class="social__container">
|
||||
<transition name="slide-fade">
|
||||
<div v-if="showInfo" class="social__welcome">
|
||||
|
|
Ładowanie…
Reference in New Issue