diff --git a/DB/friendships.sql b/DB/friendships.sql new file mode 100644 index 0000000..3e80df6 --- /dev/null +++ b/DB/friendships.sql @@ -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'); diff --git a/DB/users.sql b/DB/users.sql new file mode 100644 index 0000000..ef78b16 --- /dev/null +++ b/DB/users.sql @@ -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); \ No newline at end of file diff --git a/components/home/Controls/GameButtons.vue b/components/home/Controls/GameButtons.vue new file mode 100644 index 0000000..36344c6 --- /dev/null +++ b/components/home/Controls/GameButtons.vue @@ -0,0 +1,15 @@ + + + + diff --git a/components/home/Turns/Opponent.vue b/components/home/Turns/Opponent.vue new file mode 100644 index 0000000..c8b5acb --- /dev/null +++ b/components/home/Turns/Opponent.vue @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/components/home/Turns/User.vue b/components/home/Turns/User.vue new file mode 100644 index 0000000..824cbb5 --- /dev/null +++ b/components/home/Turns/User.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/home/Users/UserBox.vue b/components/home/Users/UserBox.vue new file mode 100644 index 0000000..c42b901 --- /dev/null +++ b/components/home/Users/UserBox.vue @@ -0,0 +1,26 @@ + + + \ No newline at end of file diff --git a/components/profile/Friendlist.vue b/components/profile/Friendlist.vue new file mode 100644 index 0000000..dac4093 --- /dev/null +++ b/components/profile/Friendlist.vue @@ -0,0 +1,41 @@ + + + diff --git a/components/profile/ProfileInformation.vue b/components/profile/ProfileInformation.vue new file mode 100644 index 0000000..e07e6bd --- /dev/null +++ b/components/profile/ProfileInformation.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/profile/ProfileOverview.vue b/components/profile/ProfileOverview.vue new file mode 100644 index 0000000..f8e09f9 --- /dev/null +++ b/components/profile/ProfileOverview.vue @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/layouts/FooterView.vue b/layouts/FooterView.vue new file mode 100644 index 0000000..6381c9d --- /dev/null +++ b/layouts/FooterView.vue @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/layouts/HeaderFooterView.vue b/layouts/HeaderFooterView.vue index 0cecbb1..11cadc7 100644 --- a/layouts/HeaderFooterView.vue +++ b/layouts/HeaderFooterView.vue @@ -15,8 +15,8 @@ -
- +
+
diff --git a/nuxt.config.ts b/nuxt.config.ts index debde16..9a5de5a 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,7 +1,7 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ compatibilityDate: '2024-04-03', - devtools: {enabled: false}, + devtools: {enabled: true}, modules: ['@nuxtjs/tailwindcss', '@nuxt/eslint', '@nuxtjs/supabase', '@nuxt/image'], supabase: { redirectOptions: { diff --git a/pages/index.vue b/pages/index.vue index 8686f48..0f8d7a4 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,13 +1,6 @@ +