diff --git a/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java b/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java index b15c974b06863734fc527fdc1108726c1db20424..5ef1cdcaddb9acbaac7f3b3f83c5f2d060758bc8 100644 --- a/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java +++ b/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java @@ -50,15 +50,18 @@ public class FeedApisController { List<FeedImpl> posts = feedRepository.getAllPosts(); - // set isEditable flag for each post depedant on whether they are an admin or own the post - for (FeedImpl post : posts) { - log.info("Role ID: " + currentUser.getRoleId()); - boolean isSuperAdmin = currentUser != null && currentUser.getRoleId() == 1; - log.info("IsSuperAdmin: " + isSuperAdmin); - boolean isOwner = currentUser != null && post.getUserId() == currentUser.getId(); - log.info("IsPostOwner: " + isOwner); - post.setIsDeletable(isSuperAdmin || isOwner); - log.info("PostIsEditable: " + post.getIsDeletable()); + + if (currentUser != null){ + // set isEditable flag for each post depedant on whether they are an admin or own the post + for (FeedImpl post : posts) { + log.info("Role ID: " + currentUser.getRoleId()); + boolean isSuperAdmin = currentUser != null && currentUser.getRoleId() == 1; + log.info("IsSuperAdmin: " + isSuperAdmin); + boolean isOwner = currentUser != null && post.getUserId() == currentUser.getId(); + log.info("IsPostOwner: " + isOwner); + post.setIsDeletable(isSuperAdmin || isOwner); + log.info("PostIsEditable: " + post.getIsDeletable()); + } } return posts; diff --git a/src/main/java/polish_community_group_11/polish_community/profile/controllers/ProfileController.java b/src/main/java/polish_community_group_11/polish_community/profile/controllers/ProfileController.java new file mode 100644 index 0000000000000000000000000000000000000000..fb82e1ed930c64a1369b15245aadde4468150270 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/profile/controllers/ProfileController.java @@ -0,0 +1,81 @@ +package polish_community_group_11.polish_community.profile.controllers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.ModelAndView; +import polish_community_group_11.polish_community.profile.models.Profile; +import polish_community_group_11.polish_community.profile.services.ProfileService; +import polish_community_group_11.polish_community.register.models.User; +import polish_community_group_11.polish_community.register.services.UserService; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +@Controller +public class ProfileController { + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @Autowired + private UserService userService; + @Autowired + private ProfileService profileService; + + @GetMapping("/profile") + public ModelAndView profile(Authentication authentication) { + + ModelAndView modelAndView = new ModelAndView("profile/profilePage"); + + Profile profile = getProfileForAuthenticatedUser(authentication); + modelAndView.addObject("profile", profile); + + return modelAndView; + } + + @PostMapping("/update") + public String updateProfile(@ModelAttribute Profile profile, Authentication authentication,@RequestParam("newPicture") MultipartFile newPicture) + throws IOException { + String username = authentication.getName(); + User user = userService.findByEmail(username); + profile.setUserId(user.getId()); + if (!newPicture.isEmpty()) { + String fileName = StringUtils.cleanPath(newPicture.getOriginalFilename()); + String uploadDir = "build/resources/main/static/assets/user-photos/" + user.getId(); + Path uploadPath = Paths.get(uploadDir); + + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + try (InputStream inputStream = newPicture.getInputStream()) { + Path filePath = uploadPath.resolve(fileName); + Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); + profile.setProfilePicture("/assets/user-photos/" + user.getId() + "/" + fileName); + } + } + profileService.updateProfile(profile); + return "redirect:/profile"; + } + private Profile getProfileForAuthenticatedUser(Authentication authentication) { + LOG.info("getting profile for {} - isAuthenticated: {}", authentication.getName(), authentication.isAuthenticated()); + String username = authentication.getName(); + + User user = userService.findByEmail(username); + Profile profile = profileService.getProfile(user.getId()); + return profile; + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/profile/controllers/ProfileRestController.java b/src/main/java/polish_community_group_11/polish_community/profile/controllers/ProfileRestController.java new file mode 100644 index 0000000000000000000000000000000000000000..67aef21422016f65845afd4d2a21e4200428f177 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/profile/controllers/ProfileRestController.java @@ -0,0 +1,30 @@ +package polish_community_group_11.polish_community.profile.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import polish_community_group_11.polish_community.profile.models.Profile; +import polish_community_group_11.polish_community.profile.services.ProfileService; +import polish_community_group_11.polish_community.register.models.User; +import polish_community_group_11.polish_community.register.services.UserService; + +@RestController +public class ProfileRestController { + @Autowired + private UserService userService; + @Autowired + private ProfileService profileService; + + @GetMapping("/profile-json") + public Profile getProfile(Authentication authentication) { + return getProfileForAuthenticatedUser(authentication); + } + private Profile getProfileForAuthenticatedUser(Authentication authentication) { + String username = authentication.getName(); + + User user = userService.findByEmail(username); + Profile profile = profileService.getProfile(user.getId()); + return profile; + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/profile/models/Profile.java b/src/main/java/polish_community_group_11/polish_community/profile/models/Profile.java new file mode 100644 index 0000000000000000000000000000000000000000..ae0fa9b7366301cf254290dbd8b4324afffd3716 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/profile/models/Profile.java @@ -0,0 +1,23 @@ +package polish_community_group_11.polish_community.profile.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Profile { + private int userId; + private String fullName; + private String email; + private String profilePicture; + private LocalDate dob; + private String bio; + private String phoneNumber; + private String organisation; + private boolean showPhoneNumber; + private boolean showDob; +} diff --git a/src/main/java/polish_community_group_11/polish_community/profile/repositories/ProfileRepository.java b/src/main/java/polish_community_group_11/polish_community/profile/repositories/ProfileRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..3c1011095364f46e023dcf5cc2202f8c88f6221a --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/profile/repositories/ProfileRepository.java @@ -0,0 +1,13 @@ +package polish_community_group_11.polish_community.profile.repositories; + +import org.springframework.stereotype.Repository; +import polish_community_group_11.polish_community.profile.models.Profile; + +import java.util.List; + +@Repository +public interface ProfileRepository { + Profile getProfile(Integer id); + void addProfile(Profile profile); + void updateProfile(Profile profile); +} diff --git a/src/main/java/polish_community_group_11/polish_community/profile/repositories/ProfileRepositoryImpl.java b/src/main/java/polish_community_group_11/polish_community/profile/repositories/ProfileRepositoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..1c1a562776fb9e69a6d5fd7085101332ad673e2e --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/profile/repositories/ProfileRepositoryImpl.java @@ -0,0 +1,52 @@ +package polish_community_group_11.polish_community.profile.repositories; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import polish_community_group_11.polish_community.feed.models.FeedImpl; +import polish_community_group_11.polish_community.profile.models.Profile; + +@Repository +public class ProfileRepositoryImpl implements ProfileRepository { + private final JdbcTemplate jdbcTemplate; + private final RowMapper<Profile> rowMapper; + + public ProfileRepositoryImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.rowMapper = (rs, rowNum) -> new Profile( + rs.getInt("user_id"), + rs.getString("fullName"), + rs.getString("email"), + rs.getString("profile_picture"), + rs.getDate("dob").toLocalDate(), + rs.getString("bio"), + rs.getString("phone_number"), + rs.getString("organization"), + rs.getBoolean("show_phone_number"), + rs.getBoolean("show_dob") + ); + } + @Override + public void addProfile(Profile profile) { + String sql = "INSERT INTO user_profile (user_id) VALUES (?)"; + jdbcTemplate.update(sql, profile.getUserId()); + + } + @Override + public Profile getProfile(Integer id) { + String sql = "SELECT u.id as user_id, " + + "u.fullname, u.email, u.dob, u.organization, " + + "up.profile_picture, up.phone_number, up.bio, up.show_phone_number, up.show_dob " + + "FROM users u LEFT JOIN user_profile up ON " + + "u.id = up.user_id WHERE u.id = ?"; + return jdbcTemplate.queryForObject(sql, rowMapper, id); + } + + + @Override + public void updateProfile(Profile profile) { + String sql = "UPDATE user_profile SET profile_picture = ?," + + " bio = ?, phone_number = ?, show_phone_number = ?, show_dob = ? WHERE user_id = ?"; + jdbcTemplate.update(sql, profile.getProfilePicture(), profile.getBio(), profile.getPhoneNumber(),profile.isShowPhoneNumber(), profile.isShowDob(), profile.getUserId()); + } +}; diff --git a/src/main/java/polish_community_group_11/polish_community/profile/services/ProfileService.java b/src/main/java/polish_community_group_11/polish_community/profile/services/ProfileService.java new file mode 100644 index 0000000000000000000000000000000000000000..1e4de2551b8e8bdd81b768d1f705715846182750 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/profile/services/ProfileService.java @@ -0,0 +1,22 @@ +package polish_community_group_11.polish_community.profile.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import polish_community_group_11.polish_community.profile.models.Profile; +import polish_community_group_11.polish_community.profile.repositories.ProfileRepository; + +@Service +public class ProfileService { + private ProfileRepository profileRepository; + @Autowired + public ProfileService(ProfileRepository profileRepository) { + this.profileRepository = profileRepository; + } + public void addProfile(Profile profile) {profileRepository.addProfile(profile);} + public Profile getProfile(Integer userId) { + return profileRepository.getProfile(userId); + } + public void updateProfile(Profile profile) { + profileRepository.updateProfile(profile); + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/register/controllers/RegisterController.java b/src/main/java/polish_community_group_11/polish_community/register/controllers/RegisterController.java index c6aba7401b29156bfa9fa9c9bdd8d64f2f35429f..b27e416ec76b4219449021e37a37562a14bed6d4 100644 --- a/src/main/java/polish_community_group_11/polish_community/register/controllers/RegisterController.java +++ b/src/main/java/polish_community_group_11/polish_community/register/controllers/RegisterController.java @@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; +import polish_community_group_11.polish_community.profile.models.Profile; +import polish_community_group_11.polish_community.profile.services.ProfileService; import polish_community_group_11.polish_community.register.models.User; import polish_community_group_11.polish_community.register.models.Role; import polish_community_group_11.polish_community.register.services.UserService; @@ -19,6 +21,9 @@ public class RegisterController { @Autowired private RoleService roleService; + + @Autowired + private ProfileService profileService; // displaying the registration form using get request and ModelAndView @GetMapping("/register") @@ -49,6 +54,10 @@ public class RegisterController { // save user to the database userService.saveUser(user); +// Profile newProfile = new Profile(); +// newProfile.setUserId(user.getId()); +// profileService.addProfile(newProfile); + // redirect to the login page return "redirect:/login"; } diff --git a/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java b/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java index c6b84198638ebbd06e9d2f7e05b1f7a40a4ffc0b..34029e3e1f9f3bc064cd7be86809ac353bb14b27 100644 --- a/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java +++ b/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java @@ -33,7 +33,9 @@ public class WebSecurityConfig { private final RoleService roleService; private final String[] whiteListingPath = { - "/api/feed/**" +// "/event", +// "event/*" +// "/api/feed/**" , }; public WebSecurityConfig(UserService userService, RoleService roleService) { @@ -51,6 +53,8 @@ public class WebSecurityConfig { // require authentication for events only //update in future when more protected resources are available .authorizeHttpRequests((requests) -> requests +// .requestMatchers("/api/translations/**","/api/feed/**").permitAll() +// .requestMatchers().permitAll() .requestMatchers(whiteListingPath).authenticated() .anyRequest().permitAll() ) diff --git a/src/main/java/polish_community_group_11/polish_community/translation/controller/TranslationApisController.java b/src/main/java/polish_community_group_11/polish_community/translation/controller/TranslationApisController.java new file mode 100644 index 0000000000000000000000000000000000000000..878db0d444f76e602a9c5a01d69460892ad718de --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/controller/TranslationApisController.java @@ -0,0 +1,93 @@ +package polish_community_group_11.polish_community.translation.controller; + +import jakarta.annotation.PostConstruct; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.service.TranslationService; + +import java.util.List; + +@RestController +@RequestMapping("/api/translations") +public class TranslationApisController { + + private final TranslationService translationService; + + public TranslationApisController(TranslationService translationService) { + this.translationService = translationService; + } + + @GetMapping("/test") + public ResponseEntity<String> test() { + return ResponseEntity.ok("Translation API is working"); + } + + @PostConstruct + public void init() { + System.out.println("TranslationApisController initialized with mappings:"); + System.out.println("/api/translations"); + System.out.println("/api/translations/language/{language}"); + } + + // get all translations + @GetMapping + public ResponseEntity<List<Translation>> getAllTranslations() { + List<Translation> translations = translationService.getAllTranslations(); + return ResponseEntity.ok(translations); + } + + // for specific lang + @GetMapping("/language/{language}") + public ResponseEntity<List<Translation>> getTranslationsByLanguage(@PathVariable String language) { + List<Translation> translations = translationService.getTranslationsByLanguage(language); + return ResponseEntity.ok(translations); + } + + // for specific key and lang + @GetMapping("/{key}/language/{language}") + public ResponseEntity<Translation> getTranslationByKeyAndLanguage( + @PathVariable String key, + @PathVariable String language) { + Translation translation = translationService.getTranslationByKeyAndLanguage(key, language); + if (translation == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(translation); + } + + // adding new translation + @PostMapping + public ResponseEntity<String> addTranslation(@RequestBody Translation translation) { + translationService.addTranslation(translation); + return ResponseEntity.ok("Translation added successfully."); + } + + // update existing + @PutMapping("/{id}") + public ResponseEntity<String> updateTranslation(@PathVariable int id, @RequestBody Translation translation) { + translationService.updateTranslation(id, translation); + return ResponseEntity.ok("Translation updated successfully."); + } + + // deleting a translation + @DeleteMapping("/{id}") + public ResponseEntity<String> deleteTranslation(@PathVariable int id) { + translationService.deleteTranslation(id); + return ResponseEntity.ok("Translation deleted successfully."); + } + + // get all unique keys + @GetMapping("/keys") + public ResponseEntity<List<String>> getAllTranslationKeys() { + List<String> keys = translationService.getAllTranslationKeys(); + return ResponseEntity.ok(keys); + } + + // get all translations for a specific key across all langs + @GetMapping("/keys/{key}") + public ResponseEntity<List<Translation>> getTranslationsByKey(@PathVariable String key) { + List<Translation> translations = translationService.getTranslationsByKey(key); + return ResponseEntity.ok(translations); + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/model/Translation.java b/src/main/java/polish_community_group_11/polish_community/translation/model/Translation.java new file mode 100644 index 0000000000000000000000000000000000000000..90b84c5c826b15fec1650fdcbc70369a6a92b359 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/model/Translation.java @@ -0,0 +1,93 @@ +package polish_community_group_11.polish_community.translation.model; + +public class Translation { + private int id; + private String key; + private String language; + private String value; + + public Translation () { + + } + + public Translation(int id, String key, String language, String value){ + this.id = id; + this.key = key; + this.language = language; + this.value = value; + + } + + public Translation(String key, String language, String value){ + this.key = key; + this.language = language; + this.value = value; + } + + // Getters and Setters + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + // some utility methods for debugging + @Override + public String toString() { + return "Translation{" + + "id=" + id + + ", key='" + key + '\'' + + ", language='" + language + '\'' + + ", value='" + value + '\'' + + '}'; + } + + // cheking if two translations equal each other + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Translation that = (Translation) o; + + if (id != that.id) return false; + if (!key.equals(that.key)) return false; + if (!language.equals(that.language)) return false; + return value.equals(that.value); + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + key.hashCode(); + result = 31 * result + language.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepository.java b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..378e164bb66cc13256fb1e4687bf5ba26d3080eb --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepository.java @@ -0,0 +1,32 @@ +package polish_community_group_11.polish_community.translation.repository; + +import polish_community_group_11.polish_community.translation.model.Translation; + +import java.util.List; + +public interface TranslationRepository { + + + List<Translation> getAllTranslations(); + + + List<Translation> getTranslationsByLanguage(String language); + + + Translation getTranslationByKeyAndLanguage(String key, String language); + + + void addTranslation(Translation translation); + + + void updateTranslation(int id, Translation translation); + + + void deleteTranslation(int id); + + + List<String> getAllTranslationKeys(); + + + List<Translation> getTranslationsByKey(String key); +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepositoryImpl.java b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepositoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..62bc181d910e15069afc33400e560536fcf69e0b --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepositoryImpl.java @@ -0,0 +1,109 @@ +package polish_community_group_11.polish_community.translation.repository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import polish_community_group_11.polish_community.translation.model.Translation; + +import java.util.List; + +@Repository +public class TranslationRepositoryImpl implements TranslationRepository { + private static final Logger logger = LoggerFactory.getLogger(TranslationRepositoryImpl.class); + + private final JdbcTemplate jdbcTemplate; + private final RowMapper<Translation> translationMapper; + + public TranslationRepositoryImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.translationMapper = (rs, rowNum) -> { + Translation translation = new Translation(); + translation.setId(rs.getInt("id")); + translation.setKey(rs.getString("translation_key")); + translation.setLanguage(rs.getString("language")); + translation.setValue(rs.getString("value")); + return translation; + }; + // Test database connection on startup + try { + jdbcTemplate.queryForObject("SELECT 1", Integer.class); + logger.info("Database connection successful"); + } catch (Exception e) { + logger.error("Database connection failed", e); + } + } + + @Override + public List<Translation> getTranslationsByLanguage(String language) { + try { + logger.info("Attempting to fetch translations for language: {}", language); + String sql = "SELECT * FROM translations WHERE language = ?"; + List<Translation> results = jdbcTemplate.query(sql, translationMapper, language); + logger.info("Found {} translations for language {}", results.size(), language); + return results; + } catch (Exception e) { + logger.error("Error fetching translations for language {}: {}", language, e.getMessage()); + throw e; + } + } + + // getting all translaions + @Override + public List<Translation> getAllTranslations() { + String sql = "SELECT * FROM translations"; + return jdbcTemplate.query(sql, translationMapper); + } + + // get translation by key and language + @Override + public Translation getTranslationByKeyAndLanguage(String key, String language) { + String sql = "SELECT * FROM translations WHERE translation_key = ? AND language = ?"; + return jdbcTemplate.queryForObject(sql, translationMapper, key, language); + } + + // Add a new translation + @Override + public void addTranslation(Translation translation) { + String sql = "INSERT INTO translations (translation_key, language, translation_value) VALUES (?, ?, ?)"; + jdbcTemplate.update(sql, + translation.getKey(), + translation.getLanguage(), + translation.getValue() + ); + } + + // update existing translation + @Override + public void updateTranslation(int id, Translation translation) { + String sql = "UPDATE translations SET translation_key = ?, language = ?, translation_value = ? WHERE id = ?"; + jdbcTemplate.update(sql, + translation.getKey(), + translation.getLanguage(), + translation.getValue(), + id + ); + } + + // delete a translation + @Override + public void deleteTranslation(int id) { + String sql = "DELETE FROM translations WHERE id = ?"; + jdbcTemplate.update(sql, id); + } + + // getting all unique keys for word list management + @Override + public List<String> getAllTranslationKeys() { + String sql = "SELECT DISTINCT translation_key FROM translations"; + return jdbcTemplate.query(sql, (rs, rowNum) -> rs.getString("translation_key")); + } + + // get translations for something across all languages + @Override + public List<Translation> getTranslationsByKey(String key) { + String sql = "SELECT * FROM translations WHERE translation_key = ?"; + return jdbcTemplate.query(sql, translationMapper, key); + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationService.java b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationService.java new file mode 100644 index 0000000000000000000000000000000000000000..089f24dbdc3ebf37fe9bc8bfee9d0423b37fcad2 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationService.java @@ -0,0 +1,34 @@ +package polish_community_group_11.polish_community.translation.service; + + + +import polish_community_group_11.polish_community.translation.model.Translation; + +import java.util.List; + +public interface TranslationService { + + + List<Translation> getAllTranslations(); + + + List<Translation> getTranslationsByLanguage(String language); + + + Translation getTranslationByKeyAndLanguage(String key, String language); + + + void addTranslation(Translation translation); + + + void updateTranslation(int id, Translation translation); + + + void deleteTranslation(int id); + + + List<String> getAllTranslationKeys(); + + + List<Translation> getTranslationsByKey(String key); +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImpl.java b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..82bba85308e0e4e116ff6c7f869b07d1f6f6394f --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImpl.java @@ -0,0 +1,57 @@ +package polish_community_group_11.polish_community.translation.service; + +import org.springframework.stereotype.Service; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.repository.TranslationRepository; + +import java.util.List; + +@Service +public class TranslationServiceImpl implements TranslationService { + + private final TranslationRepository translationRepository; + + public TranslationServiceImpl(TranslationRepository translationRepository) { + this.translationRepository = translationRepository; + } + + @Override + public List<Translation> getAllTranslations() { + return translationRepository.getAllTranslations(); + } + + @Override + public List<Translation> getTranslationsByLanguage(String language) { + return translationRepository.getTranslationsByLanguage(language); + } + + @Override + public Translation getTranslationByKeyAndLanguage(String key, String language) { + return translationRepository.getTranslationByKeyAndLanguage(key, language); + } + + @Override + public void addTranslation(Translation translation) { + translationRepository.addTranslation(translation); + } + + @Override + public void updateTranslation(int id, Translation translation) { + translationRepository.updateTranslation(id, translation); + } + + @Override + public void deleteTranslation(int id) { + translationRepository.deleteTranslation(id); + } + + @Override + public List<String> getAllTranslationKeys() { + return translationRepository.getAllTranslationKeys(); + } + + @Override + public List<Translation> getTranslationsByKey(String key) { + return translationRepository.getTranslationsByKey(key); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e12924f4d009b519539abb5995545d2526ee5dcd..9e1b33138e2ec0bd4cc5cab1775b2e06c1daadc9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -18,7 +18,7 @@ server.error.whitelabel.enabled=false server.error.path=/error spring.jpa.hibernate.ddl-auto=none spring.sql.init.schema-locations=classpath:/schema.sql -spring.sql.init.data-locations=classpath:/data.sql, classpath:/data_information_domains.sql +spring.sql.init.data-locations=classpath:/data.sql, classpath:/data_information_domains.sql, classpath:/translations.sql #spring.jpa.hibernate.ddl-auto=none spring.jpa.defer-datasource-initialization=true diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 51d9a719c9338ada3488481817e06977162570d5..a142a3314181fcf50f8532f4ee34f0b01e311f11 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -75,7 +75,9 @@ INSERT INTO users (email, password, fullname, dob, role_id, organization) VALUES INSERT INTO users (email, password, fullname, dob, role_id) -VALUES ('user@email.com', 'Abcdef1!', 'Jane Doe', '200-01-01', 1 ); +VALUES ('user@email.com', 'Abcdef1!', 'Jane Doe', '2000-01-01', 1 ); +UPDATE user_profile SET bio = 'Jane''s bio' WHERE user_id = (SELECT id from users where email = 'user@email.com'); + -- insert the user and role id's of the created roles and user INSERT INTO user_roles (user_id, role_id) VALUES (1, 1); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index ecc66b7c659c93ac8b24fb95e50361d85954d2aa..e54eea227e7ed0aadfd69be3ed6131ed0f894b78 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -9,9 +9,11 @@ DROP TABLE IF EXISTS feed_tags; DROP TABLE IF EXISTS comment; DROP TABLE IF EXISTS feed; DROP TABLE IF EXISTS tags; +DROP TABLE IF EXISTS user_profile; DROP TABLE IF EXISTS user_roles; DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS roles; +DROP TABLE IF EXISTS translations; create table if not exists categories ( category_id INT AUTO_INCREMENT PRIMARY KEY, @@ -122,7 +124,7 @@ create table if not exists post_likes ( CREATE TABLE IF NOT EXISTS comment ( - id INT AUTO_INCREMENT PRIMARY KEY, + id INT AUTO_INCREMENT PRIMARY KEY, comment_content TEXT NOT NULL, created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, user_id INT NOT NULL, @@ -131,6 +133,15 @@ CREATE TABLE IF NOT EXISTS comment FOREIGN KEY (user_id) REFERENCES users (id) ) ENGINE = InnoDB; +create table if not exists translations ( + id int auto_increment primary key, + translation_key varchar(255) not null, + language varchar(10) not null, + value text not null +) engine = InnoDB; + +ALTER TABLE translations MODIFY value TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + -- create schema for event create table if not exists event( @@ -145,4 +156,20 @@ create table if not exists event( whyJoin text, benefits text, foreign key event_user_id_fk (user_id) references users(id) -) engine = InnoDB; \ No newline at end of file +) engine = InnoDB; + +CREATE TABLE IF NOT EXISTS user_profile ( + user_id INT PRIMARY KEY NOT NULL, + profile_picture TEXT, + bio TEXT, + phone_number VARCHAR(20), + show_phone_number BOOLEAN DEFAULT FALSE, + show_dob BOOLEAN DEFAULT FALSE, + FOREIGN KEY (user_id) REFERENCES users(id) + ON DELETE CASCADE + ON UPDATE CASCADE +) engine = InnoDB; + +CREATE TRIGGER UMustHaveProfile AFTER INSERT ON users FOR EACH ROW INSERT INTO user_profile (user_id) VALUES (NEW.id); + + diff --git a/src/main/resources/static/assets/default-profile.jpg b/src/main/resources/static/assets/default-profile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7a8a84327d080b877818d9fb2f528a5e4784c790 Binary files /dev/null and b/src/main/resources/static/assets/default-profile.jpg differ diff --git a/src/main/resources/static/css/profile/profile.css b/src/main/resources/static/css/profile/profile.css new file mode 100644 index 0000000000000000000000000000000000000000..fed39bcd6e966147b770fc5ad97cf89c71312965 --- /dev/null +++ b/src/main/resources/static/css/profile/profile.css @@ -0,0 +1,105 @@ +.profile-container { + display: flex; + justify-content: center; + align-items: center; + min-height: calc(100vh - 64px - 100px); /* Adjust for header and footer */ + padding: 20px; +} + +.profile-card { + background-color: var(--secondary-color); + border-radius: 15px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 30px; + width: 100%; + max-width: 400px; + text-align: center; +} +.profile-picture { + width: 150px; + height: 150px; + border-radius: 50%; + object-fit: cover; + margin-bottom: 20px; + border: 3px solid var(--border-color); +} + +.full-name { + font-size: 24px; + margin-bottom: 10px; + color: var(--text-color); +} +.email, #edit-email { + font-size: 14px; + color: var(--text-color); + margin-bottom: 15px; +} +.organisation { + font-size: 16px; + color: var(--secondary-text-color); + margin-bottom: 15px; +} +/*#edit-organisation {*/ +/* color: var(--text-color);*/ +/*}*/ +.optional-field, #edit-bio, #edit-phone-number, #edit-phone-number { + font-size: 14px; + color: var(--text-color); + margin-bottom: 10px; +} + +.edit-profile-btn { + background-color: var(--secondary-text-color); + color: var(--text-color); + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +#edit-profile-form input[type="text"], +#edit-profile-form input[type="tel"], +#edit-profile-form textarea { + width: 100%; + padding: 8px; + margin-bottom: 10px; + border: 1px solid var(--secondary-text-color); + border-radius: 5px; + background-color: var(--background-color); + color: var(--text-color); +} + +.checkbox-container { + display: flex; + justify-content: space-between; + margin-bottom: 10px; +} +.not-editable { + cursor: not-allowed; + background-color: var(--secondary-color) !important; +} + +@media (max-width: 768px) { + .profile-card { + padding: 20px; + } + + .profile-picture { + width: 120px; + height: 120px; + } + + .full-name{ + font-size: 20px; + } + + .email, #edit-email { + font-size: 14px; + } + + .optional-field, #edit-bio, #edit-phone-number{ + font-size: 12px; + } +} + diff --git a/src/main/resources/static/images/logo.png b/src/main/resources/static/images/logo.png deleted file mode 100644 index b7aa35736858285f7f535c77181c595e2018765a..0000000000000000000000000000000000000000 Binary files a/src/main/resources/static/images/logo.png and /dev/null differ diff --git a/src/main/resources/static/js/layout/layout.js b/src/main/resources/static/js/layout/layout.js new file mode 100644 index 0000000000000000000000000000000000000000..f4647f6766bd47d40c78a18f1af972f900fcf8f8 --- /dev/null +++ b/src/main/resources/static/js/layout/layout.js @@ -0,0 +1,54 @@ +let translations = []; +let currentLanguage = "en"; + +/** + * Fetch translations for the given language. + * @param {string} language - Language code to fetch translations for. + */ +async function fetchTranslations(language) { + try { + const response = await fetch(`/api/translations/language/${language}`); + if (!response.ok) { + throw new Error(`Failed to fetch translations: ${response.statusText}`); + } + translations = await response.json(); // This is now an array of translation objects + currentLanguage = language; + console.log("Translations updated:", translations); + updateUIWithTranslations(); + document.dispatchEvent(new Event("translations-updated")); + } catch (error) { + console.error("Error fetching translations:", error); + } +} + +function updateUIWithTranslations() { + const elementsToTranslate = document.querySelectorAll("[data-translate-key]"); + + elementsToTranslate.forEach(element => { + const translationKey = element.getAttribute("data-translate-key"); + // Find the translation object with matching key + const translation = translations.find(t => t.key === translationKey); + if (translation) { + element.textContent = translation.value; + } else { + console.warn(`No translation found for key: ${translationKey}`); + } + }); +} + +// fetch translations on page load +document.addEventListener("DOMContentLoaded", () => { + fetchTranslations(currentLanguage); + console.log("I have run"); + + // handle language selection + const languageSelector = document.querySelector(".languageSelector select"); + if (languageSelector) { + languageSelector.addEventListener("change", async (event) => { + const selectedLanguage = event.target.value; + if (selectedLanguage !== currentLanguage) { + await fetchTranslations(selectedLanguage); + } + }); + } +}); \ No newline at end of file diff --git a/src/main/resources/static/js/profile/profile.js b/src/main/resources/static/js/profile/profile.js new file mode 100644 index 0000000000000000000000000000000000000000..7a8ccc5f9d18336d9543fd06bba0168ede8a0d40 --- /dev/null +++ b/src/main/resources/static/js/profile/profile.js @@ -0,0 +1,83 @@ +document.addEventListener('DOMContentLoaded', function () { + const profileCard = document.getElementById('profile-card'); + const editForm = document.getElementById('edit-profile-form'); + const editBtn = document.getElementById('edit-profile-btn'); + const cancelBtn = document.getElementById('cancel-edit'); + const profilePictureInput = document.getElementById('profile-picture-input'); + const changeProfilePictureBtn = document.getElementById('change-profile-picture-btn'); + const editProfilePicture = document.getElementById('edit-profile-picture'); + fetch('/profile-json') + .then(response => response.json()) + .then(profile => { + document.getElementById('profile-picture').src = profile.profilePicture || '/assets/default-profile.jpg'; + /*document.getElementById('profile-heading').innerHTML = `${profile.fullName}'s bio`;*/ + document.getElementById('full-name').innerHTML = profile.fullName; + document.getElementById('email').innerHTML = profile.email; + if (profile.organisation) { + document.getElementById('organisation').innerHTML = profile.organisation; + } + if (profile.bio) { + document.getElementById('bio').innerHTML = profile.bio; + } + if (profile.showDob && profile.dob) { + document.getElementById('date-of-birth').innerHTML = profile.dob; + } + if (profile.showPhoneNumber && profile.phoneNumber) { + document.getElementById('phone-number').innerHTML = `Phone: ${profile.phoneNumber}`; + } + populateEditForm(profile); + updateProfileCard(profile); + + }) + .catch(error => { + console.error('Error:', error); + console.error('Error stack:', error.stack); + document.getElementById('profile-container').innerHTML = 'Error loading profile.'; + }); + editBtn.addEventListener('click', () => { + profileCard.style.display = 'none'; + editForm.style.display = 'block'; + }); + + cancelBtn.addEventListener('click', () => { + editForm.style.display = 'none'; + profileCard.style.display = 'block'; + }); + changeProfilePictureBtn.addEventListener('click', () => { + profilePictureInput.click(); + }); + + profilePictureInput.addEventListener('change', (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + editProfilePicture.src = e.target.result; + }; + reader.readAsDataURL(file); + } + }); + + function updateProfileCard(profile) { + document.getElementById('profile-picture').src = profile.profilePicture || '/assets/default-profile.jpg'; + document.getElementById('full-name').textContent = profile.fullName; + document.getElementById('email').textContent = profile.email; + document.getElementById('organisation').textContent = profile.organisation || ''; + document.getElementById('bio').textContent = profile.bio || ''; + document.getElementById('date-of-birth').textContent = profile.showDob && profile.dob ? profile.dob : ''; + document.getElementById('phone-number').textContent = profile.showPhoneNumber && profile.phoneNumber ? `Phone: ${profile.phoneNumber}` : ''; + } + + function populateEditForm(profile) { + document.getElementById('edit-profile-picture').src = profile.profilePicture || '/assets/default-profile.jpg'; + document.getElementById('original-profile-picture').value = profile.profilePicture; + document.getElementById('edit-full-name').value = profile.fullName; + document.getElementById('edit-email').value = profile.email; + document.getElementById('edit-organisation').value = profile.organisation || ''; + document.getElementById('edit-bio').value = profile.bio || ''; + document.getElementById('edit-date-of-birth').value = profile.dob || ''; + document.getElementById('edit-phone-number').value = profile.phoneNumber || ''; + document.getElementById('show-dob').checked = profile.showDob; + document.getElementById('show-number').checked = profile.showPhoneNumber; + } +}); \ No newline at end of file diff --git a/src/main/resources/templates/home/home.html b/src/main/resources/templates/home/home.html index 54697729ca5270b7ec8bf5143524a05b020b24e8..2f8e70b1898dc70e031a5123a8535900a45e785f 100644 --- a/src/main/resources/templates/home/home.html +++ b/src/main/resources/templates/home/home.html @@ -10,60 +10,60 @@ </head> <section layout:fragment="content"> - <h1>Welcome to our Community</h1> - <p>Connect, Share, and Grow Together</p> + <h1 data-translate-key="home.header">Welcome to our Community</h1> + <p data-translate-key="home.smallheader">Connect, Share, and Grow Together</p> <section class="tiles"> <div class="tile" id="feedTile"> <a th:href="@{/feed}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/feed.png" class="tile-logo"><span class="tile-title">Feed</span> + <img src="/assets/navbarImages/feed.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.feed">Feed</span> </div> - <p class="tile-description">Stay updated with the latest posts</p> + <p class="tile-description" data-translate-key="home.latest_posts">Stay updated with the latest posts</p> </a> </div> <div class="tile" id="newsTile"> <a th:href="@{/news}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/news.png" class="tile-logo"><span class="tile-title">News</span> + <img src="/assets/navbarImages/news.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.news">News</span> </div> - <p class="tile-description">Discover the latest community news</p> + <p class="tile-description" data-translate-key="home.news">Discover the latest community news</p> </a> </div> <div class="tile" id="eventsTile"> <a th:href="@{/event}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/events.png" class="tile-logo"><span class="tile-title">Events</span> + <img src="/assets/navbarImages/events.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.events">Events</span> </div> - <p class="tile-description">Join our upcoming community events</p> + <p class="tile-description" data-translate-key="home.events">Join our upcoming community events</p> </a> </div> <div class="tile" id="infoTile"> <a th:href="@{/categories}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/info.png" class="tile-logo"><span class="tile-title">Info Database</span> + <img src="/assets/navbarImages/info.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.db_info">Info Database</span> </div> - <p class="tile-description">Access our community information database</p> + <p class="tile-description" data-translate-key="home.db_info">Access our community information database</p> </a> </div> <div class="tile" id="aboutTile"> <a th:href="@{/aboutUs}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/about.png" class="tile-logo"><span class="tile-title">About Us</span> + <img src="/assets/navbarImages/about.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.about_us">About Us</span> </div> - <p class="tile-description">Learn about our mission and values</p> + <p class="tile-description" data-translate-key="home.about_us">Learn about our mission and values</p> </a> </div> </section> - <h1>Join Our Thriving Community Today!</h1> - <p>Connect with like-minded individuals, share your ideas, and be part of something amazing</p> - <button>Get Started</button> + <h1 data-translate-key="home.join_community">Join Our Thriving Community Today!</h1> + <p data-translate-key="home.tagline">Connect with like-minded individuals, share your ideas, and be part of something amazing</p> + <button data-translate-key="home.get_started">Get Started</button> </section> diff --git a/src/main/resources/templates/layout/layout.html b/src/main/resources/templates/layout/layout.html index 03d4db4d0ccfbe479a9ec72f65eb40cd510b0942..551413576f822a5295b69b111c05aa30248c5dd4 100644 --- a/src/main/resources/templates/layout/layout.html +++ b/src/main/resources/templates/layout/layout.html @@ -8,104 +8,112 @@ <title>Ludek Polonia Wajiska</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> - <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" + rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="/css/comments/comments.css"> <link rel="stylesheet" href="/css/layout/layout.css"> </head> <body> - <header class="header"> - <section class="sidebar"> - <nav class="sidebar"> - <div class="navLeft"> - <a th:href="@{/}" class="logoLink"> - <img src="/assets/navbarImages/logo.png" class="logoImage"><span class="navText">Ludek Polonia Wajiska</span> - </a> - </div> - - <div class="navMiddle"> - <a th:href="@{/}" class="navLink"> - <img src="/assets/navbarImages/home.png" class="navIcons"><span class="navText">Home</span> - </a> - <a th:href="@{/feed}" class="navLink"> - <img src="/assets/navbarImages/feed.png" class="navIcons"><span class="navText">Feed</span> - </a> - <a th:href="@{/news}" class="navLink"> - <img src="/assets/navbarImages/news.png" class="navIcons"><span class="navText">News</span> - </a> - <a th:href="@{/event}" class="navLink"> - <img src="/assets/navbarImages/events.png" class="navIcons"><span class="navText">Events</span> - </a> - <a th:href="@{/categories}" class="navLink"> - <img src="/assets/navbarImages/info.png" class="navIcons"><span class="navText">Info Database</span> - </a> - <a th:href="@{/aboutUs}" class="navLink"> - <img src="/assets/navbarImages/about.png" class="navIcons"><span class="navText">About Us</span> - </a> - <a th:href="@{/contactus}" class="navLink"> - <img src="/assets/navbarImages/contact.png" class="navIcons"><span class="navText">Contact Us</span> - </a> - </div> +<header class="header"> + <section class="sidebar"> + <nav class="sidebar"> + <div class="navLeft"> + <a th:href="@{/}" class="logoLink"> + <img src="/assets/navbarImages/logo.png" class="logoImage"><span class="navText">Ludek Polonia Wajiska</span> + </a> + </div> - <!-- Right Section: Language Selector and Profile --> - <div class="navRight"> - <!-- Language Selector --> - <div class="languageSelector"> - <img src="/assets/navbarImages/globe.png" class="navIcons"> - <select name="language"> - <option value="english">English</option> - <option value="polish">Polish</option> - </select> - </div> + <div class="navMiddle"> + <a th:href="@{/}" class="navLink"> + <img src="/assets/navbarImages/home.png" class="navIcons"><span class="navText" + data-translate-key="navbar.home">Home</span> + </a> + <a th:href="@{/feed}" class="navLink"> + <img src="/assets/navbarImages/feed.png" class="navIcons"><span class="navText" + data-translate-key="navbar.feed">Feed</span> + </a> + <a th:href="@{/news}" class="navLink"> + <img src="/assets/navbarImages/news.png" class="navIcons"><span class="navText" + data-translate-key="navbar.news">News</span> + </a> + <a th:href="@{/event}" class="navLink"> + <img src="/assets/navbarImages/events.png" class="navIcons"><span class="navText" + data-translate-key="navbar.events">Events</span> + </a> + <a th:href="@{/categories}" class="navLink"> + <img src="/assets/navbarImages/info.png" class="navIcons"><span class="navText" + data-translate-key="navbar.db_info">Info Database</span> + </a> + <a th:href="@{/aboutUs}" class="navLink"> + <img src="/assets/navbarImages/about.png" class="navIcons"><span class="navText" + data-translate-key="navbar.about_us">About Us</span> + </a> + <a th:href="@{/contactus}" class="navLink"> + <img src="/assets/navbarImages/contact.png" class="navIcons"><span class="navText" + data-translate-key="navbar.contact_us">Contact Us</span> + </a> + </div> - <!-- Profile --> - <a th:href="@{/profile}" class="navLink"> - <img src="/assets/navbarImages/profile.png" class="navIcons"><span class="navText">Profile</span> - </a> + <!-- Right Section: Language Selector and Profile --> + <div class="navRight"> + <!-- Language Selector --> + <div class="languageSelector"> + <img src="/assets/navbarImages/globe.png" class="navIcons"> + <select name="language"> + <option value="en">English</option> + <option value="pl">Polish</option> + </select> </div> - </nav> - </section> - </header> - <div class="mainBody"> + <!-- Profile --> + <a th:href="@{/profile}" class="navLink"> + <img src="/assets/navbarImages/profile.png" class="navIcons"><span class="navText" + data-translate-key="navbar.profile">Profile</span> + </a> + </div> + </nav> + </section> +</header> +<div class="mainBody"> + <main layout:fragment="content" class="content"></main> +</div> - <main layout:fragment="content" class="content"></main> +<footer class="footer"> + <div class="footer-section about"> + <h3 class="footerTitle">Ludek Polonia Wajiska</h3> + <p class="footerText" data-translate-key="footer.tagline">Connecting people, sharing ideas, and building a + better future together.</p> </div> + <div class="footer-section links"> + <h3 class="footerTitle" data-translate-key="footer.quick_links">Quick Links</h3> + <ul class="footerLinks"> + <li><a th:href="@{/aboutus}" class="footerLink" data-translate-key="navbar.about_us">About Us</a></li> + <li><a th:href="@{/contact}" class="footerLink" data-translate-key="navbar.contact">Contact</a></li> + <li><a th:href="@{/}" class="footerLink" data-translate-key="footer.privacy_policy">Privacy Policy</a></li> + <li><a th:href="@{/}" class="footerLink" data-translate-key="footer.terms_of_service">Terms of Service</a> + </li> + </ul> + </div> - - <footer class="footer"> - <div class="footer-section about"> - <h3 class="footerTitle">Polish Community Website</h3> - <p class="footerText">Connecting people, sharing ideas, and building a better future together.</p> - </div> - - <div class="footer-section links"> - <h3 class="footerTitle">Quick Links</h3> - <ul class="footerLinks"> - <li><a th:href="@{/aboutus}" class="footerLink">About Us</a></li> - <li><a th:href="@{/contact}" class="footerLink">Contact</a></li> - <li><a th:href="@{/}" class="footerLink">Privacy Policy</a></li> - <li><a th:href="@{/}" class="footerLink">Terms of Service</a></li> - </ul> - </div> - - <div class="footer-section connect"> - <h3 class="footerTitle">Connect with us</h3> - <div class="social-icons"> - <a href="https://www.facebook.com/LudekPCG" class="social-icon"><img src="/assets/navbarImages/facebook.png" alt="Facebook"></a> - <a href="#" class="social-icon"><img src="/assets/navbarImages/twitter.png" alt="Twitter"></a> - <a href="#" class="social-icon"><img src="/assets/navbarImages/instagram.png" alt="Instagram"></a> - </div> - </div> - - <div class="footer-section copyright"> - <p class="footerCompanyName">© LUDEK PCG ltd. All rights reserved.</p> + <div class="footer-section connect"> + <h3 class="footerTitle" data-translate-key="footer.connect_with_us">Connect with us</h3> + <div class="social-icons"> + <a href="https://www.facebook.com/LudekPCG" class="social-icon"><img src="/assets/navbarImages/facebook.png" + alt="Facebook"></a> + <a href="#" class="social-icon"><img src="/assets/navbarImages/twitter.png" alt="Twitter"></a> + <a href="#" class="social-icon"><img src="/assets/navbarImages/instagram.png" alt="Instagram"></a> </div> - </footer> - <script th:replace="~{comments/commentFragment::commentScript}"></script> + </div> + <div class="footer-section copyright"> + <p class="footerCompanyName">© LUDEK PCG ltd. <span data-translate-key="footer.all_rights_reserved" >All rights reserved.</span></p> + </div> +</footer> +<script src="/js/layout/layout.js" defer></script> +<script th:replace="~{comments/commentFragment::commentScript}"></script> </body> </html> \ No newline at end of file diff --git a/src/main/resources/templates/profile/profilePage.html b/src/main/resources/templates/profile/profilePage.html new file mode 100644 index 0000000000000000000000000000000000000000..bba95b8f6c911dd2e9f482ed246f1ed13c3d9fe8 --- /dev/null +++ b/src/main/resources/templates/profile/profilePage.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html lang="en" xmlns:th="http://www.thymeleaf.org" + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" + layout:decorate="~{layout/layout}"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Profile</title> + <link rel="stylesheet" href="/css/profile/profile.css"> + <script src="/js/profile/profile.js" defer></script> + +</head> +<section class="profile-container" id="profile-container" layout:fragment="content"> + <div class="profile-card" id="profile-card"> + <!--<h3 id="profile-heading">Profile Page</h3>--> + <img id="profile-picture" src="/assets/default-profile.jpg" alt="Profile Picture" class="profile-picture"> + <div class="profile-info"> + <h2 class="full-name" id="full-name">Name</h2> + <p id="organisation" class="organisation"></p> + <p class="email" id="email"></p> + <p id="bio" class="optional-field"></p> + <p id="date-of-birth" class="optional-field"></p> + <p id="phone-number" class="optional-field"></p> + </div> + <button class="edit-profile-btn" id="edit-profile-btn">Edit Profile</button> + </div> + <form id="edit-profile-form" class="profile-card" style="display: none" th:action="@{/update}" method="post" enctype="multipart/form-data"> + <img id="edit-profile-picture" src="/assets/default-profile.jpg" alt="Profile Picture" class="profile-picture"> + <input type="file" id="profile-picture-input" name="newPicture" accept="image/*" style="display: none;"> + <br> + <button type="button" id="change-profile-picture-btn">Change Profile Picture</button> + <input type="hidden" id="original-profile-picture" name="profilePicture" readonly> + <input type="text" class="not-editable" id="edit-full-name" readonly> + <input type="text" class="not-editable" id="edit-email" readonly> + <input type="text" class="not-editable" id="edit-organisation" readonly placeholder="Organisation"> + <textarea id="edit-bio" name="bio" placeholder="Bio" class="optional-field"></textarea> + <input type="text" class="not-editable" id="edit-date-of-birth" readonly> + <input type="tel" id="edit-phone-number" name="phoneNumber" placeholder="Phone Number" class="optional-field"> + <div class="checkbox-container"> + <label><input type="checkbox" id="show-dob" name="showDob"> Show Date of Birth</label> + <label><input type="checkbox" id="show-number" name="showPhoneNumber"> Show Phone Number</label> + </div> + <button type="submit" class="edit-profile-btn">Save Changes</button> + <button type="button" id="cancel-edit" class="edit-profile-btn">Cancel</button> + </form> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/translations.sql b/src/main/resources/translations.sql new file mode 100644 index 0000000000000000000000000000000000000000..1ef42bbaf33963cb21d6fb99877771d42a6a5346 --- /dev/null +++ b/src/main/resources/translations.sql @@ -0,0 +1,54 @@ +INSERT INTO translations (translation_key, language, value) VALUES + -- English translations + ('navbar.home', 'en', 'Home'), + ('navbar.news', 'en', 'News'), + ('navbar.events', 'en', 'Events'), + ('navbar.db_info', 'en', 'Info Database'), + ('navbar.contact_us', 'en', 'Contact Us'), + ('navbar.feed', 'en', 'Feed'), + ('navbar.about_us', 'en', 'About Us'), + ('navbar.profile', 'en', 'Profile'), + ('home.header', 'en', 'Welcome to our Community'), + ('home.smallheader', 'en', 'Connect, Share, and Grow Together'), + ('home.latest_posts', 'en', 'Stay updated with the latest posts'), + ('home.news', 'en', 'Discover the latest community news'), + ('home.events', 'en', 'Join our upcoming community events'), + ('home.db_info', 'en', 'Access our community information database'), + ('home.about_us', 'en', 'Learn about our mission and values'), + ('home.join_community', 'en', 'Join Our Thriving Community Today!'), + ('home.tagline', 'en', 'Connect with like-minded individuals, share your ideas, and be part of something amazing'), + ('home.get_started', 'en', 'Get Started'), + ('footer.tagline', 'en', 'Connecting people, sharing ideas, and building a better future together.'), + ('footer.quick_links', 'en', 'Quick Links'), + ('footer.connect_with_us', 'en', 'Connect with us'), + ('footer.privacy_policy', 'en', 'Privacy Policy'), + ('footer.terms_of_service', 'en', 'Terms of Service'), + ('footer.all_rights_reserved', 'en', 'All rights reserved.'), + + -- Polish translations + ('navbar.home', 'pl', 'Strona główna'), + ('navbar.news', 'pl', 'AktualnoÅ›ci'), + ('navbar.events', 'pl', 'Wydarzenia'), + ('navbar.db_info', 'pl', 'Informacje o bazie danych'), + ('navbar.contact_us', 'pl', 'Skontaktuj siÄ™ z nami'), + ('navbar.feed', 'pl', 'KanaÅ‚'), + ('navbar.about_us', 'pl', 'O nas'), + ('navbar.profile', 'pl', 'Profil'), + ('home.header', 'pl', 'Witamy w naszej spoÅ‚ecznoÅ›ci'), + ('home.smallheader', 'pl', 'ÅÄ…cz siÄ™, dziel i rozwijaj razem'), + ('home.latest_posts', 'pl', 'BÄ…dź na bieżąco z najnowszymi postami'), + ('home.news', 'pl', 'Odkryj najnowsze wiadomoÅ›ci spoÅ‚ecznoÅ›ciowe'), + ('home.events', 'pl', 'Dołącz do nadchodzÄ…cych wydarzeÅ„ spoÅ‚ecznoÅ›ciowych'), + ('home.db_info', 'pl', 'Uzyskaj dostÄ™p do naszej bazy informacji spoÅ‚ecznoÅ›ciowych'), + ('home.about_us', 'pl', 'Dowiedz siÄ™ o naszej misji i wartoÅ›ciach'), + ('home.join_community', 'pl', 'Dołącz do naszej rozwijajÄ…cej siÄ™ spoÅ‚ecznoÅ›ci już dziÅ›!'), + ('home.tagline', 'pl', 'ÅÄ…cz siÄ™ z podobnie myÅ›lÄ…cymi ludźmi, dziel siÄ™ pomysÅ‚ami i bÄ…dź częściÄ… czegoÅ› niesamowitego'), + ('home.get_started', 'pl', 'Zacznij teraz'), + ('footer.tagline', 'pl', 'ÅÄ…czÄ…c ludzi, dzielÄ…c siÄ™ pomysÅ‚ami i budujÄ…c lepszÄ… przyszÅ‚ość razem.'), + ('footer.quick_links', 'pl', 'Szybkie linki'), + ('footer.connect_with_us', 'pl', 'Połącz siÄ™ z nami'), + ('footer.privacy_policy', 'pl', 'Polityka prywatnoÅ›ci'), + ('footer.terms_of_service', 'pl', 'Warunki korzystania z usÅ‚ugi'), + ('footer.all_rights_reserved', 'pl', 'Wszelkie prawa zastrzeżone.'); + + diff --git a/src/test/java/polish_community_group_11/polish_community/profile/controllers/ProfileControllerTest.java b/src/test/java/polish_community_group_11/polish_community/profile/controllers/ProfileControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..571bc90b2183b9c621cfbd440420d3ea9340b969 --- /dev/null +++ b/src/test/java/polish_community_group_11/polish_community/profile/controllers/ProfileControllerTest.java @@ -0,0 +1,36 @@ +package polish_community_group_11.polish_community.profile.controllers; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import polish_community_group_11.polish_community.register.models.User; +import polish_community_group_11.polish_community.register.services.UserService; + +import static org.junit.jupiter.api.Assertions.*; +@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment. RANDOM_PORT) +@AutoConfigureMockMvc +class ProfileControllerTest { + @Autowired + MockMvc mockMvc; + @Test + @WithUserDetails("user@email.com") + void whenUserCallsProfileEndpointThenTheirProfileIsReturned() throws Exception { + + mockMvc.perform(MockMvcRequestBuilders.get("/profile-json") + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) + .andExpect(MockMvcResultMatchers.content().json("{\"email\": \"user@email.com\"}")) + .andExpect(MockMvcResultMatchers.content().json("{\"bio\": \"Jane's bio\"}")); + } +} + + + + diff --git a/src/test/java/polish_community_group_11/polish_community/translation/controller/TranslationControllerTest.java b/src/test/java/polish_community_group_11/polish_community/translation/controller/TranslationControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..187f7c4925e51412887b4649ca95b2ca67b2e911 --- /dev/null +++ b/src/test/java/polish_community_group_11/polish_community/translation/controller/TranslationControllerTest.java @@ -0,0 +1,85 @@ +package polish_community_group_11.polish_community.translation.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.service.TranslationService; + +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(TranslationApisController.class) +class TranslationControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private TranslationService translationService; + + private List<Translation> mockTranslations; + + @BeforeEach + void setUp() { + mockTranslations = Arrays.asList( + new Translation(1, "greeting", "en", "Hello"), + new Translation(2, "greeting", "pl", "Cześć") + ); + } + + @Test + void getAllTranslations_shouldReturnAllTranslations() throws Exception { + + when(translationService.getAllTranslations()).thenReturn(mockTranslations); + mockMvc.perform(get("/api/translations") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].key", is("greeting"))) + .andExpect(jsonPath("$[0].value", is("Hello"))); + } + + @Test + void getTranslationsByLanguage_shouldReturnTranslationsForSpecificLanguage() throws Exception { + List<Translation> englishTranslations = List.of( + new Translation(1, "greeting", "en", "Hello") + ); + + when(translationService.getTranslationsByLanguage("en")).thenReturn(englishTranslations); + + mockMvc.perform(get("/api/translations/language/en") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].value", is("Hello"))); + } + + @Test + void addTranslation_shouldAddNewTranslation() throws Exception { + Translation newTranslation = new Translation(0, "farewell", "en", "Goodbye"); + + + Mockito.doNothing().when(translationService).addTranslation(Mockito.any(Translation.class)); + + mockMvc.perform(post("/api/translations") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"key\":\"farewell\",\"language\":\"en\",\"value\":\"Goodbye\"}")) + .andExpect(status().isOk()) + .andExpect(content().string("Translation added successfully.")); + } + + +} diff --git a/src/test/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImplTest.java b/src/test/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..18dad83d2eba0a5f25f7398e565f48fbcd1a23c1 --- /dev/null +++ b/src/test/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImplTest.java @@ -0,0 +1,42 @@ +package polish_community_group_11.polish_community.translation.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.repository.TranslationRepository; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class TranslationServiceImplTest { + + @Mock + private TranslationRepository translationRepository; + + @InjectMocks + private TranslationServiceImpl translationService; + + private Translation mockTranslation; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + mockTranslation = new Translation(1, "greeting", "en", "Hello"); + } + + @Test + void getAllTranslations_shouldReturnAllTranslations() { + when(translationRepository.getAllTranslations()).thenReturn(List.of(mockTranslation)); + + List<Translation> result = translationService.getAllTranslations(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Hello", result.get(0).getValue()); + } +}