pixelfed/app/Services/GroupService.php
2024-07-09 23:16:05 -06:00

366 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
];
}
}