Skip to content

Commit

Permalink
feature/likes (#41)
Browse files Browse the repository at this point in the history
Add likes backend and frontend and moved home to a dedicated challenge page and added a dynamic redirect functionality to the record page.
  • Loading branch information
mobergmann authored Mar 3, 2024
1 parent c4de341 commit bfc9133
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 24 deletions.
95 changes: 95 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ tags:
description: Endpoint for accessing the public profile for other users on the platform.
- name: activities
description: Endpoint for accessing activities.
- name: likes
description: Endoint for liking activities.

paths:
/auth/login:
Expand Down Expand Up @@ -333,6 +335,90 @@ paths:
description: Activity with the id does not exist
'403':
description: User is not logged in/ Session is not valid
'/activities/{id}/likes':
get:
description: Get the likes of an activity.
operationId: get_likes
tags:
- activities
- likes
parameters:
- name: id
description: ID of the activity
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Like'
'404':
description: Activity with the id does not exist
'403':
description: User is not logged in/ Session is not valid
post:
description: Like the post.
tags:
- activities
- likes
operationId: like_activity
parameters:
- name: id
description: ID of the activity
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Like'
'404':
description: Activity with the id does not exist
'409':
description: Activity was already liked by user
'403':
description: User is not logged in/ Session is not valid
delete:
description: Removes the like of an activity.
operationId: unlike_activity
tags:
- activities
- likes
parameters:
- name: id
description: ID of the activity
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Like'
'404':
description: Activity with the id does not exist or activity was not liked by the user
'403':
description: User is not logged in/ Session is not valid
'/activities/{from}/{to}':
get:
description: Get a list of activities in an given time Interval.
Expand Down Expand Up @@ -469,3 +555,12 @@ components:
new_password:
type: string
format: password
Like:
type: object
properties:
activity_id:
type: integer
format: int64
athlete_id:
type: integer
format: int64
4 changes: 2 additions & 2 deletions public/challenge.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<body class="center-vertically flex-column">

<header>
<h1>Home</h1>
<h1>Challenge</h1>
</header>

<aside class="weekbar">
Expand Down Expand Up @@ -60,7 +60,7 @@ <h2>Log</h2>
</div>

<aside class="controlls">
<button type="button" onclick="window.location = '/record.html'" class="button button-transparent">
<button type="button" onclick="window.location = '/record.html?redirect=/challenge.html'" class="button button-transparent">
<i class="fa-solid fa-circle-plus" style="font-size: 4rem"></i>
</button>
</aside>
Expand Down
13 changes: 13 additions & 0 deletions public/feed.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ <h1><a href="/challenge.html">Weekly PushUps Challenge</a></h1>

<div class="feed container" id="feed-list">
</div>

<aside class="controlls">
<button type="button" onclick="window.location = '/record.html?redirect=/feed.html'" class="button button-transparent">
<i class="fa-solid fa-circle-plus" style="font-size: 4rem"></i>
</button>
</aside>
</main>

<template id="post-template">
Expand Down Expand Up @@ -58,6 +64,13 @@ <h1><a href="/challenge.html">Weekly PushUps Challenge</a></h1>
<div><b>Distance:&nbsp;</b></div>
<div class="post-distance"></div>
</div>

<div class="container flex-row">
<button class="post-like-button">
<div class="post-like-icon"></div>
</button>
<div class="post-likes"></div>
</div>
</div>
</template>

Expand Down
76 changes: 74 additions & 2 deletions public/feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,72 @@ import "/scripts/helpers.js"
import {get_from_to as get_activities} from "/scripts/api/activities.js";
import {get_id as get_user_by_id} from "/scripts/api/users.js";
import {get as get_account} from "/scripts/api/account.js";
import {get as get_likes, create as like_post, remove as unlike_post} from "/scripts/api/likes.js";

const current_user = await (async () => {
let raw = await get_account();
if (!raw.ok) {
alert("You are not logged in, we will redirect you back to the main site.");
window.location = "/";
}
return raw.value;
})();

async function like_button_click(id) {
// retrieve list of likes for that post
const likes = await (async () => {
let raw = await get_likes(id);
if (!raw.ok) {
alert("Error while fetching Likes for:\n" + raw.value);
throw "Error while fetching Likes for:\n" + raw.value;
}
return raw.value;
})();

// if the current user has already liked the post
let liked = likes.find((i) => i.athlete_id === current_user.id);
if (liked === undefined) {
liked = false;
}

// invert the liked lag to get the necessary action
let liking = !liked;

// make the api request based on action
if (liking) {
await like_post(id);
} else {
await unlike_post(id);
}

// update the frontend
await update_likes(id);
}

async function update_likes(activity_id) {
// retrieve list of likes for that post
const likes = await (async () => {
let raw = await get_likes(activity_id);
if (!raw.ok) {
alert("Error while fetching Likes for:\n" + raw.value);
throw "Error while fetching Likes for:\n" + raw.value;
}
return raw.value;
})();

let liking = likes.find((i) => i.athlete_id === current_user.id);
liking = !liking;

let post = document.getElementById(activity_id);
// display number of likes
post.querySelector(".post-likes").innerHTML = likes.length;
// display a hart icon filled or empty, either if the user has liked the activity or not
if (liking) { // this is liking
post.querySelector(".post-like-icon").innerHTML = `<i class="fa-regular fa-heart"></i>`;
} else { // this is unliking
post.querySelector(".post-like-icon").innerHTML = `<i class="fa-solid fa-heart"></i>`;
}
}

async function main() {
let res = await get_account();
Expand Down Expand Up @@ -40,7 +106,8 @@ async function main() {

let user = await get_user_by_id(activity.author_id);
if (!user.ok) {
alert("Error while fetching Activities:\n" + activities.value);
alert("Error while fetching User:\n" + activity.author_id);
return;
}
user_by_id.set(activity.author_id, user.value);
}
Expand All @@ -53,7 +120,7 @@ async function main() {
let duration = "";
{
const diff = new Date(activity.end_time) - new Date(activity.start_time);
if (diff == 0) {
if (diff === 0) {
duration = "no duration";
}
else {
Expand All @@ -75,14 +142,19 @@ async function main() {
}
}

clone.querySelector(".post").id = `${activity.id}`;
clone.querySelector(".post-name").innerHTML = author_name;
clone.querySelector(".post-athlete-link").href = athlete_link;
clone.querySelector(".post-activity-type").innerHTML = activity.activity_type;
clone.querySelector(".post-start-time").innerHTML = activity.start_time;
clone.querySelector(".post-duration").innerHTML = duration;
clone.querySelector(".post-distance").innerHTML = activity.amount;
clone.querySelector(".post-like-button").onclick = () => like_button_click(activity.id);

post_list.append(clone);

// display the likes AFTER the post has been spawned
await update_likes(activity.id);
}
}

Expand Down
6 changes: 3 additions & 3 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<main class="main">
<header>
<h1>Sport Challenge</h1>
<p>Motivate yourself to do activities, compare youreself to others for the extra dose of motivation,
<p>Motivate yourself to do activities, compare yourself to others for the extra dose of motivation,
or just participate in challenges with Sport Challenge.</p>
</header>

Expand All @@ -37,8 +37,8 @@ <h2>Open Feed</h2>

<div class="dashboard-link" style="display: none">
<h2>Open Challenges</h2>
<p>You are already loogged in and you can open the challenges!</p>
<button onclick="window.location = '/home.html'" class="button button-primary">
<p>You are already logged in, and you can open the challenges!</p>
<button onclick="window.location = '/challenge.html'" class="button button-primary">
<i class="fa-solid fa-table-columns"></i>
Challenge
</button>
Expand Down
7 changes: 6 additions & 1 deletion public/record.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ document.querySelector("#form").addEventListener("submit", async (e) => {
let activity = new NewActivity(amount, activity_type, start_time_str, end_time_str);
let res = await create(activity);
if (res.ok) {
window.location = "/home.html";
const urlParams = new URLSearchParams(window.location.search);
let target_location = urlParams.get('redirect');
if (target_location === null) {
target_location = "/index.html";
}
window.location = target_location;
} else {
console.error(res.value);
alert(`Error while submitting new activity: ${res.value}`);
Expand Down
77 changes: 77 additions & 0 deletions public/scripts/api/likes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {BASE_ACTIVITIES_URL, STATUS, Result} from "./main.js";

export class Like {
constructor(athlete_id, activity_id) {
this.athlete_id = athlete_id;
this.activity_id = activity_id;
}
}

/// get a list of likes of a given activity
/// :param activity_id the id of the activity
/// :return `Vec<Like>` returns list of likes
export async function get(activity_id) {
const request = new Request(`${BASE_ACTIVITIES_URL}/${activity_id}/likes`, {
method: "GET",
credentials: 'include',
});

let response = await fetch(request);
if (response.status === STATUS.OK) {
let raw = await response.json();
let likes = [];
for (const value of raw) {
likes.push(new Like(value.athlete_id, value.activity_id));
}
return new Result(true, likes);
} else {
let error = await response.text();
return new Result(false, error);
}
}

/// creates a new activity
/// :param activity the activity object of type `NewActivity` with the updated informations of the activity
/// :return `Activity` returns the newly created activity
export async function create(activity_id) {
const request = new Request(`${BASE_ACTIVITIES_URL}/${activity_id}/likes`, {
method: "POST",
credentials: 'include',
});

let response = await fetch(request);
if (response.status === STATUS.OK) {
let raw = await response.json();
let likes = [];
for (const value of raw) {
likes.push(new Like(value.athlete_id, value.activity_id));
}
return new Result(true, likes);
} else {
let error = await response.text();
return new Result(false, error);
}
}

/// delete an activity
/// :param id id of the activity to remove
/// :return `Activity` returns the deleted activity
export async function remove(activity_id) {
const request = new Request(`${BASE_ACTIVITIES_URL}/${activity_id}/likes`, {
method: "DELETE",
credentials: 'include',
});

let response = await fetch(request);
if (response.status === STATUS.OK) {
let raw = await response.json();
let likes = [];
for (const value of raw) {
likes.push(new Like(value.athlete_id, value.activity_id));
}
return new Result(true, likes);
} else {
let error = await response.text();
return new Result(false, error);
}
}
1 change: 1 addition & 0 deletions public/scripts/api/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const STATUS = {
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
GONE: 410,
INTERNAL_SERVER_ERROR: 500,
};

Expand Down
Loading

0 comments on commit bfc9133

Please sign in to comment.