commit before i break things

This commit is contained in:
2026-06-01 23:40:58 +02:00
parent 73c9bc1f14
commit 4db77055ed
11 changed files with 152 additions and 83 deletions
+3
View File
@@ -30,6 +30,9 @@ backend/.db/
backend/init/* backend/init/*
backend/**/*temp.java backend/**/*temp.java
backend/google-services.json
backend/etf-oglasi-firebase.json
# Java / Eclipse / STS # Java / Eclipse / STS
.classpath .classpath
.project .project
+2
View File
@@ -39,6 +39,8 @@ dependencies {
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.13.0") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.13.0")
implementation("io.jsonwebtoken:jjwt-api:0.13.0") implementation("io.jsonwebtoken:jjwt-api:0.13.0")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.13.0") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.13.0")
implementation 'com.google.firebase:firebase-admin:9.9.0'
} }
generateJava { generateJava {
@@ -0,0 +1,31 @@
package dev.ksan.etfoglasiserver.config;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.util.List;
@Configuration
public class FirebaseConfig {
@Bean
public FirebaseApp firebaseApp() throws IOException {
GoogleCredentials credentials = GoogleCredentials
.fromStream(new ClassPathResource("etf-oglasi-firebase.json").getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/firebase.messaging"));
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
if (FirebaseApp.getApps().isEmpty()) {
return FirebaseApp.initializeApp(options);
}
return FirebaseApp.getInstance();
}
}
@@ -23,13 +23,13 @@ public class SubjectAltController {
return service.getSubjectAlt(subjectId); return service.getSubjectAlt(subjectId);
} }
@PostMapping("/subjectalt") // @PostMapping("/subjectalt")
public void addSubject(@RequestBody SubjectAlt subject) { // public void addSubject(@RequestBody SubjectAlt subject) {
service.addSubjectAlt(subject); // service.addSubjectAlt(subject);
} // }
//
@DeleteMapping("/subjectalt/{subjectId}") // @DeleteMapping("/subjectalt/{subjectId}")
public void deleteSubject(@PathVariable UUID subjectId) { // public void deleteSubject(@PathVariable UUID subjectId) {
service.deleteSubjectAlt(subjectId); // service.deleteSubjectAlt(subjectId);
} // }
} }
@@ -22,18 +22,21 @@ public class SubjectController {
return service.getSubject(subjectId); return service.getSubject(subjectId);
} }
@PostMapping("/subjects") @PostMapping("/subjects")
public void addSubject(@RequestBody Subject subject) { public void addSubject(@RequestBody Subject subject) {
service.addSubject(subject); service.addSubject(subject);
} }
@PutMapping("/subjects") //TODO uncomment this if ever roles are added so only admin should modify this (didnt really feel like doing it rn)
public void updateSubject(@RequestBody Subject subject) { //
service.updateSubject(subject); // @PutMapping("/subjects")
} // public void updateSubject(@RequestBody Subject subject) {
// service.updateSubject(subject);
// }
@DeleteMapping("/subjects/{subjectId}") // @DeleteMapping("/subjects/{subjectId}")
public void deleteSubject(@PathVariable UUID subjectId) { // public void deleteSubject(@PathVariable UUID subjectId) {
service.deleteSubject(subjectId); // service.deleteSubject(subjectId);
} // }
} }
@@ -4,13 +4,13 @@ import dev.ksan.etfoglasiserver.dto.JwtResponseDTO;
import dev.ksan.etfoglasiserver.dto.UserCreationDTO; import dev.ksan.etfoglasiserver.dto.UserCreationDTO;
import dev.ksan.etfoglasiserver.dto.UserDTO; import dev.ksan.etfoglasiserver.dto.UserDTO;
import dev.ksan.etfoglasiserver.dto.UserLoginDTO; import dev.ksan.etfoglasiserver.dto.UserLoginDTO;
import dev.ksan.etfoglasiserver.model.NotificationMethod; import dev.ksan.etfoglasiserver.model.User;
import dev.ksan.etfoglasiserver.service.UserService; import dev.ksan.etfoglasiserver.service.UserService;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@@ -20,45 +20,49 @@ public class UserController {
@Autowired UserService service; @Autowired UserService service;
@GetMapping("users/{userId}") @GetMapping("users/me")
public ResponseEntity<UserDTO> getUserById(@PathVariable UUID userId) { public ResponseEntity<UserDTO> getUserById(Authentication authentication) {
UserDTO user = service.getUserById(userId); String email = authentication.getName();
User user = service.getAccountByEmail(email).orElseThrow(() -> new RuntimeException("User not found"));
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND); return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} }
@PutMapping("/users") @PutMapping("/users/me")
public ResponseEntity<UserDTO> updateUser(@RequestBody UserCreationDTO user) { public ResponseEntity<UserDTO> updateUser(
@RequestBody UserCreationDTO user,
Authentication authentication) {
UserDTO userUpdated = service.updateUser(user); String email = authentication.getName();
if (userUpdated != null) {
return new ResponseEntity<>(userUpdated, HttpStatus.OK); UserDTO userUpdated = service.updateUser(email, user);
}
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return ResponseEntity.ok(userUpdated);
} }
@PutMapping("/users/{userId}/subjects/{subjectId}") @PutMapping("/users/me/subjects/{subjectId}")
public ResponseEntity<UserDTO> addSubject( public ResponseEntity<UserDTO> addSubject(
@PathVariable UUID userId, @PathVariable UUID subjectId,
@PathVariable UUID subjectId Authentication authentication) {
) {
UserDTO updatedUser = service.addSubject(userId, subjectId); String email = authentication.getName();
UserDTO updatedUser = service.addSubject(email, subjectId);
return ResponseEntity.ok(updatedUser); return ResponseEntity.ok(updatedUser);
} }
@DeleteMapping("/users/{userId}/subjects/{subjectId}") @DeleteMapping("/users/me/subjects/{subjectId}")
public ResponseEntity<UserDTO> removeSubject( public ResponseEntity<UserDTO> removeSubject(
@PathVariable UUID userId, @PathVariable UUID subjectId,
@PathVariable UUID subjectId Authentication authentication) {
) {
UserDTO updatedUser = service.removeSubject(userId, subjectId); String email = authentication.getName();
UserDTO updatedUser = service.removeSubject(email, subjectId);
return ResponseEntity.ok(updatedUser); return ResponseEntity.ok(updatedUser);
} }
@@ -78,18 +82,14 @@ public class UserController {
if (newUser != null) return new ResponseEntity<>(newUser, HttpStatus.CREATED); if (newUser != null) return new ResponseEntity<>(newUser, HttpStatus.CREATED);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} }
//
// @DeleteMapping("/users/{userId}")
// public void deleteUser(@PathVariable UUID userId) {
// service.deleteUser(userId);
// }
/* @DeleteMapping("/users/me")
@PostMapping("/users") public ResponseEntity<Void> deleteUser(Authentication authentication) {
public void addUser(@RequestBody UserCreationDTO user) {
NotificationMethod method = NotificationMethod.valueOf(user.getNotification()); String email = authentication.getName();
user.setNotificationMethod(method);
service.addUser(user); service.deleteUser(email);
return ResponseEntity.noContent().build();
} }
*/
} }
@@ -2,6 +2,8 @@ package dev.ksan.etfoglasiserver.dto;
import dev.ksan.etfoglasiserver.model.NotificationMethod; import dev.ksan.etfoglasiserver.model.NotificationMethod;
import dev.ksan.etfoglasiserver.model.Subject; import dev.ksan.etfoglasiserver.model.Subject;
import dev.ksan.etfoglasiserver.model.User;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@@ -17,6 +19,13 @@ public class UserDTO {
this.id = id; this.id = id;
this.email = email; this.email = email;
this.subjectSet = subjectSet; this.subjectSet = subjectSet;
}
public UserDTO(User user) {
this.id = user.getId();
this.email = user.getEmail();
this.subjectSet = user.getSubjectSet();
} }
public UUID getId() { public UUID getId() {
@@ -61,8 +61,8 @@ public class UserService {
} }
} }
public UserDTO updateUser(UserCreationDTO user) { public UserDTO updateUser(String email, UserCreationDTO user) {
Optional<User> existingUserOpt = userRepo.findByEmail(user.getEmail()); Optional<User> existingUserOpt = userRepo.findByEmail(email);
if (userRepo.findByEmail(user.getNewEmail()).isPresent()) { if (userRepo.findByEmail(user.getNewEmail()).isPresent()) {
throw new RuntimeException("Email taken"); throw new RuntimeException("Email taken");
@@ -96,8 +96,12 @@ public class UserService {
} else throw new RuntimeException("User not found"); } else throw new RuntimeException("User not found");
} }
public void deleteUser(UUID userId) { public void deleteUser(String email) {
userRepo.deleteById(userId); User user = userRepo.findByEmail(email).orElseThrow(() -> new RuntimeException("User not found"));
user.getSubjectSet().clear();
userRepo.save(user);
userRepo.delete(user);
} }
public UserDTO register(UserCreationDTO user) { public UserDTO register(UserCreationDTO user) {
@@ -170,9 +174,9 @@ public class UserService {
} }
public UserDTO addSubject(UUID userId, UUID subjectId) { public UserDTO addSubject(String email, UUID subjectId) {
User user = userRepo.findById(userId) User user = userRepo.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found")); .orElseThrow(() -> new RuntimeException("User not found"));
Subject subject = subjectRepo.findById(subjectId) Subject subject = subjectRepo.findById(subjectId)
@@ -185,9 +189,9 @@ public class UserService {
return getUserDTO(user); return getUserDTO(user);
} }
public UserDTO removeSubject(UUID userId, UUID subjectId) { public UserDTO removeSubject(String email, UUID subjectId) {
User user = userRepo.findById(userId) User user = userRepo.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found")); .orElseThrow(() -> new RuntimeException("User not found"));
Subject subject = subjectRepo.findById(subjectId) Subject subject = subjectRepo.findById(subjectId)
+7 -4
View File
@@ -50,7 +50,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
let subscribedSubjectIds: string[] = []; let subscribedSubjectIds: string[] = [];
if (res.userId) { if (res.userId) {
try { try {
const dto = await authApi.getUser(res.userId, res.token); const dto = await authApi.getUser( res.token);
subscribedSubjectIds = dto.subjectSet?.map((s) => s.id) ?? []; subscribedSubjectIds = dto.subjectSet?.map((s) => s.id) ?? [];
} catch { } catch {
// aaaa // aaaa
@@ -67,7 +67,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const register = async (payload: RegisterPayload) => { const register = async (payload: RegisterPayload) => {
await authApi.register(payload); await authApi.register(payload);
await login({ email: payload.email, password: payload.password }); await login({
email: payload.email.trim(),
password: payload.password.trim(),
});
}; };
const updateUser = async (newEmail?: string, newPassword?: string) => { const updateUser = async (newEmail?: string, newPassword?: string) => {
@@ -91,7 +94,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const subscribe = async (subjectId: string) => { const subscribe = async (subjectId: string) => {
if (!user) return; if (!user) return;
const dto = await subscriptionsApi.subscribe(user.id, subjectId, user.token); const dto = await subscriptionsApi.subscribe( subjectId, user.token);
const updated = { const updated = {
...user, ...user,
subscribedSubjectIds: dto.subjectSet?.map((s) => s.id) ?? [...user.subscribedSubjectIds, subjectId], subscribedSubjectIds: dto.subjectSet?.map((s) => s.id) ?? [...user.subscribedSubjectIds, subjectId],
@@ -101,7 +104,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const unsubscribe = async (subjectId: string) => { const unsubscribe = async (subjectId: string) => {
if (!user) return; if (!user) return;
const dto = await subscriptionsApi.unsubscribe(user.id, subjectId, user.token); const dto = await subscriptionsApi.unsubscribe( subjectId, user.token);
const updated = { const updated = {
...user, ...user,
subscribedSubjectIds: dto.subjectSet?.map((s) => s.id) ?? user.subscribedSubjectIds.filter((id) => id !== subjectId), subscribedSubjectIds: dto.subjectSet?.map((s) => s.id) ?? user.subscribedSubjectIds.filter((id) => id !== subjectId),
+9 -3
View File
@@ -25,13 +25,19 @@ export default function AuthGate({ onSuccess }: { onSuccess?: () => void }) {
const accent = dark ? "#E07B45" : "#C4622D"; const accent = dark ? "#E07B45" : "#C4622D";
const submit = async () => { const submit = async () => {
const trimmedEmail = email.trim();
const trimmedPassword = password.trim();
setError(""); setError("");
if (!email || !password) { setError("Please fill in all fields."); return; } if (!trimmedEmail || !trimmedPassword) { setError("Please fill in all fields."); return; }
setLoading(true); setLoading(true);
try { try {
if (mode === "login") await login({ email, password }); if (mode === "login") await login({ email:trimmedEmail, password:trimmedPassword});
else await register({ email, password, name }); else await register({ email: trimmedEmail,password:trimmedPassword , name });
onSuccess?.(); onSuccess?.();
} catch (e: any) { } catch (e: any) {
setError(e.message ?? "Something went wrong."); setError(e.message ?? "Something went wrong.");
+15 -7
View File
@@ -103,8 +103,15 @@ export type RegisterPayload = { email: string; password: string; name?: string }
export const authApi = { export const authApi = {
login: (p: LoginPayload) => post<JwtResponse>("/login", p), login: (p: LoginPayload) => post<JwtResponse>("/login", p),
register: (p: RegisterPayload) => post<UserDTO>("/register", p), register: (p: RegisterPayload) => post<UserDTO>("/register", p),
getUser: (userId: string, token: string) => get<UserDTO>(`/users/${userId}`, token),
updateUser: (body: UserUpdateDTO, token: string) => request<UserDTO>("PUT", "/users", token, body), getUser: (token: string) =>
get<UserDTO>("/users/me", token),
updateUser: (body: UserUpdateDTO, token: string) =>
request<UserDTO>("PUT", "/users/me", token, body),
deleteUser: (token: string) =>
del<void>("/users/me", token),
}; };
@@ -114,19 +121,20 @@ export const subjectsApi = {
}; };
export const subscriptionsApi = { export const subscriptionsApi = {
subscribe: (userId: string, subjectId: string, token: string) => subscribe: (subjectId: string, token: string) =>
put<UserDTO>( put<UserDTO>(
`/users/${userId}/subjects/${subjectId}`, `/users/me/subjects/${subjectId}`,
{}, // body placeholder {},
token token
), ),
unsubscribe: (userId: string, subjectId: string, token: string) => unsubscribe: (subjectId: string, token: string) =>
del<UserDTO>( del<UserDTO>(
`/users/${userId}/subjects/${subjectId}`, `/users/me/subjects/${subjectId}`,
token token
), ),
}; };
export const entriesApi = { export const entriesApi = {
getEntries: (params: { subjectId?: string; groupName?: string; page?: number }) => getEntries: (params: { subjectId?: string; groupName?: string; page?: number }) =>
get<SpringPage<Entry>>("/entries", undefined, params), get<SpringPage<Entry>>("/entries", undefined, params),