Commit 24e4b7c0 authored by Mathias Lund Ahrn's avatar Mathias Lund Ahrn
Browse files

Merge branch 'testing' into 'master'

Github version to gitlab

See merge request !1
parents cde19659 b1043400
Pipeline #113891 passed with stages
in 2 minutes and 23 seconds
......@@ -20,12 +20,13 @@ from workouts.permissions import (
IsReadOnly,
IsPublic,
IsWorkoutPublic,
IsOwnerOfExercise
)
from workouts.mixins import CreateListModelMixin
from workouts.models import Workout, Exercise, ExerciseInstance, WorkoutFile
from workouts.models import Workout, Exercise, ExerciseInstance, WorkoutFile, ExerciseFile
from workouts.serializers import WorkoutSerializer, ExerciseSerializer
from workouts.serializers import RememberMeSerializer
from workouts.serializers import ExerciseInstanceSerializer, WorkoutFileSerializer
from workouts.serializers import ExerciseInstanceSerializer, WorkoutFileSerializer, ExerciseFileSerializer
from django.core.exceptions import PermissionDenied
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.response import Response
......@@ -48,6 +49,9 @@ def api_root(request, format=None):
"workout-files": reverse(
"workout-file-list", request=request, format=format
),
"exercise-files": reverse(
"exercise-file-list", request=request, format=format
),
"comments": reverse("comment-list", request=request, format=format),
"likes": reverse("like-list", request=request, format=format),
}
......@@ -186,6 +190,10 @@ class ExerciseList(
queryset = Exercise.objects.all()
serializer_class = ExerciseSerializer
permission_classes = [permissions.IsAuthenticated]
parser_classes = [
MultipartJsonParser,
JSONParser
]
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
......@@ -193,6 +201,8 @@ class ExerciseList(
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class ExerciseDetail(
mixins.RetrieveModelMixin,
......@@ -221,6 +231,23 @@ class ExerciseDetail(
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class ExerciseFileDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
queryset = ExerciseFile.objects.all()
serializer_class = ExerciseFileSerializer
permission_classes = [permissions.IsAuthenticated & IsOwnerOfExercise]
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class ExerciseInstanceList(
mixins.ListModelMixin,
......
File added
version: '3'
version: "3.8"
services:
backend:
container_name: django_group_${GROUPID}
build:
context: backend/secfit/
dockerfile: Dockerfile
args:
DJANGO_SUPERUSER_USERNAME: "${DJANGO_SUPERUSER_USERNAME}"
DJANGO_SUPERUSER_PASSWORD: "${DJANGO_SUPERUSER_PASSWORD}"
DJANGO_SUPERUSER_EMAIL: "${DJANGO_SUPERUSER_EMAIL}"
environment:
- GROUPID=${GROUPID}
networks:
backend_bridge:
ipv4_address: 10.${GROUPID}.0.4
image: jonev/secfit:backend
volumes:
- ./db.sqlite3:/code/db.sqlite3
application:
container_name: node_group_${GROUPID}
build:
context: frontend/
dockerfile: Dockerfile
args:
GROUPID: ${GROUPID}
DOMAIN: ${DOMAIN}
URL_PREFIX: ${URL_PREFIX}
PORT_PREFIX: ${PORT_PREFIX}
networks:
backend_bridge:
ipv4_address: 10.${GROUPID}.0.5
frontend:
image: jonev/secfit:frontend
web:
container_name: nginx_group_${GROUPID}
build:
context: .
dockerfile: Dockerfile
proxy:
image: nginx:perl
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- ${PORT_PREFIX}${GROUPID}:80
environment:
- GROUPID=${GROUPID}
- PORT_PREFIX=${PORT_PREFIX}
networks:
backend_bridge:
ipv4_address: 10.${GROUPID}.0.6
networks:
backend_bridge:
driver: bridge
ipam:
config:
- subnet: 10.${GROUPID}.0.0/18
- 4011:80
{
"name": "Cordova-Dev",
"build": {
"dockerfile": "../Dockerfile"
}
}
/node_modules
\ No newline at end of file
# Get docker image
FROM debian:buster-slim
FROM node:lts-buster-slim
# Import groupid and host environment variable
ARG GROUPID
......@@ -14,11 +14,13 @@ WORKDIR /app
COPY . /app/
# Set the host variable in default.js (overwrite)
RUN echo "const HOST = '${URL_PREFIX}${DOMAIN}:${PORT_PREFIX}${GROUPID}';" > ./www/scripts/defaults.js
RUN echo "const HOST = 'https://secfit.vassbo.as';" > ./www/scripts/defaults.js
# Install cordova
# Also install shelljs because of windows for some reason
RUN apt-get update -y && apt-get install npm -y && npm install -g cordova && npm install shelljs
RUN npm install --silent -g cordova \
&& npm install --silent -g shelljs \
&& npm install -y --silent
# Run cordova app
CMD [ "cordova", "run", "browser", "--release", "--port=3000" ]
# Development
Start development:
```bash
cordova run --emulator
```
- No reload on file change
This diff is collapsed.
......@@ -32,4 +32,4 @@
"android"
]
}
}
\ No newline at end of file
}
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>HelloCordova</name>
<name>SecFit</name>
<description>
A sample Apache Cordova application that responds to the deviceready event.
The most secure fitness logging application there is.
</description>
<author email="dev@cordova.apache.org" href="http://cordova.io">
Apache Cordova Team
<author email="kyleo@stud.ntnu.no">
Kyle Orlando
</author>
<content src="index.html" />
<allow-intent href="http://*/*" />
......
......@@ -35,6 +35,17 @@
<input type="text" class="form-control" id="inputUnit" name="unit" readonly>
</div>
<div class="col-lg-6"></div>
<div class="col-lg-6" id="media-group">
</div>
<div class="col-lg-6"></div>
<div class="col-lg-6">
<div class="input-group">
<input type="file" class="form-control hide" id="customFile" name="files" multiple disabled>
</div>
<div id="uploaded-files" class="ms-1 mt-2">
</div>
</div>
<div class="col-lg-6"></div>
<div class="col-lg-6">
<input type="button" class="btn btn-primary hide" id="btn-ok-exercise" value=" OK ">
<input type="button" class="btn btn-primary" id="btn-edit-exercise" value=" Edit ">
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profile</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<script src="https://kit.fontawesome.com/0ce6c392ca.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="styles/style.css">
<script src="scripts/navbar.js" type="text/javascript" defer></script>
</head>
<body>
<navbar-el></navbar-el>
<div class="container">
<div class="row">
<div class="col-lg">
<h3 class="mt-3">Profile</h3>
</div>
</div>
<form class="row g-3 mb-4" id="form-profile">
<div class="col-lg-6">
<label for="inputUsername" class="form-label">Username</label>
<input type="text" class="form-control" id="inputUsername" name="username" readonly>
</div>
<div class="col-lg-6">
<label for="inputEmail" class="form-label">Email</label>
<input type="text" class="form-control" id="inputEmail" name="email" readonly>
</div>
<div class="col-lg-6">
<label for="inputPhone" class="form-label">Phone</label>
<input type="text" class="form-control" id="inputPhone" name="phone_number" readonly>
</div>
<div class="col-lg-6">
<label for="inputAddress" class="form-label">Street address</label>
<input type="text" class="form-control" id="inputAddress" name="street_address" readonly>
</div>
<div class="col-lg-6">
<label for="inputCity" class="form-label">City</label>
<input type="text" class="form-control" id="inputCity" name="city" readonly>
</div>
<div class="col-lg-6">
<label for="inputCountry" class="form-label">Country</label>
<input type="text" class="form-control" id="inputCountry" name="country" readonly>
</div>
<div class="col-lg-6">
</div>
<div class="col-lg-6">
<input type="button" class="btn btn-success float-end hide mx-1" id="btn-confirm-edit" value="Confirm">
<input type="button" class="btn btn-secondary float-end hide mx-1" id="btn-cancel-edit" value="Cancel">
<input type="button" class="btn btn-danger float-end hide mx-1" id="btn-initiate-delete" value="Delete">
<input type="button" class="btn btn-primary float-end mx-1" id="btn-edit-profile" value="Edit">
</div>
</form>
</div>
<div id="delete-modal" class="hide modal-content">
<div class="row m-3">
<div class="col-lg-4"></div>
<div class="col-lg-4 text-center">
<h5>Are you sure you want to delete your profile?</h5>
</div>
<div class="col-lg-4"></div>
<div class="col-lg-4">
</div>
<div class="col-lg-4 text-center">
<input type="button" class="btn btn-danger" id="btn-delete-user" value="Delete">
<input type="button" class="btn btn-secondary" id="btn-cancel-delete" value="Cancel">
</div>
<div class="col-lg-4">
</div>
</div>
</div>
<script src="scripts/defaults.js"></script>
<script src="scripts/scripts.js"></script>
<script src="scripts/profile.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
crossorigin="anonymous"></script>
</body>
</html>
\ No newline at end of file
......@@ -3,6 +3,7 @@ let okButton;
let deleteButton;
let editButton;
let oldFormData;
let currentUser;
function handleCancelButtonDuringEdit() {
......@@ -29,14 +30,26 @@ function handleCancelButtonDuringCreate() {
window.location.replace("exercises.html");
}
async function createExercise() {
function exerciseForm() {
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let body = {"name": formData.get("name"),
"description": formData.get("description"),
"unit": formData.get("unit")};
const submitForm = new FormData();
submitForm.append("name", formData.get("name"));
submitForm.append("description", formData.get("description"));
submitForm.append("unit", formData.get("unit"));
for (let file of formData.getAll("files")) {
submitForm.append("files", file);
}
return submitForm;
}
async function createExercise() {
const submitForm = exerciseForm();
let response = await sendRequest("POST", `${HOST}/api/exercises/`, body);
let response = await sendRequest("POST", `${HOST}/api/exercises/`, submitForm, "");
if (response.ok) {
window.location.replace("exercises.html");
......@@ -74,7 +87,6 @@ async function deleteExercise(id) {
async function retrieveExercise(id) {
let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`);
console.log(response.ok);
if (!response.ok) {
let data = await response.json();
let alert = createAlert("Could not retrieve exercise data!", data);
......@@ -83,6 +95,10 @@ async function retrieveExercise(id) {
let exerciseData = await response.json();
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
if(currentUser.username === exerciseData["owner_username"]) {
const customFile = document.querySelector("#customFile");
customFile.classList.remove("hide");
}
for (let key of formData.keys()) {
let selector = `input[name="${key}"], textarea[name="${key}"]`;
......@@ -90,16 +106,22 @@ async function retrieveExercise(id) {
let newVal = exerciseData[key];
input.value = newVal;
}
if (exerciseData.files && exerciseData.files.length > 0) {
const mediaGroup = document.querySelector("#media-group");
const img = document.createElement("img");
img.src = exerciseData.files[0].file;
mediaGroup.appendChild(img);
}
}
}
async function updateExercise(id) {
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let body = {"name": formData.get("name"),
"description": formData.get("description"),
"unit": formData.get("unit")};
let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, body);
const submitForm = exerciseForm();
let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, submitForm, "");
if (!response.ok) {
let data = await response.json();
......@@ -128,6 +150,7 @@ window.addEventListener("DOMContentLoaded", async () => {
deleteButton = document.querySelector("#btn-delete-exercise");
editButton = document.querySelector("#btn-edit-exercise");
oldFormData = null;
currentUser = await getCurrentUser();
const urlParams = new URLSearchParams(window.location.search);
......@@ -138,7 +161,9 @@ window.addEventListener("DOMContentLoaded", async () => {
editButton.addEventListener("click", handleEditExerciseButtonClick);
deleteButton.addEventListener("click", (async (id) => await deleteExercise(id)).bind(undefined, exerciseId));
okButton.addEventListener("click", (async (id) => await updateExercise(id)).bind(undefined, exerciseId));
okButton.addEventListener("click", async () => {
await updateExercise(exerciseId)
});
}
//create
else {
......
......@@ -18,6 +18,7 @@ class NavBar extends HTMLElement {
<a class="nav-link hide" id="nav-exercises" href="exercises.html">Exercises</a>
<a class="nav-link hide" id="nav-mycoach" href="mycoach.html">Coach</a>
<a class="nav-link hide" id="nav-myathletes" href="myathletes.html">Athletes</a>
<a class="nav-link hide" id="nav-profile" href="profile.html">Profile</a>
<hr>
</div>
<div class="my-2 my-lg-0 me-5">
......
let deleteProfileButton;
let initiateDeleteButton;
let cancelDeleteButton;
let editProfileButton;
let confirmButton;
let cancelEditButton;
async function getCurrentUser() {
let user = null;
let response = await sendRequest("GET", `${HOST}/api/users/?user=current`)
if (!response.ok) {
console.log("COULD NOT RETRIEVE CURRENTLY LOGGED IN USER");
} else {
let data = await response.json();
user = data.results[0];
}
return user;
}
async function retrieveProfile() {
let user = null;
let response = await sendRequest("GET", `${HOST}/api/users/?user=current`);
if (!response.ok) {
let alert = createAlert("Could not retrieve profile data!");
document.body.prepend(alert);
} else {
let data = await response.json();
user = data.results[0];
let form = document.querySelector("#form-profile");
let formData = new FormData(form);
for (let key of formData.keys()) {
let selector = `input[name="${key}"], textarea[name="${key}"]`;
let input = form.querySelector(selector);
let newVal = user[key];
input.value = newVal;
}
}
return user;
}
function handleEditProfile() {
confirmButton = document.querySelector("#btn-confirm-edit");
cancelEditButton = document.querySelector("#btn-cancel-edit");
setReadOnly(false, "#form-profile");
editProfileButton.classList.add("hide");
confirmButton.classList.remove("hide");
cancelEditButton.classList.remove("hide");
initiateDeleteButton.classList.remove("hide");
cancelEditButton.addEventListener("click", handleCancel);
}
async function updateProfile(user) {
let submitForm = generateProfileForm();
let response = await sendRequest("PATCH", `${HOST}/api/users/${user.id}/`, submitForm, "");
if (!response.ok) {
let alert = createAlert("Could not update profile!");
document.body.prepend(alert);
} else {
location.reload();
}
}
function handleCancel() {
location.reload();
}
function generateProfileForm() {
let form = document.querySelector("#form-profile");
let formData = new FormData(form);
let submitForm = new FormData();
submitForm.append("username", formData.get('username'));
submitForm.append("email", formData.get('email'));
submitForm.append("phone_number", formData.get('phone_number'));
submitForm.append("street_address", formData.get('street_address'));
submitForm.append("city", formData.get('city'));
submitForm.append("country", formData.get('country'));
return submitForm;
}
async function deleteProfile(user) {
let response = await sendRequest("DELETE", `${HOST}/api/users/${user.id}/`);
if (!response.ok) {
let data = await response.json();
let alert = createAlert(`Could not delete profile ${id}!`, data);
document.body.prepend(alert);
} else {
window.location.replace("logout.html");
}
}
window.addEventListener("DOMContentLoaded", async () => {
let user = await retrieveProfile();
deleteProfileButton = document.querySelector("#btn-delete-user");
cancelDeleteButton = document.querySelector("#btn-cancel-delete");
initiateDeleteButton = document.querySelector("#btn-initiate-delete");
editProfileButton = document.querySelector("#btn-edit-profile");
confirmButton = document.querySelector("#btn-confirm-edit");
let modal = document.querySelector("#delete-modal");
confirmButton.addEventListener("click", (() => updateProfile(user)))
editProfileButton.addEventListener("click", (() => handleEditProfile()));
deleteProfileButton.addEventListener("click", (async () => await deleteProfile(user)));
cancelDeleteButton.addEventListener("click", (() => handleCancel()));
initiateDeleteButton.addEventListener("click", (() => {
initiateDeleteButton.classList.add("hide");
modal.classList.remove("hide");
}))
});
\ No newline at end of file
......@@ -32,6 +32,7 @@ function updateNavBar() {
document.querySelector('a[href="mycoach.html"').classList.remove("hide");
document.querySelector('a[href="exercises.html"').classList.remove("hide");
document.querySelector('a[href="myathletes.html"').classList.remove("hide");
document.querySelector('a[href="profile.html"').classList.remove("hide");
} else {
document.getElementById("btn-login-nav").classList.remove("hide");
document.getElementById("btn-register").classList.remove("hide");
......
......@@ -178,6 +178,7 @@ function generateWorkoutForm() {
for (let file of formData.getAll("files")) {
submitForm.append("files", file);
}
return submitForm;
}
......
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
load_module modules/ngx_http_perl_module.so;
env GROUPID;
env PORT_PREFIX;
events {
worker_connections 1024;
}
http {
perl_set $GROUPID 'sub { return $ENV{"GROUPID"}; }';
perl_set $PORT_PREFIX 'sub { return $ENV{"PORT_PREFIX"}; }';
client_max_body_size 100M;
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://10.${GROUPID}.0.5:3000;
proxy_http_version 1.1;
proxy_set_header Host $host:${PORT_PREFIX}${GROUPID};
}
location /api/ {
proxy_pass http://10.${GROUPID}.0.4:8000;
proxy_http_version 1.1;