mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-20 20:13:17 +00:00
367 lines
8.2 KiB
PHP
367 lines
8.2 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace App\Services;
|
||
|
|
||
|
use App\Profile;
|
||
|
use App\Models\Group;
|
||
|
use App\Models\GroupCategory;
|
||
|
use App\Models\GroupMember;
|
||
|
use App\Models\GroupPost;
|
||
|
use App\Models\GroupInteraction;
|
||
|
use App\Models\GroupLimit;
|
||
|
use App\Util\ActivityPub\Helpers;
|
||
|
use Cache;
|
||
|
use Purify;
|
||
|
use App\Http\Resources\Groups\GroupResource;
|
||
|
|
||
|
class GroupService
|
||
|
{
|
||
|
const CACHE_KEY = 'pf:services:groups:';
|
||
|
|
||
|
protected static function key($name)
|
||
|
{
|
||
|
return self::CACHE_KEY . $name;
|
||
|
}
|
||
|
|
||
|
public static function get($id, $pid = false)
|
||
|
{
|
||
|
$res = Cache::remember(
|
||
|
self::key($id),
|
||
|
1209600,
|
||
|
function() use($id, $pid) {
|
||
|
$group = (new Group)->withoutRelations()->whereNull('status')->find($id);
|
||
|
|
||
|
if(!$group) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$admin = $group->profile_id ? AccountService::get($group->profile_id) : null;
|
||
|
|
||
|
return [
|
||
|
'id' => (string) $group->id,
|
||
|
'name' => $group->name,
|
||
|
'description' => $group->description,
|
||
|
'short_description' => str_limit(strip_tags($group->description), 120),
|
||
|
'category' => self::categoryById($group->category_id),
|
||
|
'local' => (bool) $group->local,
|
||
|
'url' => $group->url(),
|
||
|
'shorturl' => url('/g/'.HashidService::encode($group->id)),
|
||
|
'membership' => $group->getMembershipType(),
|
||
|
'member_count' => $group->members()->whereJoinRequest(false)->count(),
|
||
|
'verified' => false,
|
||
|
'self' => null,
|
||
|
'admin' => $admin,
|
||
|
'config' => [
|
||
|
'recommended' => (bool) $group->recommended,
|
||
|
'discoverable' => (bool) $group->discoverable,
|
||
|
'activitypub' => (bool) $group->activitypub,
|
||
|
'is_nsfw' => (bool) $group->is_nsfw,
|
||
|
'dms' => (bool) $group->dms
|
||
|
],
|
||
|
'metadata' => $group->metadata,
|
||
|
'created_at' => $group->created_at->toAtomString(),
|
||
|
];
|
||
|
}
|
||
|
);
|
||
|
|
||
|
if($pid) {
|
||
|
$res['self'] = self::getSelf($id, $pid);
|
||
|
}
|
||
|
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
public static function del($id)
|
||
|
{
|
||
|
Cache::forget('ap:groups:object:' . $id);
|
||
|
return Cache::forget(self::key($id));
|
||
|
}
|
||
|
|
||
|
public static function getSelf($gid, $pid)
|
||
|
{
|
||
|
return Cache::remember(
|
||
|
self::key('self:gid-' . $gid . ':pid-' . $pid),
|
||
|
3600,
|
||
|
function() use($gid, $pid) {
|
||
|
$group = Group::find($gid);
|
||
|
|
||
|
if(!$gid || !$pid) {
|
||
|
return [
|
||
|
'is_member' => false,
|
||
|
'role' => null,
|
||
|
'is_requested' => null
|
||
|
];
|
||
|
}
|
||
|
|
||
|
return [
|
||
|
'is_member' => $group->isMember($pid),
|
||
|
'role' => $group->selfRole($pid),
|
||
|
'is_requested' => optional($group->members()->whereProfileId($pid)->first())->join_request ?? false
|
||
|
];
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public static function delSelf($gid, $pid)
|
||
|
{
|
||
|
Cache::forget(self::key("is_member:{$gid}:{$pid}"));
|
||
|
return Cache::forget(self::key('self:gid-' . $gid . ':pid-' . $pid));
|
||
|
}
|
||
|
|
||
|
public static function sidToGid($gid, $pid)
|
||
|
{
|
||
|
return Cache::remember(self::key('s2gid:' . $gid . ':' . $pid), 3600, function() use($gid, $pid) {
|
||
|
return optional(GroupPost::whereGroupId($gid)->whereStatusId($pid)->first())->id;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public static function membershipsByPid($pid)
|
||
|
{
|
||
|
return Cache::remember(self::key("mbpid:{$pid}"), 3600, function() use($pid) {
|
||
|
return GroupMember::whereProfileId($pid)->pluck('group_id');
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public static function config()
|
||
|
{
|
||
|
return [
|
||
|
'enabled' => config('exp.gps') ?? false,
|
||
|
'limits' => [
|
||
|
'group' => [
|
||
|
'max' => 999,
|
||
|
'federation' => false,
|
||
|
],
|
||
|
|
||
|
'user' => [
|
||
|
'create' => [
|
||
|
'new' => true,
|
||
|
'max' => 10
|
||
|
],
|
||
|
'join' => [
|
||
|
'max' => 10
|
||
|
],
|
||
|
'invite' => [
|
||
|
'max' => 20
|
||
|
]
|
||
|
]
|
||
|
],
|
||
|
'guest' => [
|
||
|
'public' => false
|
||
|
]
|
||
|
];
|
||
|
}
|
||
|
|
||
|
public static function fetchRemote($url)
|
||
|
{
|
||
|
// todo: refactor this demo
|
||
|
$res = Helpers::fetchFromUrl($url);
|
||
|
|
||
|
if(!$res || !isset($res['type']) || $res['type'] != 'Group') {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$group = Group::whereRemoteUrl($url)->first();
|
||
|
|
||
|
if($group) {
|
||
|
return $group;
|
||
|
}
|
||
|
|
||
|
$group = new Group;
|
||
|
$group->remote_url = $res['url'];
|
||
|
$group->name = $res['name'];
|
||
|
$group->inbox_url = $res['inbox'];
|
||
|
$group->metadata = [
|
||
|
'header' => [
|
||
|
'url' => $res['icon']['image']['url']
|
||
|
]
|
||
|
];
|
||
|
$group->description = Purify::clean($res['summary']);
|
||
|
$group->local = false;
|
||
|
$group->save();
|
||
|
|
||
|
return $group->url();
|
||
|
}
|
||
|
|
||
|
public static function log(
|
||
|
string $groupId,
|
||
|
string $profileId,
|
||
|
string $type = null,
|
||
|
array $meta = null,
|
||
|
string $itemType = null,
|
||
|
string $itemId = null
|
||
|
)
|
||
|
{
|
||
|
// todo: truncate (some) metadata after XX days in cron/queue
|
||
|
$log = new GroupInteraction;
|
||
|
$log->group_id = $groupId;
|
||
|
$log->profile_id = $profileId;
|
||
|
$log->type = $type;
|
||
|
$log->item_type = $itemType;
|
||
|
$log->item_id = $itemId;
|
||
|
$log->metadata = $meta;
|
||
|
$log->save();
|
||
|
}
|
||
|
|
||
|
public static function getRejoinTimeout($gid, $pid)
|
||
|
{
|
||
|
$key = self::key('rejoin-timeout:gid-' . $gid . ':pid-' . $pid);
|
||
|
return Cache::has($key);
|
||
|
}
|
||
|
|
||
|
public static function setRejoinTimeout($gid, $pid)
|
||
|
{
|
||
|
// todo: allow group admins to manually remove timeout
|
||
|
$key = self::key('rejoin-timeout:gid-' . $gid . ':pid-' . $pid);
|
||
|
return Cache::put($key, 1, 86400);
|
||
|
}
|
||
|
|
||
|
public static function getMemberInboxes($id)
|
||
|
{
|
||
|
// todo: cache this, maybe add join/leave methods to this service to handle cache invalidation
|
||
|
$group = (new Group)->withoutRelations()->findOrFail($id);
|
||
|
if(!$group->local) {
|
||
|
return [];
|
||
|
}
|
||
|
$members = GroupMember::whereGroupId($id)->whereLocalProfile(false)->pluck('profile_id');
|
||
|
return Profile::find($members)->map(function($u) {
|
||
|
return $u->sharedInbox ?? $u->inbox_url;
|
||
|
})->toArray();
|
||
|
}
|
||
|
|
||
|
public static function getInteractionLimits($gid, $pid)
|
||
|
{
|
||
|
return Cache::remember(self::key(":il:{$gid}:{$pid}"), 3600, function() use($gid, $pid) {
|
||
|
$limit = GroupLimit::whereGroupId($gid)->whereProfileId($pid)->first();
|
||
|
if(!$limit) {
|
||
|
return [
|
||
|
'limits' => [
|
||
|
'can_post' => true,
|
||
|
'can_comment' => true,
|
||
|
'can_like' => true
|
||
|
],
|
||
|
'updated_at' => null
|
||
|
];
|
||
|
}
|
||
|
|
||
|
return [
|
||
|
'limits' => $limit->limits,
|
||
|
'updated_at' => $limit->updated_at->format('c')
|
||
|
];
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public static function clearInteractionLimits($gid, $pid)
|
||
|
{
|
||
|
return Cache::forget(self::key(":il:{$gid}:{$pid}"));
|
||
|
}
|
||
|
|
||
|
public static function canPost($gid, $pid)
|
||
|
{
|
||
|
$limits = self::getInteractionLimits($gid, $pid);
|
||
|
if($limits) {
|
||
|
return (bool) $limits['limits']['can_post'];
|
||
|
} else {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function canComment($gid, $pid)
|
||
|
{
|
||
|
$limits = self::getInteractionLimits($gid, $pid);
|
||
|
if($limits) {
|
||
|
return (bool) $limits['limits']['can_comment'];
|
||
|
} else {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function canLike($gid, $pid)
|
||
|
{
|
||
|
$limits = self::getInteractionLimits($gid, $pid);
|
||
|
if($limits) {
|
||
|
return (bool) $limits['limits']['can_like'];
|
||
|
} else {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function categories($onlyActive = true)
|
||
|
{
|
||
|
return Cache::remember(self::key(':categories'), 2678400, function() use($onlyActive) {
|
||
|
return GroupCategory::when($onlyActive, function($q, $onlyActive) {
|
||
|
return $q->whereActive(true);
|
||
|
})
|
||
|
->orderBy('order')
|
||
|
->pluck('name')
|
||
|
->toArray();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public static function categoryById($id)
|
||
|
{
|
||
|
return Cache::remember(self::key(':categorybyid:'.$id), 2678400, function() use($id) {
|
||
|
$category = GroupCategory::find($id);
|
||
|
if($category) {
|
||
|
return [
|
||
|
'name' => $category->name,
|
||
|
'url' => url("/groups/explore/category/{$category->slug}")
|
||
|
];
|
||
|
}
|
||
|
return false;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public static function isMember($gid = false, $pid = false)
|
||
|
{
|
||
|
if(!$gid || !$pid) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$key = self::key("is_member:{$gid}:{$pid}");
|
||
|
return Cache::remember($key, 3600, function() use($gid, $pid) {
|
||
|
return GroupMember::whereGroupId($gid)
|
||
|
->whereProfileId($pid)
|
||
|
->whereJoinRequest(false)
|
||
|
->exists();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public static function mutualGroups($cid = false, $pid = false, $exclude = [])
|
||
|
{
|
||
|
if(!$cid || !$pid) {
|
||
|
return [
|
||
|
'count' => 0,
|
||
|
'groups' => []
|
||
|
];
|
||
|
}
|
||
|
|
||
|
$self = self::membershipsByPid($cid);
|
||
|
$user = self::membershipsByPid($pid);
|
||
|
|
||
|
if(!$self->count() || !$user->count()) {
|
||
|
return [
|
||
|
'count' => 0,
|
||
|
'groups' => []
|
||
|
];
|
||
|
}
|
||
|
|
||
|
$intersect = $self->intersect($user);
|
||
|
$count = $intersect->count();
|
||
|
$groups = $intersect
|
||
|
->values()
|
||
|
->filter(function($id) use($exclude) {
|
||
|
return !in_array($id, $exclude);
|
||
|
})
|
||
|
->shuffle()
|
||
|
->take(1)
|
||
|
->map(function($id) {
|
||
|
return self::get($id);
|
||
|
});
|
||
|
|
||
|
return [
|
||
|
'count' => $count,
|
||
|
'groups' => $groups
|
||
|
];
|
||
|
}
|
||
|
}
|