<?php
namespace App\Controller;
use App\DTO\HelpfulCommentDto;
use App\DTO\LikeDto;
use App\DTO\SaveCommentDto;
use App\DTO\SavePostDto;
use App\Entity\Image;
use App\Entity\Post;
use App\Entity\Token;
use App\Entity\User;
use App\Helper\AppversionHelper;
use App\Helper\SettingConstants;
use App\Response\PostResponse;
use App\Service\CommunityNoteService;
use App\Service\ContentService;
use App\Service\FeedService;
use App\Service\DynamicLinksService;
use App\Service\FreshdeskService;
use App\Service\GardenService;
use App\Service\ImageService;
use App\Service\MailService;
use App\Service\NotificationService;
use App\Service\PushNotificationService;
use App\Service\SlackNotificationService;
use App\Service\TagConnectionService;
use App\Service\UserService;
use App\Structures\PushNotification;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Annotations as OA;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Cache\ItemInterface;
/**
* @OA\Tag(name="Community")
*/
class FeedController extends BaseController
{
private FeedService $feedService;
private UserService $userService;
private PushNotificationService $pushNotificationService;
private ImageService $imageService;
private NotificationService $notificationService;
private FreshdeskService $freshdeskService;
private MailService $mailService;
private CommunityNoteService $communityNoteService;
private DynamicLinksService $dynamicLinksService;
private TagConnectionService $tagConnectionService;
private GardenService $gardenService;
private SlackNotificationService $slackNotificationService;
private ContentService $contentService;
public function __construct(
FeedService $feedService,
UserService $userService,
PushNotificationService $pushNotificationService,
ImageService $imageService,
NotificationService $notificationService,
FreshdeskService $freshdeskService,
MailService $mailService,
CommunityNoteService $communityNoteService,
DynamicLinksService $dynamicLinksService,
TagConnectionService $tagConnectionService,
GardenService $gardenService,
SlackNotificationService $slackNotificationService,
ContentService $contentService
) {
$this->feedService = $feedService;
$this->userService = $userService;
$this->pushNotificationService = $pushNotificationService;
$this->imageService = $imageService;
$this->notificationService = $notificationService;
$this->freshdeskService = $freshdeskService;
$this->mailService = $mailService;
$this->communityNoteService = $communityNoteService;
$this->dynamicLinksService = $dynamicLinksService;
$this->tagConnectionService = $tagConnectionService;
$this->gardenService = $gardenService;
$this->slackNotificationService = $slackNotificationService;
$this->contentService = $contentService;
}
/**
* @OA\Get(
* description="Lists a feed of all posts for the given user",
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="posts", type="array",
* @OA\Items(ref=@Model(type=PostResponse::class))
* ),
* @OA\Property(property="_metadata",type="object",
* @OA\Property(property="currentPage", type="number"),
* @OA\Property(property="totalPages", type="number"),
* @OA\Property(property="unreadNotifications", type="number"),
* @OA\Property(property="trendingTags", type="array", @OA\Items(type="string")),
* @OA\Property(property="myTags", type="array", @OA\Items(type="string")),
* )
* )
* ),
* )
*/
public function listAction(Request $request): JsonResponse
{
$user = $this->getUser();
$page = $request->get('page');
$query = $request->get('q');
$category = $request->get('c');
$userFilter = $request->get('u');
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
if (empty($page)) {
$page = 1;
}
$isV2 = $this->isApiV2($request);
$filters = $this->createFilters($query, $category, $userFilter);
// no app-version set means we have an older app which does not support urls in richtext
$appVersion = $request->headers->get('app-version');
if (empty($appVersion)) {
$this->feedService->setFlag(FeedService::FLAG_NO_URLS_IN_RICHTEXT);
} elseif (AppversionHelper::hasRequiredVersion($appVersion, '2.2.4')) {
$this->feedService->setFlag(FeedService::FLAG_LIST_ARTICLES);
$this->feedService->setFlag(FeedService::FLAG_REDUCE_DATA_IN_PROFILE);
}
$feed = $this->feedService->fetchFeed($user, $accessToken, $isV2, $filters, $page);
$metadata = [
'currentPage' => $feed['currentPage'],
'totalPages' => $feed['totalPages'],
'unreadNotifications' => $this->notificationService->getUnreadNotificationCount($user),
'trendingTags' => $this->feedService->fetchTrendingTags(),
'myTags' => [], // todo: add if users can save tags
];
$stickyNote = $this->communityNoteService->getCurrentNote($user->getLocale() ?? User::DEFAULT_LOCALE);
if ($stickyNote !== null) {
$metadata['stickyNote'] = $stickyNote;
}
$feed['_metadata'] = $metadata;
return new JsonResponse($feed);
}
public function userHasAnswersAction(): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$hasAnswers = $this->feedService->userHasAnswerToPost($user);
return new JsonResponse(['hasAnswers' => $hasAnswers]);
}
/**
* @OA\Get(
* description="Lists a feed of customized posts for the given user",
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="posts", type="array",
* @OA\Items(ref=@Model(type=PostResponse::class))
* ),
* @OA\Property(property="_metadata",type="object",
* @OA\Property(property="currentPage", type="number"),
* @OA\Property(property="totalPages", type="number"),
* @OA\Property(property="unreadNotifications", type="number"),
* @OA\Property(property="trendingTags", type="array", @OA\Items(type="string")),
* @OA\Property(property="myTags", type="array", @OA\Items(type="string")),
* )
* )
* ),
* )
*/
public function listCustomizedAction(Request $request): JsonResponse
{
$user = $this->getUser();
$page = $request->get('page') ?? 1;
$query = $request->get('q');
$category = $request->get('c');
$userFilter = $request->get('u');
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$this->feedService->setFlag(FeedService::FLAG_LIST_ARTICLES);
$this->feedService->setFlag(FeedService::FLAG_REDUCE_DATA_IN_PROFILE);
$filters = $this->createFilters($query, $category, $userFilter);
$feed = $this->feedService->fetchCustomizedFeed($user, $filters, $page);
$metadata = [
'currentPage' => $feed['currentPage'],
'totalPages' => $feed['totalPages'],
'unreadNotifications' => $this->notificationService->getUnreadNotificationCount($user),
'trendingTags' => $this->feedService->fetchTrendingTags(),
'myTags' => $this->tagConnectionService->listConnections($user),
];
$stickyNote = $this->communityNoteService->getCurrentNote($user->getLocale() ?? User::DEFAULT_LOCALE);
if ($stickyNote !== null) {
$metadata['stickyNote'] = $stickyNote;
}
$feed['_metadata'] = $metadata;
return new JsonResponse($feed);
}
public function listBookmarksAction(Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
$isV2 = $this->isApiV2($request);
$feed = $this->feedService->fetchBookmarks($user, $accessToken, $isV2);
return new JsonResponse($feed);
}
/**
* @OA\Post(
* description="Creates a new post in the community",
* @OA\Response(
* response=200,
* description="Post sucessfully created",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="posts", type="array",
* @OA\Items(ref=@Model(type=PostResponse::class))
* )
* )
* ),
* @OA\Parameter(
* name="body",
* in="path",
* required=true,
* @OA\JsonContent(
* type="object",
* @OA\Property(property="text", type="string"),
* @OA\Property(property="image", type="string"),
* @OA\Property(property="images", type="array",
* @OA\Items(
* @OA\Property(property="base64")
* )
* ),
* @OA\Property(property="isSupportRequest", type="boolean"),
* @OA\Property(property="isContentPiece", type="boolean"),
* @OA\Property(property="browser", type="string"),
* @OA\Property(property="version", type="string"),
* @OA\Property(property="appVersion", type="string"),
* @OA\Property(property="appOS", type="string"),
* @OA\Property(property="appDevice", type="string"),
* ),
* )
* )
* @param Request $request
* @return JsonResponse
*/
public function savePostAction(SavePostDto $dto, Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
// check if post is support request
$userEmailValid = str_contains($user->getEmail(), '@');
if ($dto->isSupportQuestion === true && $userEmailValid) {
$additionalData = [
'browser' => $dto->browser,
'version' => $dto->version,
'appVersion' => $dto->appVersion,
'appOS' => $dto->appOS,
'appDevice' => $dto->appDevice,
];
$this->freshdeskService->createSupportTicket($user, $dto->text, $dto->image, $additionalData);
return new JsonResponse(['post' => []]);
} else {
if ($dto->isContentPiece === true) {
$this->mailService->sendAppcontentMail($user, $dto->text, $dto->images);
return new JsonResponse(['post' => []]);
} else {
if ($dto->gardenId) {
$gardenHash = $this->gardenService->shareGarden($dto->user, $dto->gardenId);
if ($gardenHash === false) {
return new JsonResponse('cannot share non existing garden', 400);
}
$dto->gardenHash = $gardenHash;
}
$blocked = $this->feedService->checkBlocklist($dto->text);
if ($blocked) {
$this->slackNotificationService->sendMessageToChannel(SlackNotificationService::CHANNEL_C3PO,
':poop: Post blocked due to blocklist:' . "\n" .
'Posted by: ' . $user->getDisplayName() . '(id: ' . $user->getId() . ')' . "\n" .
'Text: ' . $dto->text);
return new JsonResponse('content blocked', 400);
}
$postEntity = $this->feedService->savePost($dto);
$isV2 = $this->isApiV2($request);
$deeplink = $this->dynamicLinksService->createCommunityPostLink($postEntity->getId());
$this->feedService->addDeeplinkToPost($postEntity, $deeplink);
$post = $this->feedService->fetchPost($user, $postEntity->getId(), $accessToken, true, $isV2);
$notificationsToPush = $this->notificationService->createNotificationsForPost($postEntity);
if (count($notificationsToPush) > 0) {
$this->pushNotificationService->createPushNotifications($notificationsToPush);
}
return new JsonResponse(['post' => $post]);
}
}
}
public function editPostAction(Request $request)
{
$user = $this->getUser();
$data = json_decode($request->getContent(), true);
$postId = $request->get('postId');
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
$post = $this->feedService->updatePost($user, $postId, $data);
if ($post === false) {
return new JsonResponse('bad request', 400);
}
$isV2 = $this->isApiV2($request);
$post = $this->feedService->fetchPost($user, $post->getId(), $accessToken, true, $isV2);
return new JsonResponse(['post' => $post]);
}
public function deletePostAction(Request $request)
{
$user = $this->getUser();
$postId = $request->get('postId');
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$post = $this->feedService->fetchPost($user, $postId);
if ($post === null) {
return new JsonResponse('bad request', 400);
}
if (!$this->feedService->postBelongsToUser($user, $postId)) {
return new JsonResponse('bad request', 400);
}
$this->notificationService->deleteNotificationsForPost($postId);
$result = $this->feedService->deletePost($user, $postId);
if ($result === false) {
return new JsonResponse('bad request', 400);
}
return new JsonResponse(['success' => true]);
}
/**
* @OA\Post(
* description="Creates a new comment for a given post",
* @OA\Response(
* response=200,
* description="Comment sucessfully created",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="post", type="object", ref=@Model(type=PostResponse::class))
* )
* ),
* @OA\Parameter(
* name="body",
* in="path",
* required=true,
* @OA\JsonContent(
* type="object",
* @OA\Property(property="parentPostId", type="int"),
* @OA\Property(property="text", type="string"),
* @OA\Property(property="image", type="string"),
* ),
* ),
* )
* @param Request $request
* @return JsonResponse
*/
public function saveCommentAction(SaveCommentDto $dto, Request $request): JsonResponse
{
/** @var User $user */
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
$comment = $this->feedService->saveComment($dto);
/** @var Post $post */
$postEntity = $this->feedService->fetchPost($user, $dto->postId, null, false);
$notificationsToPush = $this->notificationService->createNotificationsForComment($comment);
if (count($notificationsToPush) > 0) {
$this->pushNotificationService->createPushNotifications($notificationsToPush);
}
if ($postEntity->getUser()->getId() !== $user->getId()) {
// send push notification to author
$pn = $this->pushNotificationService->createPushNotification(
$postEntity->getUser(),
NotificationService::NOTIFICATION_TYPE_ANSWER_TO_YOUR_POST,
['%name%' => $user->getDisplayName()]);
$pn->setView(PushNotification::VIEW_COMMUNITY_STACK);
$pn->setViewParams(['screen' => PushNotification::SCREEN_NOTIFICATIONS]);
$pn->setWithBadgeCount(true);
$this->pushNotificationService->sendPushNotification($postEntity->getUser(), $pn);
}
$isV2 = $this->isApiV2($request);
$post = $this->feedService->fetchPost($user, $dto->postId, $accessToken, true, $isV2);
return new JsonResponse(['post' => $post]);
}
public function editCommentAction(Request $request): JsonResponse
{
$user = $this->getUser();
$postId = $request->get('postId');
$commentId = $request->get('commentId');
$data = json_decode($request->getContent(), true);
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
$this->feedService->updateComment($user, $commentId, $data);
$isV2 = $this->isApiV2($request);
$post = $this->feedService->fetchPost($user, $postId, $accessToken, true, $isV2);
return new JsonResponse(['post' => $post]);
}
public function deleteCommentAction(Request $request): JsonResponse
{
$user = $this->getUser();
$postId = $request->get('postId');
$commentId = $request->get('commentId');
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
if (!$this->feedService->commentBelongsToUser($user, $commentId)) {
return new JsonResponse('bad request', 400);
}
$this->notificationService->deleteNotificationsForComment($commentId);
$result = $this->feedService->deleteComment($user, $commentId);
if ($result === false) {
return new JsonResponse('bad request', 400);
}
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
$isV2 = $this->isApiV2($request);
$post = $this->feedService->fetchPost($user, $postId, $accessToken, true, $isV2);
return new JsonResponse(['post' => $post]);
}
public function bookmarkAction(Request $request): JsonResponse
{
$postId = $request->get('postId');
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$this->feedService->bookmarkPost($user, $postId);
$post = $this->getPost($user, $postId, $request);
return new JsonResponse(['post' => $post]);
}
public function deleteBookmarkAction(Request $request): JsonResponse
{
$postId = $request->get('postId');
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$this->feedService->deleteBookmark($user, $postId);
$post = $this->getPost($user, $postId, $request);
return new JsonResponse(['post' => $post]);
}
public function imageAction(Request $request): BinaryFileResponse|JsonResponse
{
$hash = $request->get('hash');
$filePath = $this->feedService->getImageFilename($hash);
if ($filePath !== false) {
$response = new BinaryFileResponse($filePath, 200);
} else {
return new JsonResponse(['error' => 'no_image'], 404);
}
return $response;
}
public function likeCommentAction(LikeDto $dto, Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$like = $this->feedService->likeComment($user, $dto->commentId, $dto->emotion);
if ($like !== false) {
$this->notificationService->createNotificationForCommentLike($like);
$author = $like->getComment()->getUser();
// send push notification to author
$pn = $this->pushNotificationService->createPushNotification(
$author,
NotificationService::NOTIFICATION_TYPE_LIKED_YOUR_COMMENT,
['%name%' => $user->getDisplayName()]
);
$pn->setView(PushNotification::VIEW_COMMUNITY_STACK);
$pn->setViewParams(['screen' => PushNotification::SCREEN_NOTIFICATIONS]);
$pn->setWithBadgeCount(true);
$this->pushNotificationService->sendPushNotification($author, $pn);
}
$post = $this->getPost($user, $dto->postId, $request);
return new JsonResponse(['post' => $post]);
}
public function dislikeCommentAction(int $postId, int $commentId, Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$this->feedService->dislikeComment($user, $commentId);
$post = $this->getPost($user, $postId, $request);
return new JsonResponse(['post' => $post]);
}
public function likePostAction(LikeDto $dto, Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$like = $this->feedService->likePost($user, $dto->postId, $dto->emotion);
if ($like !== false) {
$this->notificationService->createNotificationForLike($like);
}
$post = $this->feedService->fetchPost($user, $dto->postId, null, false);
if ($post->getUser()->getId() !== $user->getId()) {
$pn = $this->pushNotificationService->createPushNotification(
$post->getUser(),
NotificationService::NOTIFICATION_TYPE_REACTED_TO_YOUR_POST,
['%name%' => $user->getDisplayName()]
);
$pn->setView(PushNotification::VIEW_COMMUNITY_STACK);
$pn->setViewParams(['screen' => PushNotification::SCREEN_NOTIFICATIONS]);
$pn->setWithBadgeCount(true);
$this->pushNotificationService->sendPushNotification($post->getUser(), $pn);
}
$post = $this->getPost($user, $dto->postId, $request);
return new JsonResponse(['post' => $post]);
}
public function dislikePostAction(int $postId, Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$this->feedService->dislikePost($user, $postId);
$post = $this->getPost($user, $postId, $request);
return new JsonResponse(['post' => $post]);
}
public function profileImageAction(Request $request): BinaryFileResponse
{
$hash = $request->get('hash');
$user = $this->getUser();
$filePath = $this->userService->getProfileImage($hash);
$thumbPath = str_replace('.jpg', '_thumb.jpg', $filePath);
if ($filePath === false || !file_exists($filePath)) {
// return random default user image
$number = rand(1, 4);
$filePath = __DIR__ . '/../../data/placeholder_images/user_' . $number . '.png';
} else {
if (!file_exists($thumbPath)) {
$this->imageService->makeThumbnails($filePath, $thumbPath, 200, 200);
$filePath = $thumbPath;
} else {
$filePath = $thumbPath;
}
}
return new BinaryFileResponse($filePath, 200);
}
public function reportPostAction(Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$postId = $request->get('postId');
$body = json_decode($request->getContent(), true);
$post = $this->feedService->fetchPost($user, $postId, null, false);
$postHidden = $this->feedService->reportPost($user, $postId, $body['reason']);
if ($postHidden && $post !== null) {
$this->slackNotificationService->sendMessageToChannel(SlackNotificationService::CHANNEL_C3PO,
':poop: Post hidden due to 3 or more reports:' . "\n" .
'Posted by: ' . $post->getUser()->getDisplayName() . '(id: ' . $post->getUser()->getId() . ')' . "\n" .
'Text: ' . $post->getText());
}
if ($post !== null) {
$images = '';
foreach ($post->getImageUrls() as $imageUrl) {
$images .= $imageUrl . " ";
}
$this->freshdeskService->createReportTicket($user, [
'reason' => $body['reason'],
'postId' => $post->getId(),
'postText' => $post->getText(),
'postLink' => $post->getDynamicLink(),
'postImages' => $images
]);
}
return new JsonResponse(null, 200);
}
public function reportCommentAction(Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$postId = $request->get('postId');
$commentId = $request->get('commentId');
$body = json_decode($request->getContent(), true);
$post = $this->feedService->fetchPost($user, $postId, null, false);
$comment = null;
if ($post !== null) {
foreach ($post->getPostComment() as $comment) {
if ($comment->getId() === $commentId) {
break;
}
}
if ($comment !== null) {
$commentHidden = $this->feedService->reportComment($user, $commentId, $body['reason']);
if ($commentHidden && $comment !== null) {
$this->slackNotificationService->sendMessageToChannel(SlackNotificationService::CHANNEL_C3PO,
':poop: Comment hidden due to 3 or more reports:' . "\n" .
'Posted by: ' . $comment->getUser()->getDisplayName() . '(id: ' . $comment->getUser()->getId() . ')' . "\n" .
'Text: ' . $comment->getText());
}
$this->freshdeskService->createReportTicket($user, [
'reason' => $body['reason'],
'postId' => $post->getId(),
'postText' => $post->getText(),
'postLink' => $post->getDynamicLink(),
'commentId' => $comment->getId(),
'commentText' => $comment->getText(),
'commentImage' => $comment->getImageUrl()
]);
}
}
return new JsonResponse(null, 200);
}
public function profileAction(Request $request): JsonResponse
{
$hash = $request->get('hash');
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
$profile = $this->feedService->getUserProfile($hash, $user);
if ($profile === null) {
return new JsonResponse('not_found', 404);
}
$arrUser = $this->userService->fetchPublicUser($hash, false, true);
$bgImages = $this->imageService->fetchImagesByReference($arrUser['id'], ImageService::TYPE_PROFILE_GARDEN);
$profile['backgroundImages'] = [];
/** @var Image $bgImage */
foreach ($bgImages as $bgImage) {
$profile['backgroundImages'][] = [
'imageId' => $bgImage->getId(),
'imageUrl' => $this->imageService->getImageUrl($bgImage->getId()),
];
}
// add interests
$profileUser = $this->userService->fetchUserWithHash($hash);
$ownOnboardingData = $user->getSetting('onboardingData');
$onboardingData = $profileUser->getSetting('onboardingData');
if (isset($onboardingData['topicsOfInterest'])) {
$profile['topics'] = [];
$topics = $onboardingData['topicsOfInterest'];
$ownTopics = $ownOnboardingData['topicsOfInterest'] ?? [];
foreach ($topics as $topic) {
$profile['topics'][] = [
'label' => $topic,
'isShared' => in_array($topic, $ownTopics)
];
}
}
$favorites = $profileUser->getFavoriteCrop();
$ownFavorites = $user->getFavoriteCrop();
$ownFavoriteIds = [];
foreach ($ownFavorites as $ownFavorite) {
$crop = $ownFavorite->getCrop();
if ($crop->getParentCrop() !== null) {
$crop = $crop->getParentCrop();
}
$ownFavoriteIds[] = $crop->getId();
}
$profile['favoriteCrops'] = [];
$usedFavoriteIds = [];
foreach ($favorites as $favorite) {
$crop = $favorite->getCrop();
if ($crop->getParentCrop() !== null) {
$crop = $crop->getParentCrop();
}
if (!in_array($crop->getId(), $usedFavoriteIds)) {
$profile['favoriteCrops'][] = [
'id' => $crop->getId(),
'name' => $crop->getName(),
'isShared' => in_array($crop->getId(), $ownFavoriteIds)
];
$usedFavoriteIds[] = $crop->getId();
}
}
// add shareable link to user profile
$profileLink = $profileUser->getSetting(SettingConstants::PROFILE_LINK);
if (empty($profileLink)) {
$profileLink = $this->dynamicLinksService->createProfileLink($profileUser->getPublicProfileHash());
$profileUser->setSetting(SettingConstants::PROFILE_LINK, $profileLink);
$this->userService->save($profileUser);
}
$profile['shareLink'] = $profileLink;
return new JsonResponse($profile);
}
public function listTagsAction(Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', 403);
}
// cache result for 1h
$cacheAdapter = new ApcuAdapter('feed');
$cacheKey = 'feed-tags-' . $user->getId();
$tags = $cacheAdapter->get($cacheKey, function (ItemInterface $item) use ($user) {
$item->expiresAfter(1 * 3600);
// list all tags that can be used in posts
return $this->feedService->fetchTags($user);
});
return new JsonResponse($tags);
}
public function categorizeAction(Request $request): JsonResponse
{
/** @var User $user */
$user = $this->getUser();
if (empty($user) || !$user->isSuperModerator()) {
return new JsonResponse('forbidden', 403);
}
$postId = $request->get('postId');
$data = json_decode($request->getContent(), true);
$category = $data['category'];
$post = $this->feedService->fetchPost($user, $postId, null, false);
if (empty($post)) {
return new JsonResponse('not_found', Response::HTTP_NOT_FOUND);
}
$this->feedService->categorizePost($postId, $category);
$post = $this->getPost($user, $postId, $request);
return new JsonResponse($post);
}
public function getPostAction(Request $request): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$postId = $request->get('postId');
$post = $this->getPost($user, $postId, $request);
if ($post === null) {
return new JsonResponse(null, Response::HTTP_NOT_FOUND);
}
return new JsonResponse($post);
}
/**
* @OA\Get(
* description="Lists all users that are growing a crop with their community profile. Growing a
crop means that the user either has this crop in her patches or that she has posted in the
community with the name of the crop",
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="users", type="array",
* @OA\Items(
* @OA\Property(property="displayName", type="string"),
* @OA\Property(property="imageUrl", type="string"),
* @OA\Property(property="description", type="string"),
* @OA\Property(property="commentCount", type="string"),
* @OA\Property(property="links", type="array", @OA\Items(type="string"))
* ),
* ),
* @OA\Property(property="amount", type="number")
* ),
* ),
* )
*/
public function listCommunityUsersForCrop(int $cropId): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$users = $this->feedService->fetchUsersGrowingCrops($cropId, 50);
$usersWithProfile = [];
shuffle($users);
/** @var User $user */
foreach ($users as $user) {
$hash = $user->getPublicProfileHash();
$filePath = $this->userService->getProfileImage($hash);
if ($filePath === false || !file_exists($filePath)) {
continue;
}
$usersWithProfile[] = $this->feedService->getUserProfile($hash, $user);
if (count($usersWithProfile) >= 5) {
break;
}
}
return new JsonResponse([
'amount' => count($users),
'users' => $usersWithProfile
]);
}
public function followTagAction(int $tagId): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$result = $this->tagConnectionService->saveConnection($user, $tagId);
return new JsonResponse(['success' => $result]);
}
public function unfollowTagAction(int $tagId): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$result = $this->tagConnectionService->deleteConnection($user, $tagId);
return new JsonResponse(['success' => $result]);
}
public function listTagConnectionAction(): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$tags = $this->tagConnectionService->listConnections($user);
return new JsonResponse(['tags' => $tags]);
}
public function getPostEmotionsAction(int $postId): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$tags = $this->feedService->fetchEmotionsForPost($postId);
return new JsonResponse(['emotions' => $tags]);
}
public function getCommentEmotionsAction(int $commentId): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$tags = $this->feedService->fetchEmotionsForComment($commentId);
return new JsonResponse(['emotions' => $tags]);
}
public function helpfulCommentAction(HelpfulCommentDto $dto): JsonResponse
{
if (empty($dto->user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$helpful = $this->feedService->markCommentAsHelpful($dto);
if ($dto->isHelpful) {
$notification = $this->notificationService->createNotificationForCommentHelpful($helpful);
if ($notification) {
$this->pushNotificationService->createPushNotifications([$notification]);
}
}
return new JsonResponse(['success' => true]);
}
public function trendingPostsAction(): JsonResponse
{
$user = $this->getUser();
if (empty($user)) {
return new JsonResponse('forbidden', Response::HTTP_FORBIDDEN);
}
$hashtagFilter = $this->contentService->getActiveChallengeHashtags($user);
$posts = $this->feedService->fetchTrendingPosts($user, $hashtagFilter);
return new JsonResponse(['posts' => $posts]);
}
private function getPost(User $user, int $postId, Request $request): ?array
{
/** @var Token $token */
$token = $user->getToken()->first();
$accessToken = $token->getToken();
// no app-version set means we have an older app which does not support urls in richtext
$appVersion = $request->headers->get('app-version');
if (empty($appVersion)) {
$this->feedService->setFlag(FeedService::FLAG_NO_URLS_IN_RICHTEXT);
}
$isV2 = $this->isApiV2($request);
return $this->feedService->fetchPost($user, $postId, $accessToken, true, $isV2);
}
/**
* @param $query
* @param array $filters
* @param $category
* @param $userFilter
* @return array
*/
private function createFilters($query, $category, $userFilter): array
{
$filters = [];
if (!empty($query)) {
if (strpos($query, '#') === 0) {
$filters['tag'] = substr($query, 1);
} else {
$filters['query'] = $query;
}
}
if (!empty($category)) {
$filters['category'] = $category;
} else {
$filters['category'] = 'default';
}
if (!empty($userFilter)) {
$filters['userHash'] = $userFilter;
}
return $filters;
}
}