Skip to content

Commit c939c61

Browse files
committed
Fix video likes
1 parent a4aac9c commit c939c61

File tree

10 files changed

+119
-80
lines changed

10 files changed

+119
-80
lines changed

app/Http/Controllers/Api/VideoController.php

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use App\Models\Video;
2727
use App\Models\VideoLike;
2828
use App\Services\AccountService;
29+
use App\Services\LikeService;
2930
use App\Services\NotificationService;
3031
use App\Services\VideoService;
3132
use Illuminate\Http\Request;
@@ -59,6 +60,7 @@ public function store(StoreVideoRequest $request)
5960
$model->profile_id = $pid;
6061
$model->caption = Purify::clean($request->description);
6162
$model->size_kb = $videoMeta['size'];
63+
$model->is_sensitive = $request->filled('is_sensitive') ? (bool) $request->boolean('is_sensitive') : false;
6264
$model->comment_state = $request->filled('comment_state') ? ($request->input('comment_state') == 4 ? 4 : 0) : 4;
6365
$model->can_download = $request->filled('can_download') ? $request->boolean('can_download') : false;
6466
$model->media_metadata = $videoMeta;
@@ -138,22 +140,27 @@ public function like(Request $request, $id)
138140
return $this->error('Video not found or is unavailable', 404);
139141
}
140142

141-
$res = VideoLike::updateOrCreate([
143+
$like = VideoLike::firstOrCreate([
142144
'profile_id' => $pid,
143145
'video_id' => $video->id,
144146
]);
145147

146-
if ($pid != $video->profile_id) {
147-
NotificationService::newVideoLike(
148-
$video->profile_id,
149-
$video->id,
150-
$pid
151-
);
148+
if ($like->wasRecentlyCreated) {
149+
$video->increment('likes');
150+
151+
LikeService::addVideo($video->id, $pid);
152+
153+
if ($pid !== $video->profile_id) {
154+
NotificationService::newVideoLike(
155+
$video->profile_id,
156+
$video->id,
157+
$pid
158+
);
159+
}
152160
}
153161

154162
$resp = (new VideoResource($video))->toArray($request);
155163
$resp['has_liked'] = true;
156-
$resp['likes'] = $res->wasRecentlyCreated ? $resp['likes'] + 1 : $resp['likes'];
157164

158165
return $resp;
159166
}
@@ -170,14 +177,23 @@ public function unlike(Request $request, $id)
170177
return $this->error('Video not found or is unavailable', 404);
171178
}
172179

173-
$res = VideoLike::whereProfileId($pid)
174-
->whereVideoId($video->id)
180+
$res = VideoLike::where('profile_id', $pid)
181+
->where('video_id', $video->id)
175182
->first();
176183

184+
if ($res) {
185+
$video->decrement('likes');
186+
LikeService::remVideo($video->id, $pid);
187+
$res->delete();
188+
} else {
189+
$resp = (new VideoResource($video))->toArray($request);
190+
191+
return $resp;
192+
}
193+
177194
$resp = (new VideoResource($video))->toArray($request);
178195

179196
if ($res) {
180-
$res->delete();
181197
$resp['has_liked'] = false;
182198
$resp['likes'] = $resp['likes'] ? $resp['likes'] - 1 : 0;
183199
}

app/Http/Resources/VideoResource.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function toArray(Request $request): array
2424
$mediaUrl = data_get($media, 'media.src_url', null);
2525

2626
$pid = $this->getAuthenticatedProfileId($request);
27+
$hasLiked = $pid ? LikeService::hasVideo($this->id, $pid) : false;
2728

2829
$res = [
2930
'id' => (string) $this->id,
@@ -41,7 +42,7 @@ public function toArray(Request $request): array
4142
'likes' => $this->likes,
4243
'shares' => $this->shares,
4344
'comments' => $this->comments,
44-
'has_liked' => $pid ? LikeService::hasVideo($this->id, $pid) : false,
45+
'has_liked' => $hasLiked,
4546
'permissions' => [
4647
'can_comment' => (bool) $this->comment_state == 4,
4748
'can_download' => (bool) $this->can_download,

app/Models/VideoLike.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22

33
namespace App\Models;
44

5-
use App\Observers\VideoLikeObserver;
6-
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
75
use Illuminate\Database\Eloquent\Factories\HasFactory;
86
use Illuminate\Database\Eloquent\Model;
97

10-
#[ObservedBy([VideoLikeObserver::class])]
118
class VideoLike extends Model
129
{
1310
use HasFactory;

app/Observers/VideoLikeObserver.php

Lines changed: 0 additions & 44 deletions
This file was deleted.

app/Services/LikeService.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ class LikeService
3131
/**
3232
* Check if a profile has liked a video
3333
*/
34-
public static function hasVideo($videoId, $profileId)
34+
public static function hasVideo(string $videoId, string $profileId)
3535
{
3636
$cacheKey = self::VIDEO_LIKES_KEY.$videoId;
3737

38-
if (Redis::sismember($cacheKey, $profileId)) {
38+
if (Redis::sismember($cacheKey, $profileId) == 1) {
3939
return true;
4040
}
4141

@@ -54,7 +54,7 @@ public static function hasVideo($videoId, $profileId)
5454
/**
5555
* Add a video like to cache
5656
*/
57-
public static function addVideo($videoId, $profileId)
57+
public static function addVideo(string $videoId, string $profileId)
5858
{
5959
$cacheKey = self::VIDEO_LIKES_KEY.$videoId;
6060
$timestampKey = self::VIDEO_LIKES_TIMESTAMP_KEY.$videoId;
@@ -74,17 +74,27 @@ public static function addVideo($videoId, $profileId)
7474
/**
7575
* Remove a video like from cache
7676
*/
77-
public static function remVideo($videoId, $profileId)
77+
public static function remVideo(string $videoId, string $profileId)
7878
{
7979
$cacheKey = self::VIDEO_LIKES_KEY.$videoId;
8080

81-
return Redis::srem($cacheKey, $profileId) > 0;
81+
return Redis::srem($cacheKey, $profileId);
82+
}
83+
84+
/**
85+
* Get video likes from cache
86+
*/
87+
public static function getVideoCount(string $videoId)
88+
{
89+
$cacheKey = self::VIDEO_LIKES_KEY.$videoId;
90+
91+
return Redis::scard($cacheKey);
8292
}
8393

8494
/**
8595
* Get video likes from cache
8696
*/
87-
public static function getVideo($videoId)
97+
public static function getVideo(string $videoId)
8898
{
8999
$cacheKey = self::VIDEO_LIKES_KEY.$videoId;
90100

lang/en/post.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
'creator' => 'Creator',
1717
'delete' => 'Delete',
1818
'deleteVideo' => 'Delete Video',
19+
'deleteVideoConfirmMessage' => 'Are you sure you want to delete this video?',
1920
'deletingDotDotDot' => 'Deleting...',
2021
'dotDotDotMore' => '...more',
2122
'downloadsEnabled' => 'Downloads Enabled',

resources/js/components/Feed/VideoPlayer.vue

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,20 @@
386386
>{{ $t("common.report") }}</span
387387
>
388388
</button>
389+
<button
390+
v-if="
391+
authStore.authenticated &&
392+
profileId == authStore.user.id
393+
"
394+
class="flex w-full items-center justify-start gap-2 py-3 px-4 hover:bg-gray-100 text-red-500 dark:hover:bg-slate-800 cursor-pointer"
395+
@click="handleVideoDelete"
396+
>
397+
<i class="bx bx-trash text-[20px]"></i>
398+
<span
399+
class="pl-2 font-semibold text-sm"
400+
>{{ $t("common.delete") }}</span
401+
>
402+
</button>
389403
</div>
390404
</div>
391405
</div>
@@ -544,6 +558,8 @@ import "video.js/dist/video-js.css";
544558
import LoopLink from "../LoopLink.vue";
545559
import { useReportModal } from "@/composables/useReportModal";
546560
import { useQueryClient } from "@tanstack/vue-query";
561+
import { useAlertModal } from "@/composables/useAlertModal.js";
562+
import { useI18n } from "vue-i18n";
547563
548564
const props = defineProps({
549565
videoId: { type: String, required: true },
@@ -588,6 +604,8 @@ const isSensitiveRevealed = ref(false);
588604
const pendingPlay = ref(false);
589605
const { openReportModal } = useReportModal();
590606
const queryClient = useQueryClient();
607+
const { alertModal, confirmModal } = useAlertModal();
608+
const { t } = useI18n();
591609
592610
const {
593611
hasInteracted: hasGlobalInteraction,
@@ -732,17 +750,19 @@ const submitComment = async () => {
732750
733751
const toggleLike = async () => {
734752
const state = videoLiked.value;
735-
queryClient.invalidateQueries({ queryKey: ["feed"] });
736-
queryClient.invalidateQueries({ queryKey: ["following-feed"] });
737-
await videoStore.likeVideo().then((res) => {
738-
videoStore.setVideo(res.data);
739-
});
753+
740754
if (state) {
741-
videoLiked.value = false;
742-
likeCount.value = Math.max((likeCount.value ?? 0) - 1, 0);
755+
await videoStore.unlikeVideo(props.videoId).then((res) => {
756+
videoStore.setVideo(res.data);
757+
videoLiked.value = false;
758+
likeCount.value = res.data.likes;
759+
});
743760
} else {
744-
videoLiked.value = true;
745-
likeCount.value = likeCount.value + 1;
761+
await videoStore.likeVideo(props.videoId).then((res) => {
762+
videoStore.setVideo(res.data);
763+
videoLiked.value = true;
764+
likeCount.value = res.data.likes;
765+
});
746766
}
747767
};
748768
@@ -951,6 +971,28 @@ const formatCount = (count) => {
951971
return count.toString();
952972
};
953973
974+
const handleVideoDelete = async () => {
975+
showMenu.value = false;
976+
977+
const result = await confirmModal(
978+
t("post.deleteVideo"),
979+
t("post.deleteVideoConfirmMessage"),
980+
t("common.delete"),
981+
t("common.cancel"),
982+
);
983+
if (result) {
984+
try {
985+
await videoStore.deleteVideoById(props.videoId);
986+
await nextTick();
987+
queryClient.invalidateQueries({ queryKey: ["feed"] });
988+
queryClient.invalidateQueries({ queryKey: ["following-feed"] });
989+
window.location.reload();
990+
} catch (error) {
991+
console.log(error);
992+
}
993+
}
994+
};
995+
954996
defineExpose({
955997
play,
956998
pause,

resources/js/i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
"creator": "Creator",
139139
"delete": "Delete",
140140
"deleteVideo": "Delete Video",
141+
"deleteVideoConfirmMessage": "Are you sure you want to delete this video?",
141142
"deletingDotDotDot": "Deleting...",
142143
"dotDotDotMore": "...more",
143144
"downloadsEnabled": "Downloads Enabled",

resources/js/i18n/locales/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Auto-generated by vue-i18n:generate command
2-
// Generated: 2025-09-10T03:44:41+00:00
2+
// Generated: 2025-09-10T09:16:49+00:00
33
import { createI18n } from "vue-i18n";
44

55
import ca from "./ca.json";

resources/js/stores/video.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,31 @@ export const useVideoStore = defineStore("video", {
7171
};
7272
},
7373

74-
async likeVideo() {
74+
async likeVideo(videoId) {
7575
const axiosInstance = axios.getAxiosInstance();
7676

7777
try {
78-
const uri = this.currentVideo.has_liked
79-
? `/api/v1/video/unlike/${this.currentVideo.id}`
80-
: `/api/v1/video/like/${this.currentVideo.id}`;
81-
await axiosInstance.post(uri).then((res) => {
78+
const uri = `/api/v1/video/like/${videoId}`;
79+
return await axiosInstance.post(uri).then((res) => {
8280
this.currentVideo.has_liked = res.data.has_liked;
8381
this.currentVideo.likes = res.data.likes;
82+
return res;
83+
});
84+
} catch (error) {
85+
console.error("Error liking post:", error);
86+
throw error;
87+
}
88+
},
89+
90+
async unlikeVideo(videoId) {
91+
const axiosInstance = axios.getAxiosInstance();
92+
93+
try {
94+
const uri = `/api/v1/video/unlike/${videoId}`;
95+
return await axiosInstance.post(uri).then((res) => {
96+
this.currentVideo.has_liked = res.data.has_liked;
97+
this.currentVideo.likes = res.data.likes;
98+
return res;
8499
});
85100
} catch (error) {
86101
console.error("Error liking post:", error);

0 commit comments

Comments
 (0)