Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 154 additions & 2 deletions api/openapi.yaml

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions services/py-genai-helper/generated/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# generated by datamodel-codegen:
# filename: openapi.yaml
# timestamp: 2026-06-12T12:58:15+00:00
# timestamp: 2026-06-18T08:03:34+00:00

from __future__ import annotations
from pydantic import AwareDatetime, BaseModel, Field
from pydantic import AwareDatetime, BaseModel, Field, SecretStr
from datetime import date
from uuid import UUID
from typing import Annotated
Expand Down Expand Up @@ -99,6 +99,7 @@ class MemberCreate(BaseModel):
first_name: str
last_name: str
email: str
password: SecretStr
birthday: date | None = None
phone_number: str | None = None
address: str | None = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ default Optional<NativeWebRequest> getRequest() {

/**
* POST /events : Create event
* Creates a new event. - Directors: can create events for their sport. - Trainers: can create events for their team. - Admins: can create any event.
*
* @param eventCreate The request body for creating a new event. (required)
* @return The request was successful, and a new resource was created. (status code 201)
Expand All @@ -63,6 +64,7 @@ default Optional<NativeWebRequest> getRequest() {
@Operation(
operationId = "createEvent",
summary = "Create event",
description = "Creates a new event. - Directors: can create events for their sport. - Trainers: can create events for their team. - Admins: can create any event. ",
tags = { "events" },
responses = {
@ApiResponse(responseCode = "201", description = "The request was successful, and a new resource was created.", content = {
Expand Down Expand Up @@ -139,6 +141,7 @@ default ResponseEntity<Event> createEvent(

/**
* DELETE /events/{event_id} : Delete event
* Deletes a specific event. - Creators: can delete events they created. - Directors: can delete events linked to their sport. - Admins: can delete any event.
*
* @param eventId (required)
* @return The request was successful, but there is no content to return in the response. (status code 204)
Expand All @@ -150,6 +153,7 @@ default ResponseEntity<Event> createEvent(
@Operation(
operationId = "deleteEvent",
summary = "Delete event",
description = "Deletes a specific event. - Creators: can delete events they created. - Directors: can delete events linked to their sport. - Admins: can delete any event. ",
tags = { "events" },
responses = {
@ApiResponse(responseCode = "204", description = "The request was successful, but there is no content to return in the response."),
Expand Down Expand Up @@ -210,6 +214,7 @@ default ResponseEntity<Void> deleteEvent(

/**
* GET /events : Get all events
* Returns a list of all events. - All authenticated users: can see events linked to their team or sport, or events where they are an attendee. - Creators: can see all events they created. - Admins: can see all events.
*
* @return The request was successful, and the server has returned the requested resource in the response body. (status code 200)
* or Authentication is required to access the requested resource. The client must include the appropriate credentials. (status code 401)
Expand All @@ -219,6 +224,7 @@ default ResponseEntity<Void> deleteEvent(
@Operation(
operationId = "getAllEvents",
summary = "Get all events",
description = "Returns a list of all events. - All authenticated users: can see events linked to their team or sport, or events where they are an attendee. - Creators: can see all events they created. - Admins: can see all events. ",
tags = { "events" },
responses = {
@ApiResponse(responseCode = "200", description = "The request was successful, and the server has returned the requested resource in the response body.", content = {
Expand Down Expand Up @@ -278,6 +284,7 @@ default ResponseEntity<List<EventSummary>> getAllEvents(

/**
* GET /events/{event_id} : Get event details
* Returns the details of a specific event. - All authenticated users: can access events linked to their team or sport, or events where they are an attendee. - Creators: can view events they created. - Admins: can view any event.
*
* @param eventId (required)
* @return The request was successful, and the server has returned the requested resource in the response body. (status code 200)
Expand All @@ -289,6 +296,7 @@ default ResponseEntity<List<EventSummary>> getAllEvents(
@Operation(
operationId = "getEventDetails",
summary = "Get event details",
description = "Returns the details of a specific event. - All authenticated users: can access events linked to their team or sport, or events where they are an attendee. - Creators: can view events they created. - Admins: can view any event. ",
tags = { "events" },
responses = {
@ApiResponse(responseCode = "200", description = "The request was successful, and the server has returned the requested resource in the response body.", content = {
Expand Down Expand Up @@ -356,6 +364,7 @@ default ResponseEntity<Event> getEventDetails(

/**
* PATCH /events/{event_id} : Update event details
* Partially updates the details of a specific event. - Creators: can update events they created. - Directors: can update events linked to their sport. - Admins: can update any event.
*
* @param eventId (required)
* @param eventPartialUpdate The request body for partially updating an event. (required)
Expand All @@ -369,6 +378,7 @@ default ResponseEntity<Event> getEventDetails(
@Operation(
operationId = "updateEventDetails",
summary = "Update event details",
description = "Partially updates the details of a specific event. - Creators: can update events they created. - Directors: can update events linked to their sport. - Admins: can update any event. ",
tags = { "events" },
responses = {
@ApiResponse(responseCode = "200", description = "The request was successful, and the server has returned the requested resource in the response body.", content = {
Expand Down
7 changes: 7 additions & 0 deletions services/spring-feedback/config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
<module name="SuppressionSingleFilter">
<property name="files" value="[\\/]generated[\\/]"/>
</module>

<!-- Spring Data JPA derived query methods use underscores to traverse nested
properties (e.g. findAllById_MemberId) which violates the MethodName pattern. -->
<module name="SuppressionSingleFilter">
<property name="files" value="Repository\.java"/>
<property name="checks" value="MethodName"/>
</module>
<property name="fileExtensions" value="java, properties, xml"/>

<!-- No tabs -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ default Optional<NativeWebRequest> getRequest() {

/**
* POST /feedback : Create feedback
* Creates a new feedback entry for a member. - Trainers: can create feedback for their trainees. - Admins: can create feedback for any member.
*
* @param feedbackCreate The request body for creating new feedback. (required)
* @return The request was successful, and a new resource was created. (status code 201)
Expand All @@ -63,6 +64,7 @@ default Optional<NativeWebRequest> getRequest() {
@Operation(
operationId = "createFeedback",
summary = "Create feedback",
description = "Creates a new feedback entry for a member. - Trainers: can create feedback for their trainees. - Admins: can create feedback for any member. ",
tags = { "feedback" },
responses = {
@ApiResponse(responseCode = "201", description = "The request was successful, and a new resource was created.", content = {
Expand Down Expand Up @@ -139,6 +141,7 @@ default ResponseEntity<Feedback> createFeedback(

/**
* DELETE /feedback/{feedback_id} : Delete feedback
* Deletes a specific feedback entry. - Creators: can delete feedback they submitted. - Admins: can delete any feedback.
*
* @param feedbackId (required)
* @return The request was successful, but there is no content to return in the response. (status code 204)
Expand All @@ -150,6 +153,7 @@ default ResponseEntity<Feedback> createFeedback(
@Operation(
operationId = "deleteFeedback",
summary = "Delete feedback",
description = "Deletes a specific feedback entry. - Creators: can delete feedback they submitted. - Admins: can delete any feedback. ",
tags = { "feedback" },
responses = {
@ApiResponse(responseCode = "204", description = "The request was successful, but there is no content to return in the response."),
Expand Down Expand Up @@ -210,6 +214,7 @@ default ResponseEntity<Void> deleteFeedback(

/**
* GET /feedback : Get all feedback
* Returns a list of all feedback entries. - Creators: can see feedback they submitted. - Members: can see feedback about themselves. - Admins: can see all feedback.
*
* @return The request was successful, and the server has returned the requested resource in the response body. (status code 200)
* or Authentication is required to access the requested resource. The client must include the appropriate credentials. (status code 401)
Expand All @@ -219,6 +224,7 @@ default ResponseEntity<Void> deleteFeedback(
@Operation(
operationId = "getAllFeedback",
summary = "Get all feedback",
description = "Returns a list of all feedback entries. - Creators: can see feedback they submitted. - Members: can see feedback about themselves. - Admins: can see all feedback. ",
tags = { "feedback" },
responses = {
@ApiResponse(responseCode = "200", description = "The request was successful, and the server has returned the requested resource in the response body.", content = {
Expand Down Expand Up @@ -278,6 +284,7 @@ default ResponseEntity<List<FeedbackSummary>> getAllFeedback(

/**
* GET /feedback/{feedback_id} : Get feedback details
* Returns the details of a specific feedback entry. - Creators: can view feedback they submitted. - Members: can view feedback about themselves. - Admins: can view any feedback.
*
* @param feedbackId (required)
* @return The request was successful, and the server has returned the requested resource in the response body. (status code 200)
Expand All @@ -289,6 +296,7 @@ default ResponseEntity<List<FeedbackSummary>> getAllFeedback(
@Operation(
operationId = "getFeedbackDetails",
summary = "Get feedback details",
description = "Returns the details of a specific feedback entry. - Creators: can view feedback they submitted. - Members: can view feedback about themselves. - Admins: can view any feedback. ",
tags = { "feedback" },
responses = {
@ApiResponse(responseCode = "200", description = "The request was successful, and the server has returned the requested resource in the response body.", content = {
Expand Down Expand Up @@ -356,6 +364,7 @@ default ResponseEntity<Feedback> getFeedbackDetails(

/**
* PATCH /feedback/{feedback_id} : Update feedback details
* Partially updates a specific feedback entry. - Creators: can update feedback they submitted. - Admins: can update any feedback.
*
* @param feedbackId (required)
* @param feedbackPartialUpdate The request body for partially updating a specific feedback. (required)
Expand All @@ -369,6 +378,7 @@ default ResponseEntity<Feedback> getFeedbackDetails(
@Operation(
operationId = "updateFeedbackDetails",
summary = "Update feedback details",
description = "Partially updates a specific feedback entry. - Creators: can update feedback they submitted. - Admins: can update any feedback. ",
tags = { "feedback" },
responses = {
@ApiResponse(responseCode = "200", description = "The request was successful, and the server has returned the requested resource in the response body.", content = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package tum.devoops.feedbackservice.config;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
Expand All @@ -10,14 +15,9 @@
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableMethodSecurity(proxyTargetClass = true)
public class SecurityConfig {

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package tum.devoops.feedbackservice.controller;

import java.util.List;
import java.util.UUID;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.RestController;

import tum.devoops.feedbackservice.api.FeedbackApi;
import tum.devoops.feedbackservice.model.Feedback;
import tum.devoops.feedbackservice.model.FeedbackCreate;
import tum.devoops.feedbackservice.model.FeedbackPartialUpdate;
import tum.devoops.feedbackservice.model.FeedbackSummary;
import tum.devoops.feedbackservice.service.FeedbackService;

@RestController
@PreAuthorize("hasAnyRole('admin', 'member')")
public class FeedbackController implements FeedbackApi {

private final FeedbackService feedbackService;

public FeedbackController(FeedbackService feedbackService) {
this.feedbackService = feedbackService;
}

@Override
public ResponseEntity<List<FeedbackSummary>> getAllFeedback() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UUID requesterId = extractRequesterId(auth);
boolean isAdmin = extractIsAdmin(auth);
return ResponseEntity.ok(feedbackService.getAllFeedback(requesterId, isAdmin));
}

@Override
public ResponseEntity<Feedback> createFeedback(FeedbackCreate feedbackCreate) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UUID requesterId = extractRequesterId(auth);
boolean isAdmin = extractIsAdmin(auth);
Feedback created = feedbackService.createFeedback(feedbackCreate, requesterId, isAdmin);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

@Override
public ResponseEntity<Feedback> getFeedbackDetails(UUID feedbackId) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UUID requesterId = extractRequesterId(auth);
boolean isAdmin = extractIsAdmin(auth);
return ResponseEntity.ok(feedbackService.getFeedbackDetails(feedbackId, requesterId, isAdmin));
}

@Override
public ResponseEntity<Feedback> updateFeedbackDetails(UUID feedbackId, FeedbackPartialUpdate feedbackPartialUpdate) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UUID requesterId = extractRequesterId(auth);
boolean isAdmin = extractIsAdmin(auth);
return ResponseEntity.ok(feedbackService.updateFeedbackDetails(feedbackId, feedbackPartialUpdate, requesterId, isAdmin));
}

@Override
public ResponseEntity<Void> deleteFeedback(UUID feedbackId) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UUID requesterId = extractRequesterId(auth);
boolean isAdmin = extractIsAdmin(auth);
feedbackService.deleteFeedback(feedbackId, requesterId, isAdmin);
return ResponseEntity.noContent().build();
}

private UUID extractRequesterId(Authentication auth) {
Jwt jwt = (Jwt) auth.getPrincipal();
return UUID.fromString(jwt.getSubject());
}

private boolean extractIsAdmin(Authentication auth) {
return auth.getAuthorities().stream()
.anyMatch(a -> "ROLE_admin".equals(a.getAuthority()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package tum.devoops.feedbackservice.entity;

import java.util.UUID;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(schema = "event", name = "events")
@Getter
@NoArgsConstructor
public class EventEntity {

@Id
@Column(name = "id", nullable = false, updatable = false)
private UUID id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package tum.devoops.feedbackservice.entity;

import java.util.UUID;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(schema = "member", name = "members")
@Getter
@NoArgsConstructor
public class MemberEntity {

@Id
@Column(name = "id", nullable = false, updatable = false)
private UUID id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tum.devoops.feedbackservice.entity;

import java.io.Serializable;
import java.util.UUID;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(schema = "organization", name = "trainees")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TraineeEntity {

@EmbeddedId
private Id id;

@Embeddable
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Id implements Serializable {

@Column(name = "team_id", nullable = false)
private UUID teamId;

@Column(name = "member_id", nullable = false)
private UUID memberId;
}
}
Loading
Loading