Skip to content

Commit

Permalink
Merge pull request #58 from msi-se/dev
Browse files Browse the repository at this point in the history
Input Tool V1 and other small changes
  • Loading branch information
johannesbrandenburger authored May 6, 2024
2 parents 16d0620 + afb48a7 commit 29a01b2
Show file tree
Hide file tree
Showing 86 changed files with 22,910 additions and 499 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public ObjectId getId() {
return this.id;
}

public void setId(ObjectId id) {
this.id = id;
}

// name
public String getName() {
return this.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,12 @@ public FeedbackQuestion copy() {
public void setType(FeedbackQuestionType type) {
this.type = type;
}

public void setRangeLow(String rangeLow) {
this.rangeLow = rangeLow;
}

public void setRangeHigh(String rangeHigh) {
this.rangeHigh = rangeHigh;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package de.htwg_konstanz.mobilelearning.repositories;




import de.htwg_konstanz.mobilelearning.models.external.menu.MenuState;
import io.quarkus.mongodb.panache.PanacheMongoRepository;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class MenuStateRepository implements PanacheMongoRepository<MenuState> {

public MenuState getLatestMenuState() {
return findAll().firstResult();
// sort on timestamp descending and return first
return this.listAll(Sort.by("timestamp", Sort.Direction.Descending)).stream().findFirst().orElse(null);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public List<Course> updateCourses(List<ApiCourse> courses) {
}

// validate input
if (courses == null || courses.isEmpty()) {
if (courses == null) {
throw new IllegalArgumentException("Courses must not be empty.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ public MenuState getMenu() {
MenuState menuState = menuStateRepository.getLatestMenuState();

// if it is older than 10 minutes, update it
if (menuState == null || new Date().getTime() - menuState.timestamp.getTime() > 10 * 60 * 1000) {
Integer minutes = 10;
Date currentTimestamp = new Date();
Date menuTimestamp = menuState != null ? menuState.getTimestamp() : new Date(0);
Integer diff = (int) ((currentTimestamp.getTime() - menuTimestamp.getTime()) / 1000);
if (menuState == null || diff > minutes * 60) {
menuState = updateMenu();
}

Expand All @@ -62,11 +66,20 @@ private MenuState updateMenu() {

// parse xml
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// xmlMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
Menu menueFromXML = xmlMapper.readValue(xmlString, Menu.class);
MenuState menuState = new MenuState(new Date(), menueFromXML);

// check if menu is empty -> if so, return the last menu
try {
if (menuState.getMenu().getDays().isEmpty() || menuState.getMenu().getDays().get(0).getItems().isEmpty()) {
return menuStateRepository.getLatestMenuState();
}
} catch (Exception e) {
return menuStateRepository.getLatestMenuState();
}

// save menu to database
MenuState menuState = new MenuState(new Date(), menueFromXML);
menuStateRepository.persist(menuState);

return menuState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestResponse;
import org.jose4j.jwt.consumer.InvalidJwtException;

import de.htwg_konstanz.mobilelearning.enums.FormStatus;
import de.htwg_konstanz.mobilelearning.models.Course;
import de.htwg_konstanz.mobilelearning.models.QuestionWrapper;
import de.htwg_konstanz.mobilelearning.models.auth.User;
import de.htwg_konstanz.mobilelearning.models.auth.UserRole;
import de.htwg_konstanz.mobilelearning.models.feedback.FeedbackForm;
import de.htwg_konstanz.mobilelearning.models.feedback.FeedbackQuestion;
import de.htwg_konstanz.mobilelearning.repositories.CourseRepository;
import de.htwg_konstanz.mobilelearning.repositories.UserRepository;
import de.htwg_konstanz.mobilelearning.services.auth.JwtService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.DefaultValue;
Expand All @@ -34,6 +40,12 @@ public class FeedbackFormService {
@Inject
private JsonWebToken jwt;

@Inject
private JwtService jwtService;

@Inject
private UserRepository userRepository;

/**
* Returns a single feedback form of a course.
*
Expand Down Expand Up @@ -166,14 +178,52 @@ public RestResponse<String> participate(@RestPath String courseId, @RestPath Str
return RestResponse.ok("Successfully added");
}

// @Path("/{formId}/downloadresults")
// @GET
// @Produces(MediaType.APPLICATION_OCTET_STREAM)
// public Response downloadResults(@RestPath String courseId, @RestPath String formId) {
// Course course = courseRepository.findById(new ObjectId(courseId));
// FeedbackForm feedbackForm = course.getFeedbackFormById(new ObjectId(formId));
// return Response.ok(feedbackForm.getResultsAsCsv(course)).header("Content-Disposition", "attachment; filename=results_" + feedbackForm.name + ".csv").build();
// }

@Path("/{formId}/downloadresults")
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@RolesAllowed({ UserRole.STUDENT, UserRole.PROF })
public Response downloadResults(@RestPath String courseId, @RestPath String formId) {
public Response downloadResultsWithToken(@RestPath String courseId, @RestPath String formId, @QueryParam("token") String token) {

User user = userRepository.findByUsername(jwt.getName());
if (user == null) {
try {
user = userRepository.findById(new ObjectId(jwtService.getJwtClaims(token).getSubject()));
} catch (InvalidJwtException e) {
}
}

if (user == null) {
return Response.status(Response.Status.FORBIDDEN).build();
}

Course course = courseRepository.findById(new ObjectId(courseId));

if (!course.isOwner(user.getId())) {
return Response.status(Response.Status.FORBIDDEN).build();
}

FeedbackForm feedbackForm = course.getFeedbackFormById(new ObjectId(formId));
return Response.ok(feedbackForm.getResultsAsCsv(course)).header("Content-Disposition", "attachment; filename=results_" + feedbackForm.name + ".csv").build();
}

@Path("/{formId}/question/{questionId}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({ UserRole.PROF, UserRole.STUDENT })
public Response getQuestion(@RestPath String courseId, @RestPath String formId, @RestPath String questionId) {
Course course = courseRepository.findById(new ObjectId(courseId));
FeedbackForm feedbackForm = course.getFeedbackFormById(new ObjectId(formId));
QuestionWrapper question = feedbackForm.getQuestionById(new ObjectId(questionId));
FeedbackQuestion feedbackQuestion = course.getFeedbackQuestionById(question.getQuestionId());
return Response.ok(feedbackQuestion).build();
}

}
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ services:
networks:
- proxy

# nextjs maintenance frontend (inputtool)
mobile-learning-inputtool:
restart: always
container_name: mobile-learning-inputtool
build:
context: ./inputtool
dockerfile: Dockerfile
labels:
- "traefik.enable=true"
- "traefik.http.routers.input.entrypoints=websecure"
- "traefik.http.routers.input.rule=Host(`${DOMAIN}`) && PathPrefix(`/input`)"
# - "traefik.http.routers.input.middlewares=input-stripprefix"
- "traefik.http.middlewares.input-stripprefix.stripprefix.prefixes=/input"
# - "traefik.http.middlewares.input-stripprefix.stripprefix.forceSlash=false"
- "traefik.docker.network=proxy"
networks:
- proxy


traefik-proxy:
container_name: traefik-proxy
image: traefik:v2.11
Expand Down
2 changes: 1 addition & 1 deletion frontend/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="Connect"
android:label="HTWG-Connect"
android:name="${applicationName}"
android:icon="@mipmap/htwg_icon">
<activity
Expand Down
Binary file modified frontend/assets/animations/rive/animations.riv
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/assets/backgrounds/htwg-pattern.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/assets/logo/HTWG_Connect.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/assets/logo/HTWG_Connect_512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/assets/logo/favicon.ico
Binary file not shown.
4 changes: 2 additions & 2 deletions frontend/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Connect</string>
<string>HTWG-Connect</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Connect</string>
<string>HTWG-Connect</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
Expand Down
58 changes: 58 additions & 0 deletions frontend/lib/components/basicButton.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';

enum ButtonType { primary, secondary, cancel }

class BasicButton extends StatelessWidget {
final ButtonType type;
final String text;
final VoidCallback onPressed;

const BasicButton({
Key? key,
required this.type,
required this.text,
required this.onPressed,
}) : super(key: key);

Color _getButtonColor(ColorScheme colors) {
switch (type) {
case ButtonType.primary:
return colors.primary;
case ButtonType.secondary:
return colors.surfaceTint;
case ButtonType.cancel:
return const Color(0xFFEDF5F3);
default:
return colors.primary;
}
}

Color _getTextColor(ColorScheme colors) {
switch (type) {
case ButtonType.primary:
return Colors.white;
case ButtonType.secondary:
return colors.primary;
case ButtonType.cancel:
return Colors.red;
default:
return Colors.white;
}
}

@override
Widget build(BuildContext context)
{
final colors = Theme.of(context).colorScheme;
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: _getButtonColor(colors),
),
child: Text(
text,
style: TextStyle(color: _getTextColor(colors)),
),
);
}
}
45 changes: 26 additions & 19 deletions frontend/lib/components/elements/course/JoinCourseDialog.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:frontend/components/basicButton.dart';
import 'package:frontend/components/textfield.dart';

class JoinCourseDialog extends StatefulWidget {
final Function(String courseId) onJoinCourse;
Expand All @@ -15,28 +17,33 @@ class _JoinCourseDialogState extends State<JoinCourseDialog> {
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Kurs beitreten'),
content: TextField(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Kurs beitreten'),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
],
),
content: MyTextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Gebe die Kurs-Id ein...',
),
hintText: 'Gebe die Kurs-Id ein...',
obscureText: false,
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Abbruch', style: TextStyle(color: Colors.red)),
),
TextButton(
onPressed: () {
if (_controller.text.isNotEmpty) {
widget.onJoinCourse(_controller.text);
Navigator.of(context).pop();
}
},
child: const Text('Beitreten'),
Center(
child: BasicButton(
type: ButtonType.primary,
text: "Beitreten",
onPressed: () {
if (_controller.text.isNotEmpty) {
widget.onJoinCourse(_controller.text);
Navigator.of(context).pop();
}
},
),
),
],
);
Expand Down
Loading

0 comments on commit 29a01b2

Please sign in to comment.