diff --git a/pom.xml b/pom.xml index 261744b50..b85003845 100644 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,17 @@ org.flywaydb flyway-core + + org.mockito + mockito-core + 2.22.0 + test + + + + + diff --git a/src/main/java/com/makersacademy/acebook/SecurityConfiguration.java b/src/main/java/com/makersacademy/acebook/SecurityConfiguration.java index a6829646e..fed083b0d 100644 --- a/src/main/java/com/makersacademy/acebook/SecurityConfiguration.java +++ b/src/main/java/com/makersacademy/acebook/SecurityConfiguration.java @@ -26,9 +26,10 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() + .antMatchers("/", "/home", "/css/**").permitAll() .antMatchers("/posts").hasRole("USER") .antMatchers("/users").permitAll() - .and().formLogin(); + .and().formLogin().defaultSuccessUrl("/posts", true); } @Bean diff --git a/src/main/java/com/makersacademy/acebook/controller/HomeController.java b/src/main/java/com/makersacademy/acebook/controller/HomeController.java index 2036ec7e0..559a31126 100644 --- a/src/main/java/com/makersacademy/acebook/controller/HomeController.java +++ b/src/main/java/com/makersacademy/acebook/controller/HomeController.java @@ -1,13 +1,16 @@ package com.makersacademy.acebook.controller; +import com.makersacademy.acebook.model.User; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.view.RedirectView; @Controller public class HomeController { + @RequestMapping(value = "/") - public RedirectView index() { - return new RedirectView("/posts"); + public String index(Model model) { + model.addAttribute("user", new User()); + return "home"; // Ensure this matches the name of your Thymeleaf template } } diff --git a/src/main/java/com/makersacademy/acebook/controller/PostsController.java b/src/main/java/com/makersacademy/acebook/controller/PostsController.java index 57a7e5f4d..9ab4e1053 100644 --- a/src/main/java/com/makersacademy/acebook/controller/PostsController.java +++ b/src/main/java/com/makersacademy/acebook/controller/PostsController.java @@ -5,10 +5,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.servlet.view.RedirectView; -import java.util.List; +import java.util.Date; + @Controller public class PostsController { @@ -26,7 +30,15 @@ public String index(Model model) { @PostMapping("/posts") public RedirectView create(@ModelAttribute Post post) { + post.setCreatedAt(new Date()); + repository.save(post); return new RedirectView("/posts"); } + + @PostMapping("/posts/{id}/delete") + public RedirectView delete(@PathVariable Long id){ + repository.deleteById(id); + return new RedirectView("/posts"); + } } diff --git a/src/main/java/com/makersacademy/acebook/controller/UsersController.java b/src/main/java/com/makersacademy/acebook/controller/UsersController.java index 3c46bf0a1..373a8f8c0 100644 --- a/src/main/java/com/makersacademy/acebook/controller/UsersController.java +++ b/src/main/java/com/makersacademy/acebook/controller/UsersController.java @@ -5,6 +5,8 @@ import com.makersacademy.acebook.repository.AuthoritiesRepository; import com.makersacademy.acebook.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -12,6 +14,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.servlet.view.RedirectView; + @Controller public class UsersController { @@ -20,7 +23,7 @@ public class UsersController { @Autowired AuthoritiesRepository authoritiesRepository; - @GetMapping("/users/new") + @GetMapping("/register") public String signup(Model model) { model.addAttribute("user", new User()); return "users/new"; @@ -33,4 +36,35 @@ public RedirectView signup(@ModelAttribute User user) { authoritiesRepository.save(authority); return new RedirectView("/login"); } + + @GetMapping("/profile") + public String showProfile(Model model) { + User currentUser = getCurrentUser(); + model.addAttribute("user", currentUser); + return "users/profile"; + } + + @PostMapping("/profile") + public RedirectView updateProfile(@ModelAttribute User user) { + User currentUser = getCurrentUser(); + currentUser.setMobileNumber(user.getMobileNumber()); + currentUser.setEmailAddress(user.getEmailAddress()); + currentUser.setGender(user.getGender()); + currentUser.setCountry(user.getCountry()); + currentUser.setLanguage(user.getLanguage()); + userRepository.save(currentUser); + return new RedirectView("/profile"); + } + + private User getCurrentUser() { + // Assuming you're using Spring Security to manage user authentication + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String username; + if (principal instanceof UserDetails) { + username = ((UserDetails) principal).getUsername(); + } else { + username = principal.toString(); + } + return userRepository.findByUsername(username); + } } diff --git a/src/main/java/com/makersacademy/acebook/model/Post.java b/src/main/java/com/makersacademy/acebook/model/Post.java index 0098de1b3..0a1ab56e6 100644 --- a/src/main/java/com/makersacademy/acebook/model/Post.java +++ b/src/main/java/com/makersacademy/acebook/model/Post.java @@ -1,29 +1,23 @@ package com.makersacademy.acebook.model; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.GenerationType; - import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import javax.persistence.*; +import java.util.Date; @Data +@NoArgsConstructor +@AllArgsConstructor @Entity @Table(name = "POSTS") public class Post { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String content; - - public Post() {} - - public Post(String content) { - this.content = content; - } - public String getContent() { return this.content; } - public void setContent(String content) { this.content = content; } + private String title; + private String content; + private Date createdAt; } diff --git a/src/main/java/com/makersacademy/acebook/model/User.java b/src/main/java/com/makersacademy/acebook/model/User.java index df2a0edf1..629b5ef4e 100644 --- a/src/main/java/com/makersacademy/acebook/model/User.java +++ b/src/main/java/com/makersacademy/acebook/model/User.java @@ -1,16 +1,14 @@ package com.makersacademy.acebook.model; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.GenerationType; - +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; -import static java.lang.Boolean.TRUE; +import javax.persistence.*; @Data +@NoArgsConstructor +@AllArgsConstructor @Entity @Table(name = "USERS") public class User { @@ -19,26 +17,10 @@ public class User { private Long id; private String username; private String password; - private boolean enabled; - - public User() { - this.enabled = TRUE; - } - - public User(String username, String password) { - this.username = username; - this.password = password; - this.enabled = TRUE; - } - - public User(String username, String password, boolean enabled) { - this.username = username; - this.password = password; - this.enabled = enabled; - } - - public String getUsername() { return this.username; } - public String getPassword() { return this.password; } - public void setUsername(String username) { this.username = username; } - public void setPassword(String password) { this.password = password; } + private boolean enabled = true; + private String mobileNumber; + private String emailAddress; + private String gender; + private String country; + private String language; } diff --git a/src/main/java/com/makersacademy/acebook/repository/UserRepository.java b/src/main/java/com/makersacademy/acebook/repository/UserRepository.java index 2cccc950f..ff99e732c 100644 --- a/src/main/java/com/makersacademy/acebook/repository/UserRepository.java +++ b/src/main/java/com/makersacademy/acebook/repository/UserRepository.java @@ -5,4 +5,5 @@ public interface UserRepository extends CrudRepository { + User findByUsername(String username); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c7ad09d6d..0d6b93698 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,4 +2,6 @@ spring.profiles.active=dev spring.data.rest.base-path=/api spring.datasource.platform=postgres spring.jpa.hibernate.ddl-auto=validate -logging.level.org.springframework.web: DEBUG +logging.level.org.springframework.web: DEBUG# +spring.datasource.username= +spring.datasource.password= diff --git a/src/main/resources/db/migration/V3__added_title_field_to_posts.sql b/src/main/resources/db/migration/V3__added_title_field_to_posts.sql new file mode 100644 index 000000000..5a01146b9 --- /dev/null +++ b/src/main/resources/db/migration/V3__added_title_field_to_posts.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts +ADD COLUMN title varchar(100); \ No newline at end of file diff --git a/src/main/resources/db/migration/V4__added_foreign_key_to_posts_table.sql b/src/main/resources/db/migration/V4__added_foreign_key_to_posts_table.sql new file mode 100644 index 000000000..4251ce7f2 --- /dev/null +++ b/src/main/resources/db/migration/V4__added_foreign_key_to_posts_table.sql @@ -0,0 +1,7 @@ +ALTER TABLE posts + ADD COLUMN user_id int, + ADD CONSTRAINT fk_user + FOREIGN KEY (user_id) + REFERENCES users(id) + ON DELETE CASCADE +; diff --git a/src/main/resources/db/migration/V5__fixed_user_id_type.sql b/src/main/resources/db/migration/V5__fixed_user_id_type.sql new file mode 100644 index 000000000..4acdf1387 --- /dev/null +++ b/src/main/resources/db/migration/V5__fixed_user_id_type.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts + ALTER COLUMN user_id TYPE bigint; diff --git a/src/main/resources/db/migration/V6__added_created_At_field_to_posts_table.sql b/src/main/resources/db/migration/V6__added_created_At_field_to_posts_table.sql new file mode 100644 index 000000000..6c14d178e --- /dev/null +++ b/src/main/resources/db/migration/V6__added_created_At_field_to_posts_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts +ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; \ No newline at end of file diff --git a/src/main/resources/db/migration/V7__alter_user_table.sql b/src/main/resources/db/migration/V7__alter_user_table.sql new file mode 100644 index 000000000..d13fb6fb8 --- /dev/null +++ b/src/main/resources/db/migration/V7__alter_user_table.sql @@ -0,0 +1,6 @@ +ALTER TABLE USERS +ADD COLUMN mobile_number VARCHAR(15), +ADD COLUMN email_address VARCHAR(255), +ADD COLUMN gender VARCHAR(10), +ADD COLUMN country VARCHAR(50), +ADD COLUMN language VARCHAR(50); diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css new file mode 100644 index 000000000..34b8bbbc8 --- /dev/null +++ b/src/main/resources/static/css/main.css @@ -0,0 +1,275 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap'); +:root { + --primaryColor: rgb(25,119,242); + --buttonColor: rgb(66,183,41); + --bodyColor: rgb(239,242,245); + --textColor: rgb(27,30,33); +} +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +body { + background-color: var(--bodyColor); + font-family: 'Roboto', sans-serif; + +} +a { + text-decoration: none; + color: var(--buttonColor); +} +button { + border: none; +} +a:hover { + text-decoration: underline; +} +input { + padding: 7px; + margin: 2% 0; + border: 1px solid lightgray; + border-radius: 5px; + width: 200px; +} +input::placeholder { + color: gray; + font-weight: lighter; + font-size: 10px; +} +.btn { + width: 100%; + border-radius: 3px; + color: #fff; + padding: 6px; +} +#container { + display: flex; + justify-content: center; + align-items: center; + margin-top: 10%; +} +.text__wrapper { + width: 37vw; + margin-right: 3%; + margin-top: 5%; +} +.heading { + position: relative; + top: -50%; + left: -7%; +} +.heading > img { + width: 350px; +} +.description { + color: var(--textColor); + font-weight: 900; + font-size: 30px; + float: left; +} +.form__wrapper { + width: 300px; + height: 250px; + margin-top: 5%; + background-color: #fff; + padding: 5% 2% 5% 2%; + border-radius: 5px; + line-height: 150%; + display: grid; + justify-content: center; + align-items: center; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} +.form { + margin-top: -18%; + margin-bottom: 7%; +} +.loginButton { + background-color: var(--primaryColor); + color: #fff; + margin-top: 2%; +} +.forgotPassword { + color: var(--primaryColor); + font-size: 10px; + text-align: center; +} +.registerButton { + background-color: var(--buttonColor); + color: #fff; + font-size: 10px; + width: 60%; + padding: 8px; + font-weight: bold; + margin: 0 auto; + margin-top: 3%; + margin-bottom: 3%; +} +/* Responsive Design */ + +/* Small Mobile Device */ +@media (max-width: 320px) { + #container { + display: grid; + margin-top: 10vh; + justify-content: center; + } + .text__wrapper { + width: 100%; + margin-right: 3%; + margin-top: -5%; + text-align: center; + margin: 0 auto; + } + .heading > img { + width: 300px; + position: relative; + left: 8%; + } + .description { + margin-bottom: 10%; + } + .form__wrapper { + margin: 0 auto; + height: 200px; + background-color: #fff; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + } + .form { + margin-top: -5%; + } + .loginButton { + background-color: var(--primaryColor); + color: #fff; + margin-top: 2%; + } + .forgotPassword { + color: var(--primaryColor); + font-size: 10px; + text-align: center; + } + .registerButton { + background-color: var(--buttonColor); + color: #fff; + font-size: 10px; + width: 60%; + padding: 8px; + font-weight: bold; + margin: 0 auto; + } +} + +/* Average Mobile Devices */ +@media (min-width: 320px) and (max-width: 480px) { + #container { + display: grid; + margin-top: 10vh; + justify-content: center; + } + .text__wrapper { + width: 100%; + margin-right: 3%; + margin-top: -5%; + text-align: center; + } + .heading > img { + width: 350px; + position: relative; + left: 8%; + } + .description { + margin-bottom: 5%; + } + .form__wrapper { + width: 220px; + height: 200px; + margin-left: 3%; + background-color: #fff; + padding: 5% 2% 5% 2%; + border-radius: 5px; + margin: 0 auto; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + } + .form { + margin-top: -8%; + } + .loginButton { + background-color: var(--primaryColor); + color: #fff; + margin-top: 2%; + } + .forgotPassword { + color: var(--primaryColor); + font-size: 10px; + text-align: center; + } + .registerButton { + background-color: var(--buttonColor); + color: #fff; + font-size: 10px; + width: 60%; + padding: 8px; + font-weight: bold; + margin: 0 auto; + margin-top: 3%; + margin-bottom: 3%; + } +} + +/* Tablet and IPads */ +@media (min-width: 481px) and (max-width: 768px) { + #container { + display: grid; + margin-top: 10vh; + justify-content: center; + } + .text__wrapper { + width: 100%; + margin-right: 3%; + margin-top: -5%; + text-align: center; + } + .heading > img { + width: 350px; + position: relative; + left: 8%; + } + .description { + margin-bottom: 5%; + } + .form__wrapper { + width: 220px; + height: 220px; + margin-left: 3%; + background-color: #fff; + padding: 5% 2% 5% 2%; + border-radius: 5px; + margin: 0 auto; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + } + .form { + margin-top: -8%; + } + .loginButton { + background-color: var(--primaryColor); + color: #fff; + margin-top: 2%; + } + .forgotPassword { + color: var(--primaryColor); + font-size: 10px; + text-align: center; + } + .registerButton { + background-color: var(--buttonColor); + color: #fff; + font-size: 10px; + width: 60%; + padding: 8px; + font-weight: bold; + margin: 0 auto; + margin-top: 3%; + margin-bottom: 3%; + } +} \ No newline at end of file diff --git a/src/main/resources/static/main.css b/src/main/resources/static/main.css deleted file mode 100644 index d0260873c..000000000 --- a/src/main/resources/static/main.css +++ /dev/null @@ -1,10 +0,0 @@ -.posts-main { - border-collapse: collapse; -} - -.post-main { - border: 1px solid #999; - padding: 0.5rem; - text-align: left; - margin-bottom: 0.5rem;; -} diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 000000000..b7382a8f8 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,36 @@ + + + + + + + Acebook - Login or Signup + + + + +
+
+
+ Acebook +
+

Acebook helps you connect and share with the people in your life.

+
+
+
+
+ +
+
+ +
+
+ +
+
+ Forgotten Password? + +
+
+ + diff --git a/src/main/resources/templates/posts/index.html b/src/main/resources/templates/posts/index.html index 4eb260155..d756a740c 100644 --- a/src/main/resources/templates/posts/index.html +++ b/src/main/resources/templates/posts/index.html @@ -1,27 +1,35 @@ - - - - Acebook - - - + + + + Acebook + + + -

Posts

+

Posts

-
- Signed in as -
+
+ Signed in as + Account +
-
-

Content:

-

-
+
+

Title:

+

Content:

+

+
- + - + diff --git a/src/main/resources/templates/users/profile.html b/src/main/resources/templates/users/profile.html new file mode 100644 index 000000000..f0387badf --- /dev/null +++ b/src/main/resources/templates/users/profile.html @@ -0,0 +1,173 @@ + + + + + Update Profile + + + + + + + +
+ +

Update Profile

+ +
+
+ + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + diff --git a/src/test/java/SignUpTest.java b/src/test/java/SignUpTest.java index b0e16955b..e16ca41dd 100644 --- a/src/test/java/SignUpTest.java +++ b/src/test/java/SignUpTest.java @@ -8,7 +8,6 @@ import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -21,7 +20,9 @@ public class SignUpTest { @Before public void setup() { - System.setProperty("webdriver.chrome.driver", "/usr/local/bin/chromedriver"); +// use environment variable to get chromedriver location + System.setProperty("webdriver.chrome.driver", "/opt/homebrew/bin/chromedriver"); +// System.setProperty("webdriver.chrome.driver", System.getenv("CHROME_DRIVER_LOCATION")); driver = new ChromeDriver(); faker = new Faker(); } @@ -33,7 +34,7 @@ public void tearDown() { @Test public void successfulSignUpRedirectsToSignIn() { - driver.get("http://localhost:8080/users/new"); + driver.get("http://localhost:8080/login"); driver.findElement(By.id("username")).sendKeys(faker.name().firstName()); driver.findElement(By.id("password")).sendKeys("password"); driver.findElement(By.id("submit")).click(); diff --git a/src/test/java/com/makersacademy/acebook/model/PostTest.java b/src/test/java/com/makersacademy/acebook/model/PostTest.java index 732aafc6e..276f764e4 100644 --- a/src/test/java/com/makersacademy/acebook/model/PostTest.java +++ b/src/test/java/com/makersacademy/acebook/model/PostTest.java @@ -2,16 +2,64 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import com.makersacademy.acebook.model.Post; +import com.makersacademy.acebook.repository.PostRepository; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import java.util.Optional; + +@RunWith(MockitoJUnitRunner.class) public class PostTest { + @Mock + PostRepository repository; + User user = new User(); - private Post post = new Post("hello"); + private Post post = new Post("hello", "Greetings!", user); @Test public void postHasContent() { + Post post = new Post("hello", "Greetings!", user); assertThat(post.getContent(), containsString("hello")); } + @Test + public void postIsDeleted() { + Post post = new Post("hello", "Greetings!", user); + + + repository.deleteById(1L); + + verify(repository).deleteById(1L); + } + + @Test + public void postisEdited() { + // Create a new post + Post post = new Post("hello", "Greetings!", user); + post.setId(1L); // Set the ID for the post + + // Mock the behavior of findById to return the post when called with ID 1L + when(repository.findById(1L)).thenReturn(Optional.of(post)); + + // Edit the post content and title + post.setContent("hello edited"); + post.setTitle("Greetings Edited!"); + + // Call the method to edit the post + repository.save(post); + + // Verify that the post is saved + verify(repository).save(post); + assertThat(post.getContent(), containsString("hello edited")); + + } } + + +