diff --git a/api/src/Controllers/LikeController.php b/api/src/Controllers/LikeController.php index ba88c5e8..e7d803a0 100644 --- a/api/src/Controllers/LikeController.php +++ b/api/src/Controllers/LikeController.php @@ -5,6 +5,7 @@ use Flight; use Matcha\Api\Enums\NotificationType; use Matcha\Api\Model\User; +use Matcha\Api\Services\FameCalculator; class LikeController { @@ -20,6 +21,11 @@ public function store(string $username) } $me->like($target); + try { + (new FameCalculator($me, $target))->onLike(); + } catch (\Throwable $e) { + // calcul error + } if ($target->hasMatch($me->username)) { $notif = [ @@ -62,6 +68,11 @@ public function destroy(string $username) } $me->unlike($user); + try { + (new FameCalculator($me, $user))->onUnLike(); + } catch (\Throwable $e) { + // calcul error + } Flight::json([], 203); } diff --git a/api/src/Controllers/PassController.php b/api/src/Controllers/PassController.php index 34e03a29..345126a0 100644 --- a/api/src/Controllers/PassController.php +++ b/api/src/Controllers/PassController.php @@ -4,12 +4,16 @@ use Flight; use Matcha\Api\Model\User; +use Matcha\Api\Model\Pass; +use Matcha\Api\Services\FameCalculator; +use Matcha\Api\Exceptions\UniqueConstraintException; class PassController { public function store(string $username) { $target = User::find(['username' => $username]); + $me = Flight::user(); if (is_null($target)) { Flight::json([ @@ -19,8 +23,16 @@ public function store(string $username) return; } - Flight::user()->pass($target); + try { + $me->pass($target); + (new FameCalculator($me, $target))->onPass(); + } catch (UniqueConstraintException $e) { + $pass = Pass::find(['user_id' => $me->id, 'target_id' => $target->id]); + $pass->created_at = date('Y-m-d H:i:s'); + + $pass->update(['user_id' => $me->id, 'target_id' => $target->id]); + } Flight::json(['success' => true,], 204); } } diff --git a/api/src/Controllers/ViewController.php b/api/src/Controllers/ViewController.php index fd2fa1d3..9011fa68 100644 --- a/api/src/Controllers/ViewController.php +++ b/api/src/Controllers/ViewController.php @@ -5,6 +5,7 @@ use Flight; use Matcha\Api\Enums\NotificationType; use Matcha\Api\Model\User; +use Matcha\Api\Services\FameCalculator; class ViewController { @@ -30,6 +31,11 @@ public function store(string $username) } $me->view($user); + try { + (new FameCalculator($me, $user))->onView(); + } catch (\Throwable $e) { + // calcul error + } Flight::notifier()->notify($user, [ 'type' => NotificationType::VIEW, diff --git a/api/src/Model/Model.php b/api/src/Model/Model.php index b801bb9c..8cfc35a7 100644 --- a/api/src/Model/Model.php +++ b/api/src/Model/Model.php @@ -43,9 +43,18 @@ public function save(): Model return $this->id > 0 ? $this->update() : $this->create(); } - private function update(): Model + public function update(array $where = []): Model { $data = $this->getData(); + + if (empty($where)) { + $where = ['id' => $this->id]; + } + + $whereList = array_map(function ($k, $v) { + return $k . " = " . $v; + }, array_keys($where), $where); + $sqlQuery = "UPDATE " . $this->getTable() . " SET " . implode( ", ", @@ -55,7 +64,7 @@ private function update(): Model array_values($data) ) ) - . ' WHERE id = ' . $this->id; + . ' WHERE ' . implode(" AND ", $whereList); self::db()->exec($sqlQuery); $this->reload(); diff --git a/api/src/Services/FameCalculator.php b/api/src/Services/FameCalculator.php new file mode 100644 index 00000000..fc9805be --- /dev/null +++ b/api/src/Services/FameCalculator.php @@ -0,0 +1,65 @@ +save(self::K_LIKE * (1 - $this->expected())); + } + + public function onUnLike(): void + { + $this->save(-self::K_LIKE * (1 - $this->expected())); + } + + public function onPass(): void + { + $this->save(-self::K_PASS * $this->expected()); + } + + public function onView(): void + { + $this->save(self::K_VIEW * (1 - $this->expected())); + } + + private function expected(): float + { + $diff = $this->actor->fame_rating - $this->target->fame_rating; + + return 1 / (1 + 10 ** ($diff / 400)); + } + + private function save(float $delta): void + { + $stmt = Flight::db()->prepare( + 'UPDATE users + SET fame_rating = LEAST(:max, GREATEST(:min, fame_rating + :delta)) + WHERE id = :id' + ); + $stmt->execute([ + 'max' => self::MAX_FAME, + 'min' => self::MIN_FAME, + 'delta' => $delta, + 'id' => $this->target->id, + ]); + } +} diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts index bf193b56..793f2615 100644 --- a/frontend/src/api/users.ts +++ b/frontend/src/api/users.ts @@ -86,4 +86,10 @@ export const usersApi = { res.json(), ) }, + + async view(username: string) { + return ApiClient.post(`/users/${username}/view`).then(res => + res.json(), + ) + }, } diff --git a/frontend/src/composable/useUsers.ts b/frontend/src/composable/useUsers.ts index c663e5d2..fdcc345f 100644 --- a/frontend/src/composable/useUsers.ts +++ b/frontend/src/composable/useUsers.ts @@ -61,6 +61,15 @@ export function useUsers() { } } + const view = async (who: string) => { + error.value = null + try { + await usersApi.view(who) + } catch (err: any) { + error.value = err.response?.data?.message || "You can't view this user." + } + } + // --- Modération (Blocages et Signalement) --- const fetchBlockedList = async () => { isLoading.value = true @@ -180,5 +189,6 @@ export function useUsers() { suggestion, suggestionReset, pass, + view, } } diff --git a/frontend/src/views/UserProfileView.vue b/frontend/src/views/UserProfileView.vue index 4b334618..f260d441 100644 --- a/frontend/src/views/UserProfileView.vue +++ b/frontend/src/views/UserProfileView.vue @@ -24,6 +24,7 @@ import { DialogFooter, } from '@/components/ui/dialog' import { ApiClient } from '@/api/client' +import { useUsers } from '@/composable/useUsers' interface Profile { id: number @@ -32,7 +33,7 @@ interface Profile { city: string | null biography: string | null avatar: string | null - photos: { id: number; path: string }[] + photos: string [] tags: string[] liked_by_me: boolean is_match: boolean @@ -49,6 +50,7 @@ const error = ref('') const showMatch = ref(false) const editing = ref(false) const form = ref({ bio: '', city: '', tags: '' }) +const useUser = useUsers() async function load() { loading.value = true @@ -110,7 +112,10 @@ async function saveProfile() { const initials = (name: string) => name.slice(0, 2).toUpperCase() -onMounted(load) +onMounted(() => { + load() + useUser.view(`${route.params.username}`); +}) watch(() => route.params.id, load) @@ -133,9 +138,9 @@ watch(() => route.params.id, load) - +