From 4224330e41c9d388d1564b72589423df2951efca Mon Sep 17 00:00:00 2001 From: Paulsenik Date: Fri, 13 Dec 2024 17:16:36 +0100 Subject: [PATCH 1/8] fix unauthorized metric-access + image-caching --- .../prost/controller/ShopController.java | 24 ++++++-- .../fim/fsinfo/prost/security/WebConfig.java | 3 +- frontend/src/Queries.ts | 57 ++++++++----------- 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/controller/ShopController.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/controller/ShopController.java index 60395a8..98d0641 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/controller/ShopController.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/controller/ShopController.java @@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -65,18 +67,30 @@ public ResponseEntity> get(@RequestParam String id) { } @GetMapping("/item/picture") - public ResponseEntity getDisplayImage(@RequestParam String id) { + public ResponseEntity getDisplayImage(@RequestParam String id, + @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) String ifModifiedSince) { Optional item = itemRepository.findById(id); if (item.isPresent()) { - Optional file = fileStorageService.getItemPicture(item.get()); - if (file.isPresent()) { - FileSystemResource resource = new FileSystemResource(file.get()); + Optional fileO = fileStorageService.getItemPicture(item.get()); + if (fileO.isPresent()) { + File file = fileO.get(); + long lastModified = file.lastModified(); + + // Handle last modified check + if (ifModifiedSince != null && Long.parseLong(ifModifiedSince) >= lastModified) { + return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build(); + } + + FileSystemResource resource = new FileSystemResource(file); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .header(HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + file.get().getName() + "\"") + "attachment; filename=\"" + file.getName() + "\"") + .header(HttpHeaders.CACHE_CONTROL, "max-age=31536000, public, immutable") + .header(HttpHeaders.ETAG, String.valueOf(file.lastModified())) + .header(HttpHeaders.LAST_MODIFIED, String.valueOf(file.lastModified())) .body(resource); } else { return ResponseEntity.noContent().build(); diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java index 5b26d1d..ab1a6ae 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java @@ -33,7 +33,7 @@ public class WebConfig implements WebMvcConfigurer { public static final String[] AUTH_WHITELIST = { "/api/authentication", - "/api/statistics/metric/**", + "/api/shop/item/picture", }; public static final String[] USER_SPACE = { @@ -44,6 +44,7 @@ public class WebConfig implements WebMvcConfigurer { "/api/transaction/me", "/api/invoice/me", "/api/shop/item/**", + "/api/statistics/metric/**", }; public static final String[] KIOSK_SPACE = { diff --git a/frontend/src/Queries.ts b/frontend/src/Queries.ts index 9d67f7a..dd24ffc 100644 --- a/frontend/src/Queries.ts +++ b/frontend/src/Queries.ts @@ -1,12 +1,20 @@ import axios from "axios"; -import { ShopHistoryEntryPage } from "./Types/ShopHistory"; -import { ShopItem } from "./Types/ShopItem"; -import { AuthorizedUser, User } from "./Types/User"; -import { getEncodedCredentials, setAuthorizedUser } from "./SessionInfo"; -import { InvoicePage } from "./Types/Invoice"; -import { TransactionPage } from "./Types/Transaction"; -import { UserMetricType as UserMetricType, UserMetricEntry as UserMetricEntry, TimeSpan, ItemMetricType as ItemMetricType, ItemMetricEntry as ItemMetricEntry, CompositeMetricEntry as CompositeMetricEntry, CompositeMetricType as CompositeMetricType } from "./Types/Statistics"; -import { toast } from "react-toastify"; +import {ShopHistoryEntryPage} from "./Types/ShopHistory"; +import {ShopItem} from "./Types/ShopItem"; +import {AuthorizedUser, User} from "./Types/User"; +import {getEncodedCredentials, setAuthorizedUser} from "./SessionInfo"; +import {InvoicePage} from "./Types/Invoice"; +import {TransactionPage} from "./Types/Transaction"; +import { + CompositeMetricEntry as CompositeMetricEntry, + CompositeMetricType as CompositeMetricType, + ItemMetricEntry as ItemMetricEntry, + ItemMetricType as ItemMetricType, + TimeSpan, + UserMetricEntry as UserMetricEntry, + UserMetricType as UserMetricType +} from "./Types/Statistics"; +import {toast} from "react-toastify"; export const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:8081"; @@ -182,9 +190,9 @@ export async function createTransaction(receiver: User, value: string, actionTyp } export async function getAllTransactions( - size: number, - page: number, - receiverId: string | undefined + size: number, + page: number, + receiverId: string | undefined ): Promise { const params = receiverId ? "&receiverId=" + receiverId : ""; @@ -418,24 +426,7 @@ export async function uploadItemDisplayPicture(item: ShopItem, file: File): Prom } export async function getItemDisplayPicture(item: ShopItem): Promise { - try { - const result = await fetch(apiUrl + `/api/shop/item/picture?id=${item.id}`, { - method: "GET", - headers: { - Authorization: `Basic ${getEncodedCredentials()}`, - "Content-Type": "application/json", - }, - }); - - if (result.ok && result.status === 200) { - const blob = await result.blob(); - return URL.createObjectURL(blob); - } - } catch (error) { - // If there's a network error or any other error, return null - return undefined; - } - return undefined; + return `${apiUrl}/api/shop/item/picture?id=${item.id}`; } export async function getPersonalInvoices(): Promise { @@ -459,12 +450,12 @@ export async function getPersonalInvoices(): Promise { } export async function getAllInvoices( - page: number, - userId: string | undefined, - mailed: boolean | undefined + page: number, + userId: string | undefined, + mailed: boolean | undefined ): Promise { const params = - (userId ? "&userId=" + userId : "") + (mailed === undefined ? "" : "&mailed=" + (mailed ? "true" : "false")); + (userId ? "&userId=" + userId : "") + (mailed === undefined ? "" : "&mailed=" + (mailed ? "true" : "false")); try { const response = await fetch(`${apiUrl}/api/invoice/list?s=20&p=` + page + params, { From 86211de4a0cb182a53a903d7ba82f4ee5d37fee9 Mon Sep 17 00:00:00 2001 From: Paulsenik Date: Wed, 18 Dec 2024 13:11:16 +0100 Subject: [PATCH 2/8] remove hidden users form metrics --- .../ShopItemHistoryRepository.java | 2 ++ .../data/repositories/UserRepository.java | 3 +++ .../fim/fsinfo/prost/service/ShopService.java | 8 ++++--- .../fim/fsinfo/prost/service/UserService.java | 12 +++++++++-- .../statistics/AbstractMetricCollector.java | 9 ++++++++ .../service/statistics/MetricService.java | 21 ++++++++++++++++++- .../HourlyActivityMetricCollector.java | 10 +++++++++ .../ItemPurchaseMetricCollector.java | 12 ++++++++++- .../item/AbstractItemMetricCollector.java | 5 +++++ .../user/AbstractUserMetricCollector.java | 5 +++++ .../fsinfo/prost/service/UserServiceTest.java | 6 +++++- 11 files changed, 85 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/ShopItemHistoryRepository.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/ShopItemHistoryRepository.java index f26b271..b72b9ab 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/ShopItemHistoryRepository.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/ShopItemHistoryRepository.java @@ -11,6 +11,8 @@ public interface ShopItemHistoryRepository extends JpaRepository { + List findByUserId(String userId); + Page findByUserIdEquals(String username, Pageable pageable); List findByUserIdAndTimestampBetween(String userId, Long startTimestamp, diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/UserRepository.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/UserRepository.java index 8718cec..bdc493a 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/UserRepository.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/repositories/UserRepository.java @@ -1,10 +1,13 @@ package de.unipassau.fim.fsinfo.prost.data.repositories; import de.unipassau.fim.fsinfo.prost.data.dao.ProstUser; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository { + List findByHidden(Boolean hidden); + } diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java index af00c87..14398d2 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java @@ -128,9 +128,11 @@ public boolean consume(String itemId, String userId, int amount, String bearerId historyRepository.save(historyEntry); bearerLastBuy.put(bearerId, Instant.now().toEpochMilli()); - AbstractMetricCollector.updateAllEntriesFor(ProstUser.class, user); - AbstractMetricCollector.updateAllEntriesFor(ShopItem.class, item); - AbstractMetricCollector.updateAllEntriesFor(ShopItemHistoryEntry.class, historyEntry); + if (!user.getHidden()) { + AbstractMetricCollector.updateAllEntriesFor(ProstUser.class, user); + AbstractMetricCollector.updateAllEntriesFor(ShopItem.class, item); + AbstractMetricCollector.updateAllEntriesFor(ShopItemHistoryEntry.class, historyEntry); + } return true; } else { System.out.println("[SS] :: No Transaction found!"); diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java index f31a9d5..827d39a 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java @@ -4,6 +4,7 @@ import de.unipassau.fim.fsinfo.prost.data.dao.ProstUser; import de.unipassau.fim.fsinfo.prost.data.repositories.UserRepository; import de.unipassau.fim.fsinfo.prost.service.statistics.AbstractMetricCollector; +import de.unipassau.fim.fsinfo.prost.service.statistics.MetricService; import java.math.BigDecimal; import java.util.List; import java.util.Optional; @@ -15,10 +16,12 @@ public class UserService { private final UserRepository users; + private final MetricService metricService; @Autowired - public UserService(UserRepository users) { + public UserService(UserRepository users, MetricService metricService) { this.users = users; + this.metricService = metricService; } @Transactional @@ -85,7 +88,6 @@ public boolean delete(String id) { if (user.isPresent()) { users.delete(user.get()); - AbstractMetricCollector.removeAllEntriesFor(ProstUser.class, user.get()); return true; } return false; @@ -140,6 +142,12 @@ public boolean setHidden(String id, boolean value) { ProstUser u = user.get(); u.setHidden(value); users.save(u); + + if (value) { + metricService.removeFromMetrics(u); + } else { + metricService.addToMetrics(u); + } return true; } return false; diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/AbstractMetricCollector.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/AbstractMetricCollector.java index aa2dfac..0ccb4e0 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/AbstractMetricCollector.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/AbstractMetricCollector.java @@ -90,8 +90,17 @@ protected abstract BigDecimal calculateValue(T entity, TimeSpan timeSpan, Long s protected abstract T findByKey(String key); + /** + * @param entity + * @return true if entity should be ignored in the metrics. + */ + protected abstract boolean filterOut(T entity); + protected void updateEntry(T entity) { long now = Instant.now().toEpochMilli(); + if (filterOut(entity)) { + return; + } metricEntries_Weekly.put(getKey(entity), calculateValue(entity, TimeSpan.WEEK, now - WEEK_MILLIS, now)); metricEntries_Monthly.put(getKey(entity), diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/MetricService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/MetricService.java index 884ce19..769f19c 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/MetricService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/MetricService.java @@ -8,6 +8,7 @@ import de.unipassau.fim.fsinfo.prost.data.repositories.UserRepository; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.springframework.beans.factory.annotation.Autowired; @@ -45,7 +46,7 @@ public long resetMetric() { "[MS] :: Resetting Metrics :: started at " + dateTimeFormatter.format( LocalDateTime.now())); long currentTime = System.currentTimeMillis(); - AbstractMetricCollector.initAllCollectors(ProstUser.class, userRepository.findAll()); + AbstractMetricCollector.initAllCollectors(ProstUser.class, userRepository.findByHidden(false)); AbstractMetricCollector.initAllCollectors(ShopItem.class, shopItemRepository.findAll()); AbstractMetricCollector.initAllCollectors(ShopItemHistoryEntry.class, shopItemHistoryRepository.findAll()); @@ -54,4 +55,22 @@ public long resetMetric() { return dur; } + public void removeFromMetrics(ProstUser user) { + AbstractMetricCollector.removeAllEntriesFor(ProstUser.class, user); + + List entry = shopItemHistoryRepository.findByUserId(user.getId()); + for (ShopItemHistoryEntry e : entry) { + AbstractMetricCollector.removeAllEntriesFor(ShopItemHistoryEntry.class, e); + } + } + + public void addToMetrics(ProstUser user) { + AbstractMetricCollector.updateAllEntriesFor(ProstUser.class, user); + + List entry = shopItemHistoryRepository.findByUserId(user.getId()); + for (ShopItemHistoryEntry e : entry) { + AbstractMetricCollector.updateAllEntriesFor(ShopItemHistoryEntry.class, e); + } + } + } diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/HourlyActivityMetricCollector.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/HourlyActivityMetricCollector.java index 18c55b6..0279a7e 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/HourlyActivityMetricCollector.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/HourlyActivityMetricCollector.java @@ -64,6 +64,16 @@ public BigDecimal calculateValue(ShopItemHistoryEntry entity, TimeSpan timeSpan, return valueO.orElse(BigDecimal.ZERO); } + @Override + protected boolean filterOut(ShopItemHistoryEntry entity) { + Optional userO = userRepository.findById(entity.getUserId()); + if (userO.isPresent()) { + ProstUser user = userO.get(); + return user.getHidden(); + } + return false; + } + @Override public String[] getKeys(ShopItemHistoryEntry entity) { ZoneOffset offset = zone.getRules().getOffset(Instant.now()); diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/ItemPurchaseMetricCollector.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/ItemPurchaseMetricCollector.java index a3db9f2..c7d6ebe 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/ItemPurchaseMetricCollector.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/composite/ItemPurchaseMetricCollector.java @@ -30,9 +30,9 @@ public ItemPurchaseMetricCollector(ShopItemHistoryRepository shopItemHistoryRepo ShopItemRepository shopItemRepository, UserRepository userRepository) { super(ShopItemHistoryEntry.class); this.shopItemHistoryRepository = shopItemHistoryRepository; - initMetrics(shopItemHistoryRepository.findAll()); this.shopItemRepository = shopItemRepository; this.userRepository = userRepository; + initMetrics(shopItemHistoryRepository.findAll()); } @Override @@ -49,6 +49,16 @@ public BigDecimal calculateValue(ShopItemHistoryEntry entity, TimeSpan timeSpan, return valueO.orElse(BigDecimal.ZERO); } + @Override + protected boolean filterOut(ShopItemHistoryEntry entity) { + Optional userO = userRepository.findById(entity.getUserId()); + if (userO.isPresent()) { + ProstUser user = userO.get(); + return user.getHidden(); + } + return false; + } + @Override public String[] getKeys(ShopItemHistoryEntry entity) { LocalDateTime entityTime = Instant.ofEpochMilli(entity.getTimestamp()).atZone(ZoneOffset.UTC) diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/item/AbstractItemMetricCollector.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/item/AbstractItemMetricCollector.java index 3ad191a..0bb8917 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/item/AbstractItemMetricCollector.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/item/AbstractItemMetricCollector.java @@ -43,4 +43,9 @@ public ShopItem findByKey(String key) { Optional result = shopItemRepository.findById(key); return result.orElse(null); } + + @Override + protected boolean filterOut(ShopItem entity) { + return false; + } } diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/user/AbstractUserMetricCollector.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/user/AbstractUserMetricCollector.java index 48d50f5..b43db0d 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/user/AbstractUserMetricCollector.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/statistics/user/AbstractUserMetricCollector.java @@ -72,4 +72,9 @@ public ProstUser findByKey(String key) { Optional result = userRepository.findById(key); return result.orElse(null); } + + @Override + protected boolean filterOut(ProstUser entity) { + return entity.getHidden(); + } } diff --git a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java index d643c31..cd5e3fc 100644 --- a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java +++ b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java @@ -8,6 +8,7 @@ import de.unipassau.fim.fsinfo.prost.data.dao.ProstUser; import de.unipassau.fim.fsinfo.prost.data.repositories.UserRepository; +import de.unipassau.fim.fsinfo.prost.service.statistics.MetricService; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; @@ -19,6 +20,9 @@ class UserServiceTest { private UserService userService; + @Mock + private MetricService metricService; + @Mock private UserRepository userRepository; @@ -27,7 +31,7 @@ class UserServiceTest { @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); - userService = new UserService(userRepository); + userService = new UserService(userRepository, metricService); prostUser = new ProstUser("testuser", "User One", "user1@test.com", true, false); } From 87c40cd4ff173c7bf522f9cac307cfc001b9a8df Mon Sep 17 00:00:00 2001 From: Paulsenik Date: Tue, 24 Dec 2024 16:20:05 +0100 Subject: [PATCH 3/8] remove history in statistics-tab --- .../StatisticsTab/AllSystemStatistics.tsx | 106 +++++++----------- 1 file changed, 42 insertions(+), 64 deletions(-) diff --git a/frontend/src/Components/StatisticsTab/AllSystemStatistics.tsx b/frontend/src/Components/StatisticsTab/AllSystemStatistics.tsx index 2866ca6..8e5c3d6 100644 --- a/frontend/src/Components/StatisticsTab/AllSystemStatistics.tsx +++ b/frontend/src/Components/StatisticsTab/AllSystemStatistics.tsx @@ -1,23 +1,20 @@ -import { ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport } from "@radix-ui/react-scroll-area"; -import { HistoryEntryDisplay } from "./HistoryEntryDisplay"; -import { useEffect, useState } from "react"; -import { ShopHistoryEntry } from "../../Types/ShopHistory"; -import { User } from "../../Types/User"; -import { ShopItem } from "../../Types/ShopItem"; -import { getAllShopItems, getAllUsers, getHistory, getItemMetric } from "../../Queries"; -import { formatMoney } from "../../Format"; -import { MetricInfo } from "./MetricOverview"; -import { isOnlyUser } from "../../SessionInfo"; -import { CompositeMetricType, ItemMetricType, TimeSpan } from "../../Types/Statistics"; -import { ItemMetricPieChart } from "../Chart/PieChart"; -import { CompositeMetricLineChart } from "../Chart/LineChart"; +import {useEffect, useState} from "react"; +import {ShopHistoryEntry} from "../../Types/ShopHistory"; +import {User} from "../../Types/User"; +import {ShopItem} from "../../Types/ShopItem"; +import {getAllShopItems, getAllUsers, getHistory, getItemMetric} from "../../Queries"; +import {formatMoney} from "../../Format"; +import {MetricInfo} from "./MetricOverview"; +import {CompositeMetricType, ItemMetricType, TimeSpan} from "../../Types/Statistics"; +import {ItemMetricPieChart} from "../Chart/PieChart"; +import {CompositeMetricLineChart} from "../Chart/LineChart"; export function AllSystemStatistics(props: { timeSpan: TimeSpan }) { const [history, setHistory] = useState([]); const [users, setUsers] = useState([]); const [items, setItems] = useState([]); const [totalRevenue, setTotalRevenue] = useState(0); - const { timeSpan } = props; + const {timeSpan} = props; useEffect(reloadShopItems, []); @@ -29,16 +26,16 @@ export function AllSystemStatistics(props: { timeSpan: TimeSpan }) { useEffect(() => { getAllUsers() - .then((userList) => { - if (userList === undefined) { - setUsers([]); - } else { - setUsers(userList); - } - }) - .catch(() => { + .then((userList) => { + if (userList === undefined) { setUsers([]); - }); + } else { + setUsers(userList); + } + }) + .catch(() => { + setUsers([]); + }); }, []); useEffect(() => { @@ -68,46 +65,27 @@ export function AllSystemStatistics(props: { timeSpan: TimeSpan }) { } return ( - <> - {isOnlyUser() ? ( - <> - ) : ( - - -

Kürzliche Käufe

- - - {history.map((item) => ( - - ))} - -
-
- - - - - - -
- )} -
- - - - -
-
- - -
- + <> +
+ + + + +
+
+ + +
+ ); } From fee6a5802442b0a31284d2da26da19d4f0acc6ae Mon Sep 17 00:00:00 2001 From: Paulsenik Date: Sat, 28 Dec 2024 17:08:10 +0100 Subject: [PATCH 4/8] remove anonymous in user-leaderboards --- .../Components/StatisticsTab/Leaderboard.tsx | 316 +++++++++--------- 1 file changed, 161 insertions(+), 155 deletions(-) diff --git a/frontend/src/Components/StatisticsTab/Leaderboard.tsx b/frontend/src/Components/StatisticsTab/Leaderboard.tsx index 83826aa..98f39c1 100644 --- a/frontend/src/Components/StatisticsTab/Leaderboard.tsx +++ b/frontend/src/Components/StatisticsTab/Leaderboard.tsx @@ -1,19 +1,24 @@ -import { Link } from "react-router-dom"; +import {Link} from "react-router-dom"; import { - UserMetricType, - UserMetricEntry, - TimeSpan, - ItemMetricType, - ItemMetricEntry, + CompositeMetricEntry, + CompositeMetricType, convertUsersBalance, convertUsersSpent, - CompositeMetricType, - CompositeMetricEntry, + ItemMetricEntry, + ItemMetricType, + TimeSpan, + UserMetricEntry, + UserMetricType, } from "../../Types/Statistics"; -import { useEffect, useState } from "react"; -import { getAllUsers, getCompositeMetric, getItemMetric, getUserMetric } from "../../Queries"; -import { ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport } from "@radix-ui/react-scroll-area"; -import { formatMoney } from "../../Format"; +import {useEffect, useState} from "react"; +import {getAllUsers, getCompositeMetric, getItemMetric, getUserMetric} from "../../Queries"; +import { + ScrollArea, + ScrollAreaScrollbar, + ScrollAreaThumb, + ScrollAreaViewport +} from "@radix-ui/react-scroll-area"; +import {formatMoney} from "../../Format"; export function CompositeLeaderboard(props: { type: CompositeMetricType; @@ -22,7 +27,7 @@ export function CompositeLeaderboard(props: { desc: string; isMoney: boolean; }) { - const { type, timeSpan, title, desc, isMoney } = props; + const {type, timeSpan, title, desc, isMoney} = props; const [stats, setStats] = useState([]); const [options, setOptions] = useState<[string, string][]>([]); const [selected, setSelected] = useState<[string, string]>(); @@ -45,66 +50,66 @@ export function CompositeLeaderboard(props: { useEffect(() => { const uniqueKeys = Array.from( - new Set(stats.map((entry) => JSON.stringify([entry.key1, entry.key1DisplayName]))) + new Set(stats.map((entry) => JSON.stringify([entry.key1, entry.key1DisplayName]))) ).map((key) => JSON.parse(key)); setOptions(uniqueKeys.sort((a: string, b: string) => a[0].localeCompare(b[0]))); setSelected(uniqueKeys[0]); }, [stats]); return ( - -
-

{title}

- -

{desc}

-
+

{title}

+ +

{desc}

+ - - - + +
+ {stats - .filter((a) => { - return selected && a.key1 == selected[0]; - }) - .map((entry, index) => { - return ( + .filter((a) => { + return selected && a.key1 == selected[0]; + }) + .map((entry, index) => { + return ( - ); - })} - -
-
- - - - - - -
+ ); + })} + + + + + + + + + + ); } @@ -115,59 +120,59 @@ export function ItemLeaderboard(props: { desc: string; isMoney: boolean; }) { - const { type, timeSpan, title, desc, isMoney } = props; + const {type, timeSpan, title, desc, isMoney} = props; const [stats, setStats] = useState([]); useEffect(() => { getItemMetric(type, timeSpan) - .then((l) => { - if (l) { - setStats(l); - } - }) - .catch(() => { - setStats([]); - }); + .then((l) => { + if (l) { + setStats(l); + } + }) + .catch(() => { + setStats([]); + }); }, [timeSpan]); return ( - -
-

{title}

-

{desc}

-
- - - + +
+

{title}

+

{desc}

+
+ +
+ {stats.map((entry, index) => { return ( - + ); })} - -
-
- - - - - - -
+ + + + + + + + + + ); } @@ -178,7 +183,7 @@ export function UserLeaderboard(props: { desc: string; isMoney: boolean; }) { - const { type, timeSpan, title, desc, isMoney } = props; + const {type, timeSpan, title, desc, isMoney} = props; const [stats, setStats] = useState([]); useEffect(() => { @@ -204,43 +209,43 @@ export function UserLeaderboard(props: { }, [timeSpan]); return ( - -
-

{title}

-

{desc}

-
- - - - {stats.map((entry, index) => { + +
+

{title}

+

{desc}

+
+ +
+ + {stats.filter((e) => (!e.entity.hidden)).map((entry, index) => { return ( - + ); })} - -
-
- - - - - - -
+ + + + + + + + + + ); } @@ -253,23 +258,24 @@ export function LeaderboardEntry(props: { isMoney: boolean; anonymize: boolean; }) { - const { entryDisplayName, entryId, entryValue, position, isUser, isMoney, anonymize } = props; + const {entryDisplayName, entryId, entryValue, position, isUser, isMoney, anonymize} = props; return ( - - {position} - - {isUser && anonymize ? ( - <>Anonyme 🍍 - ) : isUser ? ( - - {entryDisplayName} - - ) : ( - <>{entryDisplayName} - )} - - {isMoney ? {formatMoney(entryValue)} : {entryValue}} - + + {position} + + {isUser && anonymize ? ( + <>Anonyme 🍍 + ) : isUser ? ( + + {entryDisplayName} + + ) : ( + <>{entryDisplayName} + )} + + {isMoney ? {formatMoney(entryValue)} : + {entryValue}} + ); } From 35e6136585714262a9e5a61b9152d29719f9f98f Mon Sep 17 00:00:00 2001 From: Paulsenik Date: Mon, 13 Jan 2025 12:31:29 +0100 Subject: [PATCH 5/8] specify AG Kaffeekasse as responsible --- frontend/public/data-info.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/public/data-info.html b/frontend/public/data-info.html index ebbbb2b..b7b9286 100644 --- a/frontend/public/data-info.html +++ b/frontend/public/data-info.html @@ -6,7 +6,7 @@

§ 1 Verantwortliche

Inhaber & zuständig für die Verwaltung des Systems ist - Mirjam Deml. Beauftragte für die Erhebung, Verarbeitung und + die AG Kaffeekasse. Beauftragte für die Erhebung, Verarbeitung und Speicherung der Daten ist das Administratoren-Team der FSinfo der Uni Passau.

    @@ -104,7 +104,7 @@

    Kontaktinformationen

    class="external-link" href="mailto:kaffeekasse@fsinfo.fim.uni-passau.de" target="_blank"> - kaffeekasse@fsinfo.fim.uni-passau.de + kaffeekasse@fsinfo.fim.uni-passau.de
  • @@ -116,7 +116,7 @@

    Kontaktinformationen

    class="external-link" href="mailto:admins@fsinfo.fim.uni-passau.de" target="_blank"> - admins@fsinfo.fim.uni-passau.de + admins@fsinfo.fim.uni-passau.de
  • @@ -127,8 +127,8 @@

    Kontaktinformationen

    rel="noopener" class="external-link" href="https://github.com/fsinfopassau/PRoST" target="_blank"> - github.com/fsinfopassau/PRoST - + github.com/fsinfopassau/PRoST +

From a4b78b9e64880fe9511458a39de2686dcb459e7a Mon Sep 17 00:00:00 2001 From: Paulsenik Date: Mon, 1 Dec 2025 13:17:21 +0100 Subject: [PATCH 6/8] update mail-greeting --- .../java/de/unipassau/fim/fsinfo/prost/service/MailService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java index 57d6a1c..ecfff21 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java @@ -76,7 +76,7 @@ public boolean sendInvoice(@NonNull InvoiceEntry invoice, List Date: Mon, 13 Jan 2025 12:31:29 +0100 Subject: [PATCH 7/8] specify AG Kaffeekasse as responsible --- frontend/public/data-info.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/public/data-info.html b/frontend/public/data-info.html index ebbbb2b..b7b9286 100644 --- a/frontend/public/data-info.html +++ b/frontend/public/data-info.html @@ -6,7 +6,7 @@

§ 1 Verantwortliche

Inhaber & zuständig für die Verwaltung des Systems ist - Mirjam Deml. Beauftragte für die Erhebung, Verarbeitung und + die AG Kaffeekasse. Beauftragte für die Erhebung, Verarbeitung und Speicherung der Daten ist das Administratoren-Team der FSinfo der Uni Passau.

    @@ -104,7 +104,7 @@

    Kontaktinformationen

    class="external-link" href="mailto:kaffeekasse@fsinfo.fim.uni-passau.de" target="_blank"> - kaffeekasse@fsinfo.fim.uni-passau.de + kaffeekasse@fsinfo.fim.uni-passau.de
  • @@ -116,7 +116,7 @@

    Kontaktinformationen

    class="external-link" href="mailto:admins@fsinfo.fim.uni-passau.de" target="_blank"> - admins@fsinfo.fim.uni-passau.de + admins@fsinfo.fim.uni-passau.de
  • @@ -127,8 +127,8 @@

    Kontaktinformationen

    rel="noopener" class="external-link" href="https://github.com/fsinfopassau/PRoST" target="_blank"> - github.com/fsinfopassau/PRoST - + github.com/fsinfopassau/PRoST +

From 1f295437423ab1e9770c2dc640a0fcd52eab3505 Mon Sep 17 00:00:00 2001 From: Paulsenik Date: Mon, 1 Dec 2025 13:17:21 +0100 Subject: [PATCH 8/8] update mail-greeting --- .../java/de/unipassau/fim/fsinfo/prost/service/MailService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java index 57d6a1c..ecfff21 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java @@ -76,7 +76,7 @@ public boolean sendInvoice(@NonNull InvoiceEntry invoice, List