-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from BeatBuzzer/feat/friend-system
Feat/friend system
- Loading branch information
Showing
21 changed files
with
725 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
DO | ||
$$ | ||
BEGIN | ||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'friendship_status') THEN | ||
CREATE TYPE friendship_status AS ENUM ('pending', 'accepted', 'declined'); | ||
END IF; | ||
END | ||
$$; | ||
|
||
CREATE TABLE IF NOT EXISTS friendships | ||
( | ||
friendship_id SERIAL PRIMARY KEY, | ||
user1_id UUID NOT NULL, | ||
user2_id UUID NOT NULL, | ||
status friendship_status NOT NULL, | ||
action_user_id UUID NOT NULL, -- The user who performed the last action | ||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), | ||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), | ||
|
||
-- Ensure user1_id is always less than user2_id to prevent duplicate friendships | ||
CONSTRAINT ensure_user_order CHECK (user1_id < user2_id), | ||
CONSTRAINT unique_friendship UNIQUE (user1_id, user2_id), | ||
|
||
-- Foreign keys | ||
CONSTRAINT fk_user1 FOREIGN KEY (user1_id) REFERENCES users (id) ON DELETE CASCADE, | ||
CONSTRAINT fk_user2 FOREIGN KEY (user2_id) REFERENCES users (id) ON DELETE CASCADE, | ||
CONSTRAINT fk_action_user FOREIGN KEY (action_user_id) REFERENCES users (id) | ||
); | ||
|
||
CREATE INDEX IF NOT EXISTS idx_friendship_user1 ON friendships (user1_id, status); | ||
CREATE INDEX IF NOT EXISTS idx_friendship_user2 ON friendships (user2_id, status); | ||
|
||
-- Functions | ||
|
||
-- automatically update updated_at timestamp | ||
CREATE OR REPLACE FUNCTION update_updated_at_column() | ||
RETURNS TRIGGER AS | ||
$$ | ||
BEGIN | ||
NEW.updated_at = now(); | ||
RETURN NEW; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
CREATE OR REPLACE TRIGGER update_friendships_timestamp | ||
BEFORE UPDATE | ||
ON friendships | ||
FOR EACH ROW | ||
EXECUTE FUNCTION update_updated_at_column(); | ||
|
||
-- Send a friend request | ||
CREATE OR REPLACE FUNCTION send_friend_request(sender_id UUID, receiver_id UUID) RETURNS void AS | ||
$$ | ||
DECLARE | ||
smaller_id UUID; | ||
larger_id UUID; | ||
BEGIN | ||
-- Determine order of IDs | ||
IF sender_id < receiver_id THEN | ||
smaller_id := sender_id; | ||
larger_id := receiver_id; | ||
ELSE | ||
smaller_id := receiver_id; | ||
larger_id := sender_id; | ||
END IF; | ||
|
||
-- Insert friendship record | ||
INSERT INTO friendships (user1_id, user2_id, status, action_user_id) | ||
VALUES (smaller_id, larger_id, 'pending', sender_id) | ||
ON CONFLICT (user1_id, user2_id) DO UPDATE | ||
SET status = CASE | ||
WHEN friendships.status = 'declined' THEN 'pending'::friendship_status | ||
ELSE friendships.status | ||
END, | ||
action_user_id = sender_id; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
-- accept friendship request | ||
CREATE OR REPLACE FUNCTION accept_friend_request_by_id(friendship_id_param INT) | ||
RETURNS void AS | ||
$$ | ||
BEGIN | ||
UPDATE friendships | ||
SET status = 'accepted' | ||
WHERE friendship_id = friendship_id_param | ||
AND status = 'pending'; | ||
|
||
IF NOT FOUND THEN | ||
RAISE EXCEPTION 'No pending friend request found with this ID'; | ||
END IF; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
|
||
-- decline friendship request | ||
CREATE OR REPLACE FUNCTION decline_friend_request_by_id(friendship_id_param INT) | ||
RETURNS void AS | ||
$$ | ||
BEGIN | ||
UPDATE friendships | ||
SET status = 'declined' | ||
WHERE friendship_id = friendship_id_param | ||
AND status = 'pending'; | ||
|
||
IF NOT FOUND THEN | ||
RAISE EXCEPTION 'No pending friend request found with this ID'; | ||
END IF; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
-- delete friendship | ||
CREATE OR REPLACE FUNCTION remove_friend(user_id UUID, friend_id UUID) RETURNS void AS | ||
$$ | ||
DECLARE | ||
smaller_id UUID; | ||
larger_id UUID; | ||
BEGIN | ||
-- Determine order of IDs | ||
IF friend_id < user_id THEN | ||
smaller_id := friend_id; | ||
larger_id := user_id; | ||
ELSE | ||
smaller_id := user_id; | ||
larger_id := friend_id; | ||
END IF; | ||
|
||
DELETE | ||
FROM friendships | ||
WHERE user1_id = smaller_id | ||
AND user2_id = larger_id | ||
AND status = 'accepted'; | ||
|
||
IF NOT FOUND THEN | ||
RAISE EXCEPTION 'No active friendship found between these users'; | ||
END IF; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
-- 2nd version with id | ||
CREATE OR REPLACE FUNCTION remove_friend_by_id(friendship_id_param INT) RETURNS void AS | ||
$$ | ||
BEGIN | ||
DELETE | ||
FROM friendships | ||
WHERE friendship_id = friendship_id_param | ||
AND status = 'accepted'; | ||
|
||
IF NOT FOUND THEN | ||
RAISE EXCEPTION 'No active friendship found with this ID'; | ||
END IF; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
-- retrieve all friends, incoming and outgoing friend requests | ||
CREATE OR REPLACE FUNCTION get_friends(user_id UUID) | ||
RETURNS TABLE | ||
( | ||
friendship_id INT, | ||
friend_id UUID, | ||
friend_username TEXT, | ||
friend_avatar TEXT, | ||
friend_spotify_id TEXT, | ||
friend_spotify_visibility BOOLEAN, | ||
status friendship_status, | ||
action_user_id UUID, | ||
created_at TIMESTAMP WITH TIME ZONE, | ||
updated_at TIMESTAMP WITH TIME ZONE, | ||
request_type TEXT | ||
) | ||
AS | ||
$$ | ||
BEGIN | ||
RETURN QUERY | ||
SELECT f.friendship_id, | ||
CASE WHEN f.user1_id = user_id THEN f.user2_id ELSE f.user1_id END AS friend_id, | ||
u.username AS friend_username, | ||
u.avatar_url AS friend_avatar, | ||
u.spotify_id AS friend_spotify_id, | ||
u.spotify_visibility AS friend_spotify_visibility, | ||
f.status, | ||
f.action_user_id, | ||
f.created_at, | ||
f.updated_at, | ||
CASE WHEN f.action_user_id = user_id THEN 'outgoing' ELSE 'incoming' END AS request_type | ||
FROM friendships f, users u | ||
WHERE (f.user1_id = user_id OR f.user2_id = user_id) | ||
AND (f.status != 'declined') | ||
AND (CASE WHEN f.user1_id = user_id THEN f.user2_id ELSE f.user1_id END = u.id); | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
-- examples | ||
-- SELECT send_friend_request('sender', 'receiver'); | ||
-- SELECT accept_friend_request_by_id(4); | ||
-- SELECT decline_friend_request_by_id(4); | ||
-- SELECT remove_friend('friend_user'); | ||
-- SELECT * FROM get_friends('user_id'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
CREATE TABLE users | ||
( | ||
id uuid not null references auth.users on delete cascade, | ||
avatar_url text, | ||
username text not null, | ||
spotify_id text not null, | ||
spotify_visibility boolean not null default false, | ||
primary key (id) | ||
); | ||
|
||
ALTER TABLE users | ||
ADD CONSTRAINT unique_username UNIQUE (username), | ||
ADD CONSTRAINT valid_username check (username <> '' AND length(trim(username)) >= 4 AND username ~ '^[a-zA-Z0-9_]+$'); | ||
|
||
CREATE INDEX idx_username ON users(username); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<script setup lang="ts"> | ||
</script> | ||
|
||
<template> | ||
<div class="flex space-x-3 w-full"> | ||
<button class="bg-[#22a9fb] hover:bg-blue-700 text-white font-bold py-20 rounded-3xl text-lg w-full"> | ||
Start Game | ||
</button> | ||
<button class="bg-[#22a9fb] hover:bg-blue-700 text-white font-bold py-20 rounded-3xl text-lg w-full"> | ||
Start Round | ||
</button> | ||
</div> | ||
</template> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<script setup lang="ts"> | ||
</script> | ||
|
||
<template> | ||
<div class="w-full bg-gray-200 p-3 mt-auto rounded-3xl my-3"> | ||
<p class="my-1 ">Opponentes Turn</p> | ||
<div class="flex space-x-3"> | ||
<HomeUsersUserBox name="test1" :user-turn="false"/> | ||
<HomeUsersUserBox name="test2" :user-turn="false"/> | ||
<HomeUsersUserBox name="test3" :user-turn="false"/> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<script setup lang="ts"> | ||
</script> | ||
|
||
<template> | ||
<div class="w-full bg-gray-200 p-3 mt-auto rounded-3xl my-3 h-full"> | ||
<p class="my-1">Your Turn</p> | ||
<div class="flex-col space-y-3"> | ||
<HomeUsersUserBox name="test1" :user-turn="true"/> | ||
<HomeUsersUserBox name="test2" :user-turn="true"/> | ||
<HomeUsersUserBox name="test3" :user-turn="true"/> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<script setup lang="ts"> | ||
const props = defineProps({ | ||
profilePicture: { | ||
type: String, | ||
default: 'https://i.scdn.co/image/ab6775700000ee855d4c281804e8773208248312' | ||
}, | ||
name: { | ||
type: String, | ||
default: 'Opponent' | ||
}, | ||
userTurn: { | ||
type: Boolean, | ||
default: false | ||
} | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div :class="['bg-blue-600', props.userTurn ? 'flex items-center h-30 w-full rounded-3xl p-3' : 'flex flex-col items-center justify-center h-30 w-full rounded-3xl p-3']"> | ||
<img :class="['rounded-full w-12 h-12 sm:w-16 sm:h-16 md:w-16 md:h-16 lg:w-16 lg:h-16', props.userTurn ? 'mr-3' : 'mb-2']" :src="props.profilePicture" :alt="name"> | ||
<p class="text-white">{{ props.name }}</p> | ||
<button v-if="props.userTurn" class="ml-auto p-2 sm:p-3 md:p-4 lg:p-5" @click="console.log(props.name)"> | ||
<NuxtImg src="icons/playButton.svg" alt="Play" class="w-12 h-12 sm:w-16 sm:h-16 md:w-16 md:h-16 lg:w-16 lg:h-16 hover:bg-black"/> | ||
</button> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<script setup lang="ts"> | ||
import { | ||
FriendshipStatus, | ||
FriendshipType, | ||
type GetFriendsResponse | ||
} from "@/types/api/user.friends"; | ||
const requests: Ref<GetFriendsResponse[]> = useState("incoming_friendships", () => []) | ||
const friends: Ref<GetFriendsResponse[]> = useState("accepted_friendships", () => []) | ||
const session = useSupabaseSession() | ||
onMounted(async () => { | ||
if (session.value) { | ||
await getFriendships() | ||
friends.value.push(friends.value[0]) | ||
} | ||
}) | ||
async function getFriendships() { | ||
$fetch<GetFriendsResponse[]>('http://localhost:3000/api/v1/user/friends') | ||
.then((data) => { | ||
requests.value = data.filter((item) => item.request_type == FriendshipType.INCOMING && item.status === FriendshipStatus.PENDING) | ||
friends.value = data.filter((item) => item.status === FriendshipStatus.ACCEPTED) | ||
}); | ||
} | ||
</script> | ||
|
||
<template> | ||
<div class="w-full bg-gray-200 p-3 mt-auto rounded-3xl my-3"> | ||
<p class="my-1 ">Friends</p> | ||
<div class="flex space-x-3 overflow-x-auto"> | ||
<HomeUsersUserBox | ||
v-for="item in friends" :key="item.friend_id" :name="item.friend_id" :user-turn="false" | ||
class="shrink-0 w-[calc(33.33%-1rem)]" /> | ||
</div> | ||
|
||
|
||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<script setup lang="ts"> | ||
</script> | ||
|
||
<template> | ||
<div class="flex flex-col"> | ||
<div class="flex flex-col items-center justify-center mb-3"> | ||
<img | ||
src="https://i.scdn.co/image/ab6775700000ee855d4c281804e8773208248312" | ||
class="w-16 h-16 rounded-full" > | ||
<p>Nickname</p> | ||
</div> | ||
|
||
<div class="grid grid-cols-2 gap-4"> | ||
<div class="bg-gray-200 h-8 rounded-3xl px-3">Streak</div> | ||
<div class="bg-gray-200 h-8 rounded-3xl px-3">Games played</div> | ||
<div class="bg-gray-200 h-8 rounded-3xl px-3">Placeholder</div> | ||
<div class="bg-gray-200 h-8 rounded-3xl px-3">Date joined</div> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<script setup lang="ts"> | ||
import Friendlist from './Friendlist.vue'; | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<div class="p-3"> | ||
<ProfileInformation class="py-8"/> | ||
<Friendlist/> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<script setup lang="ts"> | ||
</script> | ||
|
||
<template> | ||
<div class="flex flex-col h-screen max-h-screen items-center"> | ||
<!-- Wrapper div für die Breiten-Kontrolle --> | ||
<div class="w-full xl:w-1/2 2xl:w-1/4 flex flex-col h-full"> | ||
<!-- Content --> | ||
<div class="flex-1 border rounded-3xl"> | ||
<slot name="content"/> | ||
</div> | ||
|
||
<!-- Footer --> | ||
<div class="basis-24 border rounded-t-3xl"> | ||
<div class="flex justify-around items-center h-full"> | ||
<slot name="footer"/> | ||
</div> | ||
</div> | ||
|
||
</div> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
</style> |
Oops, something went wrong.