Contact suggestions are now cached

This commit is contained in:
Michael 2022-11-30 05:59:27 +00:00
parent 8eda9dfe7c
commit f31e617f5d
13 changed files with 218 additions and 17 deletions

View file

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2022.12-dev (Giant Rhubarb) -- Friendica 2022.12-dev (Giant Rhubarb)
-- DB_UPDATE_VERSION 1495 -- DB_UPDATE_VERSION 1496
-- ------------------------------------------ -- ------------------------------------------
@ -309,6 +309,20 @@ CREATE TABLE IF NOT EXISTS `2fa_trusted_browser` (
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Two-factor authentication trusted browsers'; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Two-factor authentication trusted browsers';
--
-- TABLE account-suggestion
--
CREATE TABLE IF NOT EXISTS `account-suggestion` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the account url',
`uid` mediumint unsigned NOT NULL COMMENT 'User ID',
`level` smallint unsigned COMMENT 'level of closeness',
`ignore` boolean NOT NULL DEFAULT '0' COMMENT 'If set, this account will not be suggested again',
PRIMARY KEY(`uid`,`uri-id`),
INDEX `uri-id_uid` (`uri-id`,`uid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Account suggestion';
-- --
-- TABLE account-user -- TABLE account-user
-- --

View file

@ -8,6 +8,7 @@ Database Tables
| [2fa_app_specific_password](help/database/db_2fa_app_specific_password) | Two-factor app-specific _password | | [2fa_app_specific_password](help/database/db_2fa_app_specific_password) | Two-factor app-specific _password |
| [2fa_recovery_codes](help/database/db_2fa_recovery_codes) | Two-factor authentication recovery codes | | [2fa_recovery_codes](help/database/db_2fa_recovery_codes) | Two-factor authentication recovery codes |
| [2fa_trusted_browser](help/database/db_2fa_trusted_browser) | Two-factor authentication trusted browsers | | [2fa_trusted_browser](help/database/db_2fa_trusted_browser) | Two-factor authentication trusted browsers |
| [account-suggestion](help/database/db_account-suggestion) | Account suggestion |
| [account-user](help/database/db_account-user) | Remote and local accounts | | [account-user](help/database/db_account-user) | Remote and local accounts |
| [addon](help/database/db_addon) | registered addons | | [addon](help/database/db_addon) | registered addons |
| [apcontact](help/database/db_apcontact) | ActivityPub compatible contacts - used in the ActivityPub implementation | | [apcontact](help/database/db_apcontact) | ActivityPub compatible contacts - used in the ActivityPub implementation |

View file

@ -0,0 +1,32 @@
Table account-suggestion
===========
Account suggestion
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------ | ------------------------------------------------------------ | ------------------ | ---- | --- | ------- | ----- |
| uri-id | Id of the item-uri table entry that contains the account url | int unsigned | NO | PRI | NULL | |
| uid | User ID | mediumint unsigned | NO | PRI | NULL | |
| level | level of closeness | smallint unsigned | YES | | NULL | |
| ignore | If set, this account will not be suggested again | boolean | NO | | 0 | |
Indexes
------------
| Name | Fields |
| ---------- | ----------- |
| PRIMARY | uid, uri-id |
| uri-id_uid | uri-id, uid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| uid | [user](help/database/db_user) | uid |
Return to [database documentation](help/database)

View file

@ -167,9 +167,23 @@ class Status extends BaseFactory
if (!empty($shared)) { if (!empty($shared)) {
$shared_uri_id = $shared['post']['uri-id']; $shared_uri_id = $shared['post']['uri-id'];
$mentions = array_merge($mentions, $this->mstdnMentionFactory->createFromUriId($shared_uri_id)->getArrayCopy()); foreach ($this->mstdnMentionFactory->createFromUriId($shared_uri_id)->getArrayCopy() as $mention) {
$tags = array_merge($tags, $this->mstdnTagFactory->createFromUriId($shared_uri_id)); if (!in_array($mention, $mentions)) {
$attachments = array_merge($attachments, $this->mstdnAttachementFactory->createFromUriId($shared_uri_id)); $mentions[] = $mention;
}
}
foreach ($this->mstdnTagFactory->createFromUriId($shared_uri_id) as $tag) {
if (!in_array($tag, $tags)) {
$tags[] = $tag;
}
}
foreach ($this->mstdnAttachementFactory->createFromUriId($shared_uri_id) as $attachment) {
if (!in_array($attachment, $attachments)) {
$attachments[] = $attachment;
}
}
if (empty($card->toArray())) { if (empty($card->toArray())) {
$card = $this->mstdnCardFactory->createFromUriId($shared_uri_id); $card = $this->mstdnCardFactory->createFromUriId($shared_uri_id);
@ -190,7 +204,7 @@ class Status extends BaseFactory
if ($is_reshare) { if ($is_reshare) {
$reshare = $this->createFromUriId($uriId, $uid, false)->toArray(); $reshare = $this->createFromUriId($uriId, $uid, false)->toArray();
} }
// $mentions = array_unique($mentions);
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $reshare, $poll); return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $reshare, $poll);
} }

View file

@ -260,6 +260,48 @@ class Relation
return true; return true;
} }
/**
* Update contact suggestions for a given user
*
* @param integer $uid
* @return void
*/
static public function updateSuggestions(int $uid)
{
if (DI::pConfig()->get($uid, 'suggestion', 'last_update') + 3600 > time()) {
return;
}
DBA::delete('account-suggestion', ['uid' => $uid, 'ignore' => false]);
foreach (self::getSuggestions($uid) as $contact) {
DBA::insert('account-suggestion', ['uri-id' => $contact['uri-id'], 'uid' => $uid, 'level' => 1], Database::INSERT_IGNORE);
}
DI::pConfig()->set($uid, 'suggestion', 'last_update', time());
}
/**
* Returns a cached array of suggested contacts for given user id
*
* @param int $uid User id
* @param int $start optional, default 0
* @param int $limit optional, default 80
* @return array
*/
static public function getCachedSuggestions(int $uid, int $start = 0, int $limit = 80): array
{
$condition = ["`uid` = ? AND `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE NOT `ignore` AND `uid` = ?)", 0, $uid];
$params = ['limit' => [$start, $limit]];
$cached = DBA::selectToArray('contact', [], $condition, $params);
if (!empty($cached)) {
return $cached;
} else {
return self::getSuggestions($uid, $start, $limit);
}
}
/** /**
* Returns an array of suggested contacts for given user id * Returns an array of suggested contacts for given user id
* *
@ -285,11 +327,12 @@ class Relation
(SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ?) (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ?)
AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`) (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`)
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)", AND NOT `hidden` AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$cid, $cid,
0, 0,
$uid, Contact::FRIEND, Contact::SHARING, $uid, Contact::FRIEND, Contact::SHARING,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid
], [ ], [
'order' => ['last-item' => true], 'order' => ['last-item' => true],
'limit' => $totallimit, 'limit' => $totallimit,
@ -315,9 +358,10 @@ class Relation
(SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?) (SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?)
AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`) (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`)
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)", AND NOT `hidden` AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$cid, 0, $uid, Contact::FRIEND, Contact::SHARING, $cid, 0, $uid, Contact::FRIEND, Contact::SHARING,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus], Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit] ['order' => ['last-item' => true], 'limit' => $totallimit]
); );
@ -335,9 +379,10 @@ class Relation
// The query returns contacts that follow the given user but aren't followed by that user. // The query returns contacts that follow the given user but aren't followed by that user.
$results = DBA::select('contact', [], $results = DBA::select('contact', [],
["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` = ?) ["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` = ?)
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)", AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$uid, Contact::FOLLOWER, 0, $uid, Contact::FOLLOWER, 0,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus], Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit] ['order' => ['last-item' => true], 'limit' => $totallimit]
); );
@ -355,9 +400,10 @@ class Relation
// The query returns any contact that isn't followed by that user. // The query returns any contact that isn't followed by that user.
$results = DBA::select('contact', [], $results = DBA::select('contact', [],
["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?) AND `nurl` = `nurl`) ["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?) AND `nurl` = `nurl`)
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)", AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$uid, Contact::FRIEND, Contact::SHARING, 0, $uid, Contact::FRIEND, Contact::SHARING, 0,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus], Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit] ['order' => ['last-item' => true], 'limit' => $totallimit]
); );

View file

@ -43,7 +43,7 @@ class Suggestions extends BaseApi
'limit' => 40, // Maximum number of results to return. Defaults to 40. 'limit' => 40, // Maximum number of results to return. Defaults to 40.
], $request); ], $request);
$suggestions = Contact\Relation::getSuggestions($uid, 0, $request['limit']); $suggestions = Contact\Relation::getCachedSuggestions($uid, 0, $request['limit']);
$accounts = []; $accounts = [];

View file

@ -57,7 +57,7 @@ class Suggestions extends \Friendica\BaseModule
$this->page['aside'] .= Widget::findPeople(); $this->page['aside'] .= Widget::findPeople();
$this->page['aside'] .= Widget::follow(); $this->page['aside'] .= Widget::follow();
$contacts = Contact\Relation::getSuggestions($this->session->getLocalUserId()); $contacts = Contact\Relation::getCachedSuggestions($this->session->getLocalUserId());
if (!$contacts) { if (!$contacts) {
return $this->t('No suggestions available. If this is a new site, please try again in 24 hours.'); return $this->t('No suggestions available. If this is a new site, please try again in 24 hours.');
} }

View file

@ -38,6 +38,7 @@ use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network; use Friendica\Util\Network;
use LightOpenID; use LightOpenID;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Worker;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** /**
@ -356,6 +357,9 @@ class Authentication
// Set the login date for all identities of the user // Set the login date for all identities of the user
$this->dba->update('user', ['login_date' => DateTimeFormat::utcNow()], $this->dba->update('user', ['login_date' => DateTimeFormat::utcNow()],
['parent-uid' => $user_record['uid'], 'account_removed' => false]); ['parent-uid' => $user_record['uid'], 'account_removed' => false]);
// Update suggestions upon login
Worker::add(Worker::PRIORITY_MEDIUM, 'UpdateSuggestions', $user_record['uid']);
} }
if ($login_initial) { if ($login_initial) {

View file

@ -130,6 +130,8 @@ class Cron
Worker::add(Worker::PRIORITY_LOW, 'CheckDeletedContacts'); Worker::add(Worker::PRIORITY_LOW, 'CheckDeletedContacts');
Worker::add(Worker::PRIORITY_LOW, 'UpdateAllSuggestions');
if (DI::config()->get('system', 'optimize_tables')) { if (DI::config()->get('system', 'optimize_tables')) {
Worker::add(Worker::PRIORITY_LOW, 'OptimizeTables'); Worker::add(Worker::PRIORITY_LOW, 'OptimizeTables');
} }

View file

@ -0,0 +1,41 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @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 <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Worker;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Util\DateTimeFormat;
/**
* Update contact suggestions for all aktive users
*/
class UpdateAllSuggestions
{
public static function execute()
{
$users = DBA::select('user', ['uid'], ["`login_date` > ?", DateTimeFormat::utc('now - 7 days')]);
while ($user = DBA::fetch($users)) {
Contact\Relation::updateSuggestions($user['uid']);
}
DBA::close($users);
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @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 <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Worker;
use Friendica\Model\Contact;
/**
* Update contact suggestions
*/
class UpdateSuggestions
{
public static function execute(int $uid)
{
Contact\Relation::updateSuggestions($uid);
}
}

View file

@ -55,7 +55,7 @@
use Friendica\Database\DBA; use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) { if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1495); define('DB_UPDATE_VERSION', 1496);
} }
return [ return [
@ -371,6 +371,18 @@ return [
"uid" => ["uid"], "uid" => ["uid"],
] ]
], ],
"account-suggestion" => [
"comment" => "Account suggestion",
"fields" => [
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the account url"],
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "primary" => "1", "foreign" => ["user" => "uid"], "comment" => "User ID"],
"level" => ["type" => "smallint unsigned", "comment" => "level of closeness"],
"ignore" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "If set, this account will not be suggested again"], ],
"indexes" => [
"PRIMARY" => ["uid", "uri-id"],
"uri-id_uid" => ["uri-id", "uid"],
]
],
"account-user" => [ "account-user" => [
"comment" => "Remote and local accounts", "comment" => "Remote and local accounts",
"fields" => [ "fields" => [

View file

@ -144,7 +144,7 @@ function vier_community_info()
// comunity_profiles // comunity_profiles
if ($show_profiles) { if ($show_profiles) {
$contacts = Contact\Relation::getSuggestions(DI::userSession()->getLocalUserId(), 0, 9); $contacts = Contact\Relation::getCachedSuggestions(DI::userSession()->getLocalUserId(), 0, 9);
$tpl = Renderer::getMarkupTemplate('ch_directory_item.tpl'); $tpl = Renderer::getMarkupTemplate('ch_directory_item.tpl');
if (DBA::isResult($contacts)) { if (DBA::isResult($contacts)) {