From fa120fe7b5009cc53dac73cb87e851908abbc5dd Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Thu, 12 Jun 2025 22:23:10 +0200 Subject: [PATCH 01/16] ADD database integration & ADD simple dummy user entity --- bruno/VoD-System/Edit/Edit.bru | 4 +-- docker-compose.yml | 2 +- .../java/com/ddf/vodsystem/entities/User.java | 32 +++++++++++++++++++ .../repositories/UserRepository.java | 9 ++++++ src/main/resources/application.properties | 7 ++-- src/main/resources/db/data.sql | 12 +++++++ src/main/resources/db/schema.sql | 9 ++++++ 7 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ddf/vodsystem/entities/User.java create mode 100644 src/main/java/com/ddf/vodsystem/repositories/UserRepository.java create mode 100644 src/main/resources/db/data.sql create mode 100644 src/main/resources/db/schema.sql diff --git a/bruno/VoD-System/Edit/Edit.bru b/bruno/VoD-System/Edit/Edit.bru index 13bce1f..4836047 100644 --- a/bruno/VoD-System/Edit/Edit.bru +++ b/bruno/VoD-System/Edit/Edit.bru @@ -11,6 +11,6 @@ post { } body:form-urlencoded { - startPoint: 30 - endPoint: 120 + startPoint: 130 + endPoint: 140 } diff --git a/docker-compose.yml b/docker-compose.yml index 5d62a4a..38dfd15 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: POSTGRES_USER: myuser POSTGRES_PASSWORD: mypassword - POSTGRES_DB: mydb + POSTGRES_DB: vodSystem ports: - "5432:5432" volumes: diff --git a/src/main/java/com/ddf/vodsystem/entities/User.java b/src/main/java/com/ddf/vodsystem/entities/User.java new file mode 100644 index 0000000..46929b1 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/User.java @@ -0,0 +1,32 @@ +package com.ddf.vodsystem.entities; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "users") +@Data +public class User { + @Column(name = "id", nullable = false, unique = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + @Column(name = "google_id", nullable = false, unique = true) + private String googleId; + + @Column(name = "username", nullable = false, unique = true) + private String username; + + @Column(name = "email", nullable = false, unique = true) + private String email; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; +} + diff --git a/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java b/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java new file mode 100644 index 0000000..b907163 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java @@ -0,0 +1,9 @@ +package com.ddf.vodsystem.repositories; + +import com.ddf.vodsystem.entities.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c798028..f3ab51b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,13 +7,16 @@ temp.vod.storage=videos/inputs/ temp.vod.output=videos/outputs/ # Database -spring.datasource.url=jdbc:postgresql://localhost:5432/mydb +spring.datasource.url=jdbc:postgresql://postgres:5432/vodSystem spring.datasource.username=myuser spring.datasource.password=mypassword spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true -spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect +spring.sql.init.mode=always + +spring.sql.init.schema-locations=classpath:db/schema.sql +spring.sql.init.data-locations=classpath:db/data.sql # Logging logging.level.org.springframework.web=DEBUG \ No newline at end of file diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql new file mode 100644 index 0000000..42776a3 --- /dev/null +++ b/src/main/resources/db/data.sql @@ -0,0 +1,12 @@ +INSERT INTO users (id, google_id, username, email, name) +VALUES + (1, 'google-uid-001', 'alice', 'alice@example.com', 'Alice Example'), + (2, 'google-uid-002', 'bob', 'bob@example.com', 'Bob Example'), + (3, 'google-uid-003', 'carol', 'carol@example.com', 'Carol Example'), + (4, 'google-uid-004', 'wizard42', 'gandalf@middle.earth', 'Gandalf the Grey'), + (5, 'google-uid-005', 'catnap', 'whiskers@meowmail.com', 'Sir Whiskers McFluff'), + (6, 'google-uid-006', 'robotron', 'bender@futurama.tv', 'Bender Rodriguez'), + (7, 'google-uid-007', 'unicorn', 'sparkle@rainbow.com', 'Princess Sparklehoof'), + (8, 'google-uid-008', 'pirate', 'blackbeard@seas.com', 'Edward Teach'), + (9, 'google-uid-009', 'detective', 'holmes@bakerstreet.uk', 'Sherlock Holmes'), + (10, 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown'); \ No newline at end of file diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql new file mode 100644 index 0000000..8afe3a0 --- /dev/null +++ b/src/main/resources/db/schema.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS users; +CREATE TABLE IF NOT EXISTS users ( + id BIGINT PRIMARY KEY, + google_id VARCHAR(64), + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file From 09865520597bfc02daaabdf9affbb1191c9df049 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Fri, 13 Jun 2025 10:35:52 +0200 Subject: [PATCH 02/16] ADD Clip Table --- .../java/com/ddf/vodsystem/entities/Clip.java | 49 +++++++++++++++++++ .../java/com/ddf/vodsystem/entities/User.java | 5 +- .../repositories/ClipRepository.java | 9 ++++ src/main/resources/db/data.sql | 15 +++++- src/main/resources/db/schema.sql | 19 +++++++ 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/ddf/vodsystem/entities/Clip.java create mode 100644 src/main/java/com/ddf/vodsystem/repositories/ClipRepository.java diff --git a/src/main/java/com/ddf/vodsystem/entities/Clip.java b/src/main/java/com/ddf/vodsystem/entities/Clip.java new file mode 100644 index 0000000..f58d6ea --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/Clip.java @@ -0,0 +1,49 @@ +package com.ddf.vodsystem.entities; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "clips") +@Data +public class Clip { + @Id + @Column(name = "id", nullable = false, unique = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @ToString.Exclude + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "title", nullable = false, length = 255) + private String title; + + @Column(name = "description") + private String description; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "width", nullable = false) + private Integer width; + + @Column(name = "height", nullable = false) + private Integer height; + + @Column(name = "fps", nullable = false) + private Integer fps; + + @Column(name = "duration", nullable = false) + private Integer duration; + + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "video_path", nullable = false, length = 255) + private String videoPath; +} diff --git a/src/main/java/com/ddf/vodsystem/entities/User.java b/src/main/java/com/ddf/vodsystem/entities/User.java index 46929b1..2ea785c 100644 --- a/src/main/java/com/ddf/vodsystem/entities/User.java +++ b/src/main/java/com/ddf/vodsystem/entities/User.java @@ -9,9 +9,9 @@ import java.time.LocalDateTime; @Table(name = "users") @Data public class User { + @Id @Column(name = "id", nullable = false, unique = true) @GeneratedValue(strategy = GenerationType.IDENTITY) - @Id private Long id; @Column(name = "google_id", nullable = false, unique = true) @@ -26,6 +26,9 @@ public class User { @Column(name = "name", nullable = false) private String name; + @Column(name = "role", nullable = false) + private Integer role; // 0: user, 1: admin + @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; } diff --git a/src/main/java/com/ddf/vodsystem/repositories/ClipRepository.java b/src/main/java/com/ddf/vodsystem/repositories/ClipRepository.java new file mode 100644 index 0000000..5c804ff --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/repositories/ClipRepository.java @@ -0,0 +1,9 @@ +package com.ddf.vodsystem.repositories; + +import com.ddf.vodsystem.entities.Clip; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ClipRepository extends JpaRepository { +} diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql index 42776a3..86a5171 100644 --- a/src/main/resources/db/data.sql +++ b/src/main/resources/db/data.sql @@ -9,4 +9,17 @@ VALUES (7, 'google-uid-007', 'unicorn', 'sparkle@rainbow.com', 'Princess Sparklehoof'), (8, 'google-uid-008', 'pirate', 'blackbeard@seas.com', 'Edward Teach'), (9, 'google-uid-009', 'detective', 'holmes@bakerstreet.uk', 'Sherlock Holmes'), - (10, 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown'); \ No newline at end of file + (10, 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown'); + +INSERT INTO clips (id, user_id, title, description, width, height, fps, duration, file_size, video_path) +VALUES + (1, 4, 'Fireworks Over Hobbiton', 'A magical display of fireworks by Gandalf.', 1920, 1080, 30, 120, 104857600, '/videos/fireworks_hobbiton.mp4'), + (2, 5, 'Catnap Chronicles', 'Sir Whiskers McFluff naps in 12 different positions.', 1280, 720, 24, 60, 52428800, '/videos/catnap_chronicles.mp4'), + (3, 6, 'Bite My Shiny Metal...', 'Bender shows off his new upgrades.', 1920, 1080, 60, 45, 73400320, '/videos/bender_upgrades.mp4'), + (4, 7, 'Rainbow Dash', 'Princess Sparklehoof gallops across a double rainbow.', 1920, 1080, 30, 90, 67108864, '/videos/rainbow_dash.mp4'), + (5, 8, 'Pirate Karaoke Night', 'Blackbeard sings sea shanties with his crew.', 1280, 720, 25, 180, 157286400, '/videos/pirate_karaoke.mp4'), + (6, 9, 'The Case of the Missing Sandwich', 'Sherlock Holmes investigates a lunchtime mystery.', 1920, 1080, 30, 75, 50331648, '/videos/missing_sandwich.mp4'), + (7, 10, '88 Miles Per Hour', 'Doc Brown demonstrates time travel with style.', 1920, 1080, 60, 30, 41943040, '/videos/88mph.mp4'), + (8, 1, 'Alice in Videoland', 'Alice explores a surreal digital wonderland.', 1280, 720, 30, 150, 94371840, '/videos/alice_videoland.mp4'), + (9, 2, 'Bob''s Building Bonanza', 'Bob constructs a house out of cheese.', 1920, 1080, 24, 200, 209715200, '/videos/bob_cheesehouse.mp4'), + (10, 3, 'Carol''s Coding Catastrophe', 'Carol debugs a spaghetti codebase.', 1280, 720, 30, 100, 73400320, '/videos/carol_coding.mp4'); \ No newline at end of file diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 8afe3a0..4e8bcda 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -1,9 +1,28 @@ +DROP TABLE IF EXISTS clips; DROP TABLE IF EXISTS users; + CREATE TABLE IF NOT EXISTS users ( id BIGINT PRIMARY KEY, google_id VARCHAR(64), username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, + role INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + + +CREATE TABLE IF NOT EXISTS clips ( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL, + title VARCHAR(255) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + width INTEGER NOT NULL, + height INTEGER NOT NULL, + fps INTEGER NOT NULL, + duration INTEGER NOT NULL, + file_size BIGINT NOT NULL, + video_path VARCHAR(255) NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); \ No newline at end of file From fcd722f33be2a366f5ddfc2171f1033220eb0a21 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Fri, 13 Jun 2025 13:45:09 +0200 Subject: [PATCH 03/16] UPDATE .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 8055497..a83d689 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ build/ videos/ generated + +### Security ### +.env.local +.env.prod From 8d7f74e189a3990c065ac5e32b1b7481146c830f Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Fri, 13 Jun 2025 15:45:12 +0200 Subject: [PATCH 04/16] ADD SecurityConfig & ADD Auth Endpoints --- .gitignore | 8 ++-- pom.xml | 8 ++++ .../vodsystem/controllers/AuthController.java | 25 +++++++++++ .../repositories/UserRepository.java | 5 +++ .../security/CustomOAuth2UserService.java | 44 +++++++++++++++++++ .../vodsystem/security/SecurityConfig.java | 38 ++++++++++++++++ .../resources/application-local.properties | 16 +++++++ src/main/resources/application.properties | 13 +----- 8 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/ddf/vodsystem/controllers/AuthController.java create mode 100644 src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java create mode 100644 src/main/java/com/ddf/vodsystem/security/SecurityConfig.java create mode 100644 src/main/resources/application-local.properties diff --git a/.gitignore b/.gitignore index a83d689..a449874 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +### Security ### +.env.local +.env.prod + node_modules HELP.md target/ @@ -37,7 +41,3 @@ build/ videos/ generated - -### Security ### -.env.local -.env.prod diff --git a/pom.xml b/pom.xml index af35711..d722517 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,14 @@ runtime true + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-security + diff --git a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java new file mode 100644 index 0000000..107c7d4 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java @@ -0,0 +1,25 @@ +package com.ddf.vodsystem.controllers; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/auth/") +public class AuthController { + + @GetMapping("/user") + public Map user(@AuthenticationPrincipal OAuth2User principal) { + return Collections.singletonMap("name", principal.getAttribute("name")); + } + + @GetMapping("/login") + public String login() { + return "login"; + } +} diff --git a/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java b/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java index b907163..50262d6 100644 --- a/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java +++ b/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java @@ -2,8 +2,13 @@ package com.ddf.vodsystem.repositories; import com.ddf.vodsystem.entities.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { + @Query("SELECT u FROM User u WHERE u.googleId = ?1") + Optional findByGoogleId(String googleId); } diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java new file mode 100644 index 0000000..0423922 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java @@ -0,0 +1,44 @@ +package com.ddf.vodsystem.security; + +import com.ddf.vodsystem.entities.User; +import com.ddf.vodsystem.repositories.UserRepository; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Service +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final UserRepository userRepository; + + public CustomOAuth2UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) { + OAuth2User oauthUser = super.loadUser(userRequest); + + String googleId = oauthUser.getAttribute("sub"); // Google's unique user ID + String email = oauthUser.getAttribute("email"); + String name = oauthUser.getAttribute("name"); + + Optional userOptional = userRepository.findByGoogleId(googleId); + User user; + if (userOptional.isEmpty()) { + user = new User(); + user.setGoogleId(googleId); + user.setEmail(email); + user.setName(name); + user.setUsername(email.split("@")[0]); + user.setCreatedAt(LocalDateTime.now()); + userRepository.save(user); + } + + return oauthUser; + } +} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java new file mode 100644 index 0000000..93d3d8c --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java @@ -0,0 +1,38 @@ +package com.ddf.vodsystem.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + private final CustomOAuth2UserService customOAuth2UserService; + + public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) { + this.customOAuth2UserService = customOAuth2UserService; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/", "/css/**", "api/v1/**").permitAll() + .anyRequest().authenticated() + ) + .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService)) + ) + .logout(logout -> logout + .logoutSuccessUrl("/") + .permitAll() + ); + + return http.build(); + } +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..7aa05ee --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,16 @@ +# Database +spring.datasource.url=jdbc:postgresql://postgres:5432/vodSystem +spring.datasource.username=myuser +spring.datasource.password=mypassword +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:db/schema.sql +spring.sql.init.data-locations=classpath:db/data.sql + +# Security +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} +spring.security.oauth2.client.registration.google.scope=openid,profile,email +spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:8080/login/oauth2/code/google \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3ab51b..773728b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,5 @@ spring.application.name=vodSystem +spring.profiles.active=local # VODs spring.servlet.multipart.max-file-size=2GB @@ -6,17 +7,5 @@ spring.servlet.multipart.max-request-size=2GB temp.vod.storage=videos/inputs/ temp.vod.output=videos/outputs/ -# Database -spring.datasource.url=jdbc:postgresql://postgres:5432/vodSystem -spring.datasource.username=myuser -spring.datasource.password=mypassword -spring.datasource.driver-class-name=org.postgresql.Driver -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true -spring.sql.init.mode=always - -spring.sql.init.schema-locations=classpath:db/schema.sql -spring.sql.init.data-locations=classpath:db/data.sql - # Logging logging.level.org.springframework.web=DEBUG \ No newline at end of file From c7b3f6bf70afe64c9afc231102a7038730a6e045 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Mon, 23 Jun 2025 23:28:29 +0200 Subject: [PATCH 05/16] REFINE OAuth2 user handling and update database schema --- frontend/src/components/Topbar.tsx | 12 ++++-- .../vodsystem/controllers/AuthController.java | 3 +- .../java/com/ddf/vodsystem/entities/User.java | 2 +- .../security/CustomOAuth2UserService.java | 42 ++++++++----------- .../vodsystem/security/SecurityConfig.java | 21 ++++++---- .../resources/application-local.properties | 1 - src/main/resources/db/data.sql | 22 +++++----- src/main/resources/db/schema.sql | 7 ++-- 8 files changed, 56 insertions(+), 54 deletions(-) diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index 46a5c6c..8567637 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -1,19 +1,23 @@ -import { Menu, X} from 'lucide-react'; +import { Menu, X } from 'lucide-react'; import MenuButton from "./buttons/MenuButton.tsx"; import clsx from "clsx"; type props = { - sidebarToggled: boolean, - setSidebarToggled: Function + sidebarToggled: boolean; + setSidebarToggled: Function; className?: string; } const Topbar = ({sidebarToggled, setSidebarToggled, className}: props) => { return ( -
+
setSidebarToggled(!sidebarToggled)}> {sidebarToggled ? : } + + + Login +
) } diff --git a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java index 107c7d4..311d506 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java @@ -6,7 +6,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.Collections; import java.util.Map; @RestController @@ -15,7 +14,7 @@ public class AuthController { @GetMapping("/user") public Map user(@AuthenticationPrincipal OAuth2User principal) { - return Collections.singletonMap("name", principal.getAttribute("name")); + return principal.getAttributes(); } @GetMapping("/login") diff --git a/src/main/java/com/ddf/vodsystem/entities/User.java b/src/main/java/com/ddf/vodsystem/entities/User.java index 2ea785c..20e47a2 100644 --- a/src/main/java/com/ddf/vodsystem/entities/User.java +++ b/src/main/java/com/ddf/vodsystem/entities/User.java @@ -29,7 +29,7 @@ public class User { @Column(name = "role", nullable = false) private Integer role; // 0: user, 1: admin - @Column(name = "created_at", nullable = false) + @Column(name = "created_at") private LocalDateTime createdAt; } diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java index 0423922..0189ac4 100644 --- a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java @@ -1,17 +1,15 @@ package com.ddf.vodsystem.security; - -import com.ddf.vodsystem.entities.User; import com.ddf.vodsystem.repositories.UserRepository; + import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.Optional; @Service -public class CustomOAuth2UserService extends DefaultOAuth2UserService { +public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; @@ -20,25 +18,21 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { } @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) { - OAuth2User oauthUser = super.loadUser(userRequest); + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); - String googleId = oauthUser.getAttribute("sub"); // Google's unique user ID - String email = oauthUser.getAttribute("email"); - String name = oauthUser.getAttribute("name"); +// String email = oAuth2User.getAttribute("email"); +// String name = oAuth2User.getAttribute("name"); +// String googleId = oAuth2User.getAttribute("sub"); +// +// userRepository.findByGoogleId(googleId).orElseGet(() -> { +// User user = new User(); +// user.setEmail(email); +// user.setName(name); +// user.setGoogleId(googleId); +// return userRepository.save(user); +// }); - Optional userOptional = userRepository.findByGoogleId(googleId); - User user; - if (userOptional.isEmpty()) { - user = new User(); - user.setGoogleId(googleId); - user.setEmail(email); - user.setName(name); - user.setUsername(email.split("@")[0]); - user.setCreatedAt(LocalDateTime.now()); - userRepository.save(user); - } - - return oauthUser; + return oAuth2User; } -} \ No newline at end of file +} diff --git a/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java index 93d3d8c..584ba53 100644 --- a/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java +++ b/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java @@ -3,10 +3,14 @@ package com.ddf.vodsystem.security; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @Configuration +@EnableWebSecurity public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; @@ -20,19 +24,22 @@ public class SecurityConfig { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/css/**", "api/v1/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 - .loginPage("/login") .userInfoEndpoint(userInfo -> userInfo - .userService(customOAuth2UserService)) - ) - .logout(logout -> logout - .logoutSuccessUrl("/") - .permitAll() + .userService(customOAuth2UserService) + + ) + .successHandler(successHandler()) ); return http.build(); } + + @Bean + public AuthenticationSuccessHandler successHandler() { + return new SimpleUrlAuthenticationSuccessHandler("/api/v1/auth/user"); + } + } diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 7aa05ee..83b93c7 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -13,4 +13,3 @@ spring.sql.init.data-locations=classpath:db/data.sql spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} spring.security.oauth2.client.registration.google.scope=openid,profile,email -spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:8080/login/oauth2/code/google \ No newline at end of file diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql index 86a5171..ddb0c87 100644 --- a/src/main/resources/db/data.sql +++ b/src/main/resources/db/data.sql @@ -1,15 +1,15 @@ -INSERT INTO users (id, google_id, username, email, name) +INSERT INTO users ( google_id, username, email, name) VALUES - (1, 'google-uid-001', 'alice', 'alice@example.com', 'Alice Example'), - (2, 'google-uid-002', 'bob', 'bob@example.com', 'Bob Example'), - (3, 'google-uid-003', 'carol', 'carol@example.com', 'Carol Example'), - (4, 'google-uid-004', 'wizard42', 'gandalf@middle.earth', 'Gandalf the Grey'), - (5, 'google-uid-005', 'catnap', 'whiskers@meowmail.com', 'Sir Whiskers McFluff'), - (6, 'google-uid-006', 'robotron', 'bender@futurama.tv', 'Bender Rodriguez'), - (7, 'google-uid-007', 'unicorn', 'sparkle@rainbow.com', 'Princess Sparklehoof'), - (8, 'google-uid-008', 'pirate', 'blackbeard@seas.com', 'Edward Teach'), - (9, 'google-uid-009', 'detective', 'holmes@bakerstreet.uk', 'Sherlock Holmes'), - (10, 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown'); + ('google-uid-001', 'alice', 'alice@example.com', 'Alice Example'), + ( 'google-uid-002', 'bob', 'bob@example.com', 'Bob Example'), + ('google-uid-003', 'carol', 'carol@example.com', 'Carol Example'), + ( 'google-uid-004', 'wizard42', 'gandalf@middle.earth', 'Gandalf the Grey'), + ( 'google-uid-005', 'catnap', 'whiskers@meowmail.com', 'Sir Whiskers McFluff'), + ( 'google-uid-006', 'robotron', 'bender@futurama.tv', 'Bender Rodriguez'), + ('google-uid-007', 'unicorn', 'sparkle@rainbow.com', 'Princess Sparklehoof'), + ( 'google-uid-008', 'pirate', 'blackbeard@seas.com', 'Edward Teach'), + ( 'google-uid-009', 'detective', 'holmes@bakerstreet.uk', 'Sherlock Holmes'), + ( 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown'); INSERT INTO clips (id, user_id, title, description, width, height, fps, duration, file_size, video_path) VALUES diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 4e8bcda..bb261e5 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -2,18 +2,17 @@ DROP TABLE IF EXISTS clips; DROP TABLE IF EXISTS users; CREATE TABLE IF NOT EXISTS users ( - id BIGINT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, google_id VARCHAR(64), username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, role INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - + ); CREATE TABLE IF NOT EXISTS clips ( - id BIGINT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, description TEXT, From df7c2a15df80dbcdcae34b8c0f20c52e44c05554 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 00:11:01 +0200 Subject: [PATCH 06/16] UPDATE OAuth2 user service to save additional user details --- .../security/CustomOAuth2UserService.java | 25 +++++++++++-------- .../vodsystem/security/SecurityConfig.java | 2 +- .../resources/application-local.properties | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java index 0189ac4..0fa7b13 100644 --- a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java @@ -1,4 +1,5 @@ package com.ddf.vodsystem.security; +import com.ddf.vodsystem.entities.User; import com.ddf.vodsystem.repositories.UserRepository; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -21,17 +22,19 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); -// String email = oAuth2User.getAttribute("email"); -// String name = oAuth2User.getAttribute("name"); -// String googleId = oAuth2User.getAttribute("sub"); -// -// userRepository.findByGoogleId(googleId).orElseGet(() -> { -// User user = new User(); -// user.setEmail(email); -// user.setName(name); -// user.setGoogleId(googleId); -// return userRepository.save(user); -// }); + String email = oAuth2User.getAttribute("email"); + String name = oAuth2User.getAttribute("name"); + String googleId = oAuth2User.getAttribute("sub"); + + userRepository.findByGoogleId(googleId).orElseGet(() -> { + User user = new User(); + user.setEmail(email); + user.setName(name); + user.setGoogleId(googleId); + user.setUsername(email); + user.setRole(0); + return userRepository.save(user); + }); return oAuth2User; } diff --git a/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java index 584ba53..469db63 100644 --- a/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java +++ b/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java @@ -29,9 +29,9 @@ public class SecurityConfig { .oauth2Login(oauth2 -> oauth2 .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) - ) .successHandler(successHandler()) + ); return http.build(); diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 83b93c7..8a5dd26 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -12,4 +12,4 @@ spring.sql.init.data-locations=classpath:db/data.sql # Security spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} -spring.security.oauth2.client.registration.google.scope=openid,profile,email +spring.security.oauth2.client.registration.google.scope=profile,email From 0237ec57db99eabc5e73a534d639c950a4cda357 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 14:21:59 +0200 Subject: [PATCH 07/16] UPDATE OAuth2 user service to handle user creation and redirect after login --- frontend/package-lock.json | 48 +++++++++++++++++++ frontend/package.json | 2 + frontend/src/components/Topbar.tsx | 7 ++- frontend/vite.config.ts | 8 ++++ .../CustomOAuth2UserService.java | 14 ++++-- .../SecurityConfig.java | 10 ++-- .../vodsystem/controllers/AuthController.java | 5 -- .../resources/application-local.properties | 3 ++ 8 files changed, 84 insertions(+), 13 deletions(-) rename src/main/java/com/ddf/vodsystem/{security => configuration}/CustomOAuth2UserService.java (80%) rename src/main/java/com/ddf/vodsystem/{security => configuration}/SecurityConfig.java (81%) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 84131bb..4bb2441 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "@tailwindcss/vite": "^4.1.7", "clsx": "^2.1.1", + "dotenv": "^16.5.0", "lucide-react": "^0.511.0", + "path": "^0.12.7", "react": "^19.1.0", "react-dom": "^19.1.0", "react-range-slider-input": "^3.2.1", @@ -2242,6 +2244,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.159", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.159.tgz", @@ -2738,6 +2752,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3332,6 +3352,16 @@ "node": ">=6" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3409,6 +3439,15 @@ "node": ">= 0.8.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3892,6 +3931,15 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index d1a89d6..d62b39a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,9 @@ "dependencies": { "@tailwindcss/vite": "^4.1.7", "clsx": "^2.1.1", + "dotenv": "^16.5.0", "lucide-react": "^0.511.0", + "path": "^0.12.7", "react": "^19.1.0", "react-dom": "^19.1.0", "react-range-slider-input": "^3.2.1", diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index 8567637..9d1c19e 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -9,13 +9,18 @@ type props = { } const Topbar = ({sidebarToggled, setSidebarToggled, className}: props) => { + const apiUrl = import.meta.env.VITE_API_URL; + const redirectUri = encodeURIComponent(window.location.href); + const loginUrl = `${apiUrl}/oauth2/authorization/google?redirect_uri=${redirectUri}`; + return (
setSidebarToggled(!sidebarToggled)}> {sidebarToggled ? : } - + globalThis.location.href = loginUrl}> Login
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index e75ff99..c311e63 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -5,7 +5,15 @@ import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss()], + preview: { + port: 5173, + strictPort: true, + }, server: { + port: 5173, + strictPort: true, + host: true, + origin: "http://0.0.0.0:5173", proxy: { '/api/v1': { target: 'http://localhost:8080', diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java b/src/main/java/com/ddf/vodsystem/configuration/CustomOAuth2UserService.java similarity index 80% rename from src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java rename to src/main/java/com/ddf/vodsystem/configuration/CustomOAuth2UserService.java index 0fa7b13..1c788d9 100644 --- a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java +++ b/src/main/java/com/ddf/vodsystem/configuration/CustomOAuth2UserService.java @@ -1,4 +1,4 @@ -package com.ddf.vodsystem.security; +package com.ddf.vodsystem.configuration; import com.ddf.vodsystem.entities.User; import com.ddf.vodsystem.repositories.UserRepository; @@ -8,6 +8,9 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.Optional; + @Service public class CustomOAuth2UserService extends DefaultOAuth2UserService { @@ -26,15 +29,18 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { String name = oAuth2User.getAttribute("name"); String googleId = oAuth2User.getAttribute("sub"); - userRepository.findByGoogleId(googleId).orElseGet(() -> { + Optional existingUser = userRepository.findByGoogleId(googleId); + + if (existingUser.isEmpty()) { User user = new User(); user.setEmail(email); user.setName(name); user.setGoogleId(googleId); user.setUsername(email); user.setRole(0); - return userRepository.save(user); - }); + user.setCreatedAt(LocalDateTime.now()); + userRepository.save(user); + } return oAuth2User; } diff --git a/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java similarity index 81% rename from src/main/java/com/ddf/vodsystem/security/SecurityConfig.java rename to src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java index 469db63..6e3bd10 100644 --- a/src/main/java/com/ddf/vodsystem/security/SecurityConfig.java +++ b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java @@ -1,5 +1,6 @@ -package com.ddf.vodsystem.security; +package com.ddf.vodsystem.configuration; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -7,7 +8,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @Configuration @EnableWebSecurity @@ -15,6 +15,9 @@ public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; + @Value("${frontend.url}") + private String frontendUrl; + public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) { this.customOAuth2UserService = customOAuth2UserService; } @@ -24,6 +27,7 @@ public class SecurityConfig { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/v1/auth/login", "/api/v1/auth/user").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 @@ -39,7 +43,7 @@ public class SecurityConfig { @Bean public AuthenticationSuccessHandler successHandler() { - return new SimpleUrlAuthenticationSuccessHandler("/api/v1/auth/user"); + return (request, response, authentication) -> response.sendRedirect(frontendUrl); } } diff --git a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java index 311d506..56a4e00 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java @@ -16,9 +16,4 @@ public class AuthController { public Map user(@AuthenticationPrincipal OAuth2User principal) { return principal.getAttributes(); } - - @GetMapping("/login") - public String login() { - return "login"; - } } diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 8a5dd26..99edc4e 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -13,3 +13,6 @@ spring.sql.init.data-locations=classpath:db/data.sql spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} spring.security.oauth2.client.registration.google.scope=profile,email + +# Frontend +frontend.url=http://localhost:5173 From 8ab0472ac7493b6c93a70c5ee565c3bf0ee8456d Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 20:00:05 +0200 Subject: [PATCH 08/16] UPDATE AuthController to return user details with APIResponse; ADD user fetching logic in MainLayout and Topbar --- frontend/src/components/Topbar.tsx | 11 +++++---- frontend/src/layouts/MainLayout.tsx | 21 +++++++++++++++-- frontend/src/utils/endpoints.ts | 15 +++++++++++- frontend/src/utils/types.ts | 8 ++++++- .../configuration/SecurityConfig.java | 3 ++- .../vodsystem/controllers/AuthController.java | 23 ++++++++++++++++--- .../CustomOAuth2UserService.java | 2 +- 7 files changed, 69 insertions(+), 14 deletions(-) rename src/main/java/com/ddf/vodsystem/{configuration => security}/CustomOAuth2UserService.java (97%) diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index 9d1c19e..13700d8 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -1,17 +1,18 @@ import { Menu, X } from 'lucide-react'; import MenuButton from "./buttons/MenuButton.tsx"; import clsx from "clsx"; +import type {User} from "../utils/types.ts"; type props = { sidebarToggled: boolean; setSidebarToggled: Function; + user: User | null; className?: string; } -const Topbar = ({sidebarToggled, setSidebarToggled, className}: props) => { +const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => { const apiUrl = import.meta.env.VITE_API_URL; - const redirectUri = encodeURIComponent(window.location.href); - const loginUrl = `${apiUrl}/oauth2/authorization/google?redirect_uri=${redirectUri}`; + const loginUrl = `${apiUrl}/oauth2/authorization/google`; return (
@@ -19,9 +20,9 @@ const Topbar = ({sidebarToggled, setSidebarToggled, className}: props) => { {sidebarToggled ? : } - globalThis.location.href = loginUrl}> - Login + { user ? user.name : "Login" }
) diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 1490916..aca598e 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -2,10 +2,26 @@ import Sidebar from '../components/Sidebar' import Topbar from '../components/Topbar' import { Outlet } from 'react-router-dom'; -import {useState} from "react"; +import {useEffect, useState} from "react"; +import type {User} from "../utils/types"; +import { getUser } from "../utils/endpoints"; const MainLayout = () => { const [sidebarToggled, setSidebarToggled] = useState(false); + const [user, setUser] = useState(null); + + useEffect(() => { + const fetchUser = async () => { + try { + const userData = await getUser(); + setUser(userData); + } catch (error) { + console.error('Failed to fetch user:', error); + } + }; + + fetchUser(); + }, []); return (
@@ -14,7 +30,8 @@ const MainLayout = () => { + setSidebarToggled={setSidebarToggled} + user={user}/>
diff --git a/frontend/src/utils/endpoints.ts b/frontend/src/utils/endpoints.ts index a916dfc..a5b20fe 100644 --- a/frontend/src/utils/endpoints.ts +++ b/frontend/src/utils/endpoints.ts @@ -1,4 +1,4 @@ -import type {VideoMetadata, APIResponse} from "./types.ts"; +import type {VideoMetadata, APIResponse, User} from "./types.ts"; /** * Uploads a file to the backend. @@ -129,10 +129,23 @@ const getMetadata = async (uuid: string): Promise => { } }; +const getUser = async (): Promise => { + try { + const response = await fetch('/api/v1/auth/user', {credentials: "include",}); + + const result = await response.json(); + return result.data; + } catch (error: unknown) { + console.error('Error fetching user:', error); + return null; + } +} + export { uploadFile, editFile, processFile, getProgress, getMetadata, + getUser }; \ No newline at end of file diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index 0572b48..43f80d0 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -13,7 +13,13 @@ type APIResponse = { message: string } +type User = { + name: string, + email: string +} + export type { APIResponse, - VideoMetadata + VideoMetadata, + User } \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java index 6e3bd10..ac05736 100644 --- a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java +++ b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java @@ -1,5 +1,6 @@ package com.ddf.vodsystem.configuration; +import com.ddf.vodsystem.security.CustomOAuth2UserService; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,4 +47,4 @@ public class SecurityConfig { return (request, response, authentication) -> response.sendRedirect(frontendUrl); } -} +} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java index 56a4e00..523182c 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java @@ -1,5 +1,8 @@ package com.ddf.vodsystem.controllers; +import com.ddf.vodsystem.entities.APIResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.GetMapping; @@ -13,7 +16,21 @@ import java.util.Map; public class AuthController { @GetMapping("/user") - public Map user(@AuthenticationPrincipal OAuth2User principal) { - return principal.getAttributes(); + public ResponseEntity>> user(@AuthenticationPrincipal OAuth2User principal) { + if (principal == null) { + return ResponseEntity.status(HttpStatus.FORBIDDEN). + body(new APIResponse<>( + "error", + "User not authenticated", + null + )); + } + + return ResponseEntity.ok( + new APIResponse<>("success", "User details retrieved successfully", Map.of( + "name", principal.getAttribute("name"), + "email", principal.getAttribute("email")) + ) + ); } -} +} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/configuration/CustomOAuth2UserService.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java similarity index 97% rename from src/main/java/com/ddf/vodsystem/configuration/CustomOAuth2UserService.java rename to src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java index 1c788d9..2cf1212 100644 --- a/src/main/java/com/ddf/vodsystem/configuration/CustomOAuth2UserService.java +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java @@ -1,4 +1,4 @@ -package com.ddf.vodsystem.configuration; +package com.ddf.vodsystem.security; import com.ddf.vodsystem.entities.User; import com.ddf.vodsystem.repositories.UserRepository; From 1a5403d5d728d26dcb7acb511eaa2d392ec99f38 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 20:03:43 +0200 Subject: [PATCH 09/16] ADD logout functionality to SecurityConfig --- .../com/ddf/vodsystem/configuration/SecurityConfig.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java index ac05736..9e87c39 100644 --- a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java +++ b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java @@ -35,8 +35,10 @@ public class SecurityConfig { .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) ) - .successHandler(successHandler()) - + .successHandler(successHandler())) + .logout(logout -> logout + .logoutUrl("/api/v1/auth/logout") + .logoutSuccessUrl(frontendUrl) ); return http.build(); From bf071d06f9271b7a48dc43e6c016aae9b687384f Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 20:08:22 +0200 Subject: [PATCH 10/16] UPDATE SecurityConfig to permit access for most endpoints --- .../java/com/ddf/vodsystem/configuration/SecurityConfig.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java index 9e87c39..790512e 100644 --- a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java +++ b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java @@ -29,6 +29,9 @@ public class SecurityConfig { .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/v1/auth/login", "/api/v1/auth/user").permitAll() + .requestMatchers("/api/v1/upload", "/api/v1/download/**").permitAll() + .requestMatchers("/api/v1/edit/**", "/api/v1/process/**", "/api/v1/progress/**").permitAll() + .requestMatchers("/api/v1/metadata/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 From 071f3c420c96f4855d48972ade2d5ab6beeb34cb Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 20:23:56 +0200 Subject: [PATCH 11/16] ADD logout functionality to SecurityConfig and Topbar --- frontend/src/components/Topbar.tsx | 23 +++++++++++++++---- .../configuration/SecurityConfig.java | 10 +++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index 13700d8..4357cbf 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -13,6 +13,7 @@ type props = { const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => { const apiUrl = import.meta.env.VITE_API_URL; const loginUrl = `${apiUrl}/oauth2/authorization/google`; + const logoutUrl = `${apiUrl}/api/v1/auth/logout`; return (
@@ -20,10 +21,24 @@ const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => {sidebarToggled ? : } - globalThis.location.href = loginUrl}> - { user ? user.name : "Login" } - + { user ? ( +
+ + {user.name} + + globalThis.location.href = logoutUrl}> + Logout + +
+ ) : + ( + globalThis.location.href = loginUrl}> + Login + + )}; +
) } diff --git a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java index 790512e..d7d8d04 100644 --- a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java +++ b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java @@ -9,6 +9,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; @Configuration @EnableWebSecurity @@ -41,7 +42,9 @@ public class SecurityConfig { .successHandler(successHandler())) .logout(logout -> logout .logoutUrl("/api/v1/auth/logout") - .logoutSuccessUrl(frontendUrl) + .logoutSuccessHandler(logoutSuccessHandler()) + .invalidateHttpSession(true) + .deleteCookies("JSESSIONID") ); return http.build(); @@ -52,4 +55,9 @@ public class SecurityConfig { return (request, response, authentication) -> response.sendRedirect(frontendUrl); } + @Bean + public LogoutSuccessHandler logoutSuccessHandler() { + return (request, response, authentication) -> response.sendRedirect(frontendUrl); + } + } \ No newline at end of file From 636a1db8c73be678ccb1f0063131562bc5b7d5af Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 20:28:29 +0200 Subject: [PATCH 12/16] PATCH extra item in Flexbox --- frontend/src/components/Topbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index 4357cbf..ffe0cc2 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -37,7 +37,7 @@ const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => onClick={() => globalThis.location.href = loginUrl}> Login - )}; + )}
) From d3dce9ec25f4372b17cce423b235902f4ab90ed5 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 24 Jun 2025 20:37:21 +0200 Subject: [PATCH 13/16] ADD user prop to Sidebar and conditionally render My Clips button --- frontend/src/components/Sidebar.tsx | 16 ++++++++++------ frontend/src/layouts/MainLayout.tsx | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index dcfdb47..4e6e272 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -2,12 +2,14 @@ import clsx from "clsx"; import SidebarButton from "./buttons/SidebarButton.tsx"; import { Plus, Film, Home } from 'lucide-react'; import Box from "./Box.tsx"; +import type {User} from "../utils/types.ts"; type props = { + user: User | null; className?: string } -const Sidebar = ({className}: props) => { +const Sidebar = ({user, className}: props) => { return ( { label={"Create Clip"} /> - } - label={"My Clips"} - /> + { user && ( + } + label={"My Clips"} + /> + )} ); }; diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index aca598e..0c24458 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -26,6 +26,7 @@ const MainLayout = () => { return (
Date: Tue, 24 Jun 2025 22:52:24 +0200 Subject: [PATCH 14/16] ADD Dropdown component for user actions --- frontend/package-lock.json | 422 +++++++++++++++++- frontend/package.json | 4 +- frontend/src/components/Dropdown.tsx | 65 +++ frontend/src/components/Topbar.tsx | 24 +- frontend/src/index.css | 2 + frontend/src/utils/types.ts | 3 +- frontend/vite.config.ts | 5 +- .../vodsystem/controllers/AuthController.java | 14 +- 8 files changed, 503 insertions(+), 36 deletions(-) create mode 100644 frontend/src/components/Dropdown.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4bb2441..2a1c6b0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,10 +7,12 @@ "": { "name": "frontend", "version": "0.0.0", + "hasInstallScript": true, "dependencies": { "@tailwindcss/vite": "^4.1.7", "clsx": "^2.1.1", "dotenv": "^16.5.0", + "flowbite-react": "^0.11.8", "lucide-react": "^0.511.0", "path": "^0.12.7", "react": "^19.1.0", @@ -882,6 +884,68 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", + "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", + "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.3.tgz", + "integrity": "sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.9", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.3.tgz", + "integrity": "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -948,6 +1012,12 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "license": "ISC" + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -1012,7 +1082,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1026,7 +1095,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -1036,7 +1104,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -2004,11 +2071,28 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { @@ -2026,7 +2110,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2116,6 +2199,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2125,6 +2223,12 @@ "node": ">=18" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2154,6 +2258,22 @@ "dev": true, "license": "MIT" }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2188,6 +2308,12 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2210,11 +2336,22 @@ "dev": true, "license": "MIT" }, + "node_modules/debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2235,6 +2372,15 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge-ts": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.4.tgz", + "integrity": "sha512-fxqo6nHGQ9zOVgI4KXqtWXJR/yCLtC7aXIVq+6jc8tHPFUxlFmuUcm2kC4vztQ+LJxQ3gER/XAWearGYQ8niGA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2444,7 +2590,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2471,6 +2616,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -2528,7 +2686,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2545,7 +2702,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -2572,7 +2728,6 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -2595,7 +2750,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -2642,6 +2796,128 @@ "dev": true, "license": "ISC" }, + "node_modules/flowbite-react": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.11.8.tgz", + "integrity": "sha512-ntaPUGaVqnN/dk0ivdc2UAhoCMVCyMhWj6uT9mkUueXxeSL3Q+/4opcLXMsuIrJdRrPJQIQI8ptd3AoGsxnQMg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "1.6.9", + "@floating-ui/react": "0.27.3", + "@iarna/toml": "2.2.5", + "@typescript-eslint/typescript-estree": "8.26.0", + "chokidar": "4.0.3", + "classnames": "2.5.1", + "comment-json": "4.2.5", + "debounce": "2.2.0", + "deepmerge-ts": "7.1.4", + "klona": "2.0.6", + "package-manager-detector": "0.2.9", + "recast": "0.23.11", + "tailwind-merge-v2": "npm:tailwind-merge@2.6.0", + "tailwind-merge-v3": "npm:tailwind-merge@3.0.1" + }, + "bin": { + "flowbite-react": "dist/cli/bin.js" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "tailwindcss": "^3 || ^4" + } + }, + "node_modules/flowbite-react/node_modules/@typescript-eslint/types": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/flowbite-react/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/flowbite-react/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/flowbite-react/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/flowbite-react/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flowbite-react/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2715,6 +2991,15 @@ "node": ">=8" } }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2762,7 +3047,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2772,7 +3056,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -2785,7 +3068,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -2884,6 +3166,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3181,7 +3472,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -3191,7 +3481,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -3254,7 +3543,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3339,6 +3627,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.9.tgz", + "integrity": "sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3392,7 +3686,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -3462,7 +3755,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -3567,6 +3859,44 @@ "react-dom": ">=18" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3581,7 +3911,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -3631,7 +3960,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -3696,6 +4024,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3731,6 +4068,34 @@ "node": ">=8" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tailwind-merge-v2": { + "name": "tailwind-merge", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-merge-v3": { + "name": "tailwind-merge", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz", + "integrity": "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.7.tgz", @@ -3772,6 +4137,12 @@ "node": ">=18" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -3818,7 +4189,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -3831,7 +4201,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -3840,6 +4209,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3857,7 +4232,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/frontend/package.json b/frontend/package.json index d62b39a..d265c35 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,12 +8,14 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "postinstall": "flowbite-react patch" }, "dependencies": { "@tailwindcss/vite": "^4.1.7", "clsx": "^2.1.1", "dotenv": "^16.5.0", + "flowbite-react": "^0.11.8", "lucide-react": "^0.511.0", "path": "^0.12.7", "react": "^19.1.0", diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx new file mode 100644 index 0000000..5101c5b --- /dev/null +++ b/frontend/src/components/Dropdown.tsx @@ -0,0 +1,65 @@ +import React, {useEffect, useRef} from "react"; +import clsx from "clsx"; + +type DropdownItemProps = { + item: string; + onClick: (item: string) => void; + className?: string; +} + +const DropdownItem = ({ item, onClick, className }: DropdownItemProps) => { + return ( +
  • onClick(item)}> + {item} +
  • + ); +} + +type DropdownProps = { + label: string; + children: React.ReactNode; + className?: string; +} + +const Dropdown = ({ label, children, className }: DropdownProps) => { + const [isOpen, setIsOpen] = React.useState(false); + const ref = useRef(null); + + const toggleDropdown = () => { + setIsOpen(!isOpen); + }; + + useEffect(() => { + function handleClickOutside(event: { target: any; }) { + if (ref.current && !ref.current.contains(event.target)) { + setIsOpen(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
    + + {isOpen && ( +
      + {children} +
    + )} +
    + ); +}; + + + +export { Dropdown, DropdownItem }; \ No newline at end of file diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index ffe0cc2..7b460e6 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -2,6 +2,7 @@ import { Menu, X } from 'lucide-react'; import MenuButton from "./buttons/MenuButton.tsx"; import clsx from "clsx"; import type {User} from "../utils/types.ts"; +import { Dropdown, DropdownItem } from "./Dropdown.tsx"; type props = { sidebarToggled: boolean; @@ -23,13 +24,22 @@ const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => { user ? (
    - - {user.name} - - globalThis.location.href = logoutUrl}> - Logout - + + + + globalThis.location.href = logoutUrl} className="text-red-600" /> + + + {/**/} + {/* */} + {/* Logout*/} + {/* */} + {/**/} +
    ) : ( diff --git a/frontend/src/index.css b/frontend/src/index.css index 90c61ce..fc20e05 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@plugin "flowbite-react/plugin/tailwindcss"; +@source "../.flowbite-react/class-list.json"; @theme { diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index 43f80d0..ffb6931 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -15,7 +15,8 @@ type APIResponse = { type User = { name: string, - email: string + email: string, + profilePicture: string } export type { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c311e63..7559c01 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,10 +1,11 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' +import flowbiteReact from "flowbite-react/plugin/vite"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react(), tailwindcss()], + plugins: [react(), tailwindcss(), flowbiteReact()], preview: { port: 5173, strictPort: true, @@ -22,4 +23,4 @@ export default defineConfig({ } } } -}) +}) \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java index 523182c..de455e0 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java @@ -26,10 +26,22 @@ public class AuthController { )); } + if (principal.getAttribute("email") == null + || principal.getAttribute("name") == null + || principal.getAttribute("picture") == null) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST). + body(new APIResponse<>( + "error", + "Required user attributes are missing", + null + )); + } + return ResponseEntity.ok( new APIResponse<>("success", "User details retrieved successfully", Map.of( "name", principal.getAttribute("name"), - "email", principal.getAttribute("email")) + "email", principal.getAttribute("email"), + "profilePicture", principal.getAttribute("picture")) ) ); } From 7c64649c22ef1060944e8d083206b02b905f5bf2 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 25 Jun 2025 16:44:37 +0200 Subject: [PATCH 15/16] UPDATE Dropdown and Topbar components for improved styling and functionality --- frontend/src/components/Dropdown.tsx | 4 ++-- frontend/src/components/Topbar.tsx | 11 ++--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx index 5101c5b..7cbfdb2 100644 --- a/frontend/src/components/Dropdown.tsx +++ b/frontend/src/components/Dropdown.tsx @@ -47,12 +47,12 @@ const Dropdown = ({ label, children, className }: DropdownProps) => { ref={ref}> {isOpen && ( -
      +
        {children}
      )} diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index 7b460e6..c4aa2d2 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -25,21 +25,14 @@ const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => { user ? (
      - globalThis.location.href = logoutUrl} className="text-red-600" /> + globalThis.location.href = logoutUrl} className={"text-red-500 font-medium"} /> - - {/**/} - {/* */} - {/* Logout*/} - {/* */} - {/**/} -
      ) : ( From 9c2fb9b7105a9c03445fe12f955d3f69aa6f41ae Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 25 Jun 2025 17:04:18 +0200 Subject: [PATCH 16/16] REFACTOR for consistent syntax --- frontend/src/components/Dropdown.tsx | 4 ++-- frontend/src/components/Selector.tsx | 2 +- frontend/src/components/Topbar.tsx | 4 +++- frontend/src/layouts/MainLayout.tsx | 2 +- frontend/src/pages/ClipEdit.tsx | 2 +- frontend/src/pages/Home.tsx | 16 ++++++++-------- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx index 7cbfdb2..a818fa1 100644 --- a/frontend/src/components/Dropdown.tsx +++ b/frontend/src/components/Dropdown.tsx @@ -47,12 +47,12 @@ const Dropdown = ({ label, children, className }: DropdownProps) => { ref={ref}> {isOpen && ( -
        +
          {children}
        )} diff --git a/frontend/src/components/Selector.tsx b/frontend/src/components/Selector.tsx index afb575f..7792c7a 100644 --- a/frontend/src/components/Selector.tsx +++ b/frontend/src/components/Selector.tsx @@ -12,7 +12,7 @@ const Selector = ({children, label}: props) => { className={"w-full"}> { label } -
        +
        {children}
        ) diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index c4aa2d2..3fdfbdb 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -31,7 +31,9 @@ const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => /> - globalThis.location.href = logoutUrl} className={"text-red-500 font-medium"} /> + globalThis.location.href = logoutUrl} + className={"text-red-500 font-medium"} />
        ) : diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 0c24458..db8d02d 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -29,7 +29,7 @@ const MainLayout = () => { user={user} className={`row-span-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${sidebarToggled ? "-translate-x-full" : "translate-x-0"}`}/> diff --git a/frontend/src/pages/ClipEdit.tsx b/frontend/src/pages/ClipEdit.tsx index e43959f..db93065 100644 --- a/frontend/src/pages/ClipEdit.tsx +++ b/frontend/src/pages/ClipEdit.tsx @@ -129,7 +129,7 @@ const ClipEdit = () => { /> {error && ( -
        +
        {error}
        )} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index a2bdad0..85e3807 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,39 +1,39 @@ const Home = () => { return ( -
        +
        {/* Logo */} VoD System Logo {/* Description Container */} -
        -

        +
        +

        What is the VoD System?

        -

        +

        The VoD System is a powerful clip management platform designed to streamline how you handle your video content. Whether you're a content creator, streamer, or educator, VoD System lets you:

        -
          +
          • Upload clips effortlessly and securely.
          • Edit and trim videos with intuitive controls.
          • Compress files to specific file sizes.
          • Organize your clips for quick access and sharing.
          -

          +

          Designed with simplicity and efficiency in mind, VoD System adapts to your workflow, making video clip management faster and more enjoyable than ever.

        {/* File Support Note */} -
        +
        Note: Currently, only .mp4 files are supported for upload and processing. Support for additional video formats will be added in future updates.