diff options
Diffstat (limited to 'src/main')
31 files changed, 0 insertions, 1908 deletions
diff --git a/src/main/java/com/blog/web/WebApplication.java b/src/main/java/com/blog/web/WebApplication.java deleted file mode 100644 index f5dd2ef..0000000 --- a/src/main/java/com/blog/web/WebApplication.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.blog.web; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class WebApplication { - public static void main(String[] args) { - SpringApplication.run(WebApplication.class, args); - } -} diff --git a/src/main/java/com/blog/web/controllers/ArticleController.java b/src/main/java/com/blog/web/controllers/ArticleController.java deleted file mode 100644 index 6cd5d50..0000000 --- a/src/main/java/com/blog/web/controllers/ArticleController.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.blog.web.controllers; - -import com.blog.web.dto.ArticleDto; -import com.blog.web.dto.ArticlePublicDto; -import com.blog.web.models.Article; -import com.blog.web.models.UserEntity; -import com.blog.web.services.ArticleService; -import com.blog.web.services.UserService; -import jakarta.validation.Valid; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.List; - -@RestController -@Controller -public class ArticleController { - private ArticleService articleService; - private UserService userService; - - public ArticleController(ArticleService articleService, UserService userService) { - this.articleService = articleService; - this.userService = userService; - } - - @GetMapping("/get") - public Article getMethod() { - return new Article( - 5, - "blah", - "blah", - "blah", - new UserEntity(), - LocalDateTime.now(), - LocalDateTime.now() - ); - } - - @GetMapping("/articles") - public HashSet<ArticlePublicDto> listArticles(Model model) { - HashSet<ArticlePublicDto> articles = new HashSet<ArticlePublicDto>(articleService.findAllArticles()); - //UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); - //model.addAttribute("user", user); - //model.addAttribute("articles", articles); - //return "index"; - return articles; - } - - @GetMapping("/articles/{articleId}") - public String showArticle(@PathVariable("articleId") long articleId, Model model) { - ArticleDto articleDto = articleService.findArticleById(articleId); - model.addAttribute("article", articleDto); - UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); - model.addAttribute("user", user); - return "articles/show"; - } - - @GetMapping("/articles/new") - public String createArticleForm(Model model) { - model.addAttribute("user", userService.getLoggedInUser().orElse(new UserEntity())); - model.addAttribute("article", new Article()); - return "articles/new"; - } - - @PostMapping("/articles/new") - public String saveArticle(@Valid @ModelAttribute("article") ArticleDto articleDto, BindingResult result, Model model) { - // if non-authenticated in user tries to create an article - // redirect them to login page - UserEntity user = userService.getLoggedInUser().orElse(null); - if (user == null) { - return "redirect:/userlogin"; - } else if (result.hasErrors()) { - model.addAttribute("article", articleDto); - return "articles/new"; - } else { - articleService.saveArticle(articleDto); - return "redirect:/articles"; - } - } - - @GetMapping("/articles/delete/{articleId}") - public String deleteArticle(@PathVariable("articleId") Long articleId) { - articleService.delete(articleId); - return "redirect:/articles"; - } - - @GetMapping("/articles/edit/{articleId}") - public String editArticleForm(@PathVariable("articleId") long articleId, Model model) { - UserEntity user = userService.getLoggedInUser().orElse(null); - if (user != null) { - model.addAttribute("user", user); - ArticleDto articleDto = articleService.findArticleById(articleId); - model.addAttribute("article", articleDto); - } - return "articles/edit"; - } - - @PostMapping("/articles/edit/{articleId}") - public String updateArticle(@PathVariable("articleId") Long articleId, @Valid @ModelAttribute("article") ArticleDto article, BindingResult result) { - if (result.hasErrors()) { - return "articles/edit"; - } - article.setId(articleId); - articleService.updateArticle(article); - return "redirect:/articles"; - } - - @GetMapping("/articles/search") - public String searchArticle(@RequestParam(value = "search") String search, Model model) { - UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); - model.addAttribute("user", user); - List<ArticleDto> articles = articleService.searchArticles(search); - model.addAttribute("articles", articles); - return "index"; - } - - @GetMapping("/") - public String getArticles() { - return "redirect:/articles"; - } -} diff --git a/src/main/java/com/blog/web/controllers/AuthController.java b/src/main/java/com/blog/web/controllers/AuthController.java deleted file mode 100644 index efb3672..0000000 --- a/src/main/java/com/blog/web/controllers/AuthController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.blog.web.controllers; - -import com.blog.web.dto.RegistrationDto; -import com.blog.web.models.UserEntity; -import com.blog.web.services.UserService; -import jakarta.validation.Valid; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; - -@Controller -public class AuthController { - private final UserService userService; - - public AuthController(UserService userService) { - this.userService = userService; - } - - @GetMapping("/userlogin") - public String login(Model model) { - final UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); - model.addAttribute("user", user); - return "auth/login"; - } - - @GetMapping("/register") - public String getRegisterForm(Model model) { - final RegistrationDto user = new RegistrationDto(); - model.addAttribute("user", user); - return "auth/register"; - } - - @PostMapping("/register/save") - public String register(@Valid @ModelAttribute("user") RegistrationDto user, BindingResult result, Model model) { - UserEntity existingUserEmail = userService.findByEmail(user.getEmail()).orElse(null); - if (existingUserEmail != null && StringUtils.isBlank(existingUserEmail.getEmail())) { - result.rejectValue("email", "There is already a user with this email"); - } - - UserEntity existingUsername = userService.findByUsername(user.getUsername()).orElse(null); - if (existingUsername != null && StringUtils.isBlank(existingUsername.getUsername())) { - result.rejectValue("username", "There is already a user with this username"); - } - - if (result.hasErrors()) { - model.addAttribute("user", user); - return "register"; - } - userService.saveUser(user); - return "redirect:/articles?success"; - } -} diff --git a/src/main/java/com/blog/web/dto/ArticleDto.java b/src/main/java/com/blog/web/dto/ArticleDto.java deleted file mode 100644 index d275f3b..0000000 --- a/src/main/java/com/blog/web/dto/ArticleDto.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.blog.web.dto; - -import com.blog.web.models.Article; -import com.blog.web.models.UserEntity; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotEmpty; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; -import org.hibernate.validator.constraints.URL; - -import java.time.LocalDateTime; - -public class ArticleDto { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - @NotEmpty(message = "Article title should not be empty") - private String title; - @NotEmpty(message = "Article Photo URL should not be empty") - @URL(message = "Article Photo URL should be a URL") - private String photoUrl; - @NotEmpty(message = "Article Content should not be empty") - private String content; - @CreationTimestamp - private LocalDateTime createdOn; - @UpdateTimestamp - private LocalDateTime updatedOn; - @ManyToOne - @JoinColumn(name = "created_by", nullable = false) - private UserEntity createdBy; - - public ArticleDto(long id, String title, String photoUrl, String content, UserEntity createdBy, LocalDateTime createdOn, LocalDateTime updatedOn) { - this.id = id; - this.title = title; - this.photoUrl = photoUrl; - this.content = content; - this.createdBy = createdBy; - this.createdOn = createdOn; - this.updatedOn = updatedOn; - } - - public ArticleDto() { - } - - ; - - public ArticleDto(Article article) { - this.id = article.getId(); - this.title = article.getTitle(); - this.photoUrl = article.getPhotoUrl(); - this.content = article.getContent(); - this.createdBy = article.getCreatedBy(); - this.createdOn = article.getCreatedOn(); - this.updatedOn = article.getUpdatedOn(); - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public @NotEmpty(message = "Article title should not be empty") String getTitle() { - return title; - } - - public void setTitle(@NotEmpty(message = "Article title should not be empty") String title) { - this.title = title; - } - - public @NotEmpty(message = "Article Photo URL should not be empty") @URL(message = "Article Photo URL should be a URL") String getPhotoUrl() { - return photoUrl; - } - - public void setPhotoUrl(@NotEmpty(message = "Article Photo URL should not be empty") @URL(message = "Article Photo URL should be a URL") String photoUrl) { - this.photoUrl = photoUrl; - } - - public @NotEmpty(message = "Article Content should not be empty") String getContent() { - return content; - } - - public void setContent(@NotEmpty(message = "Article Content should not be empty") String content) { - this.content = content; - } - - public LocalDateTime getCreatedOn() { - return createdOn; - } - - public void setCreatedOn(LocalDateTime createdOn) { - this.createdOn = createdOn; - } - - public LocalDateTime getUpdatedOn() { - return updatedOn; - } - - public void setUpdatedOn(LocalDateTime updatedOn) { - this.updatedOn = updatedOn; - } - - public UserEntity getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(UserEntity createdBy) { - this.createdBy = createdBy; - } - - public String getUsername() { return createdBy.getUsername(); } -} diff --git a/src/main/java/com/blog/web/dto/ArticlePublicDto.java b/src/main/java/com/blog/web/dto/ArticlePublicDto.java deleted file mode 100644 index 3ced6d2..0000000 --- a/src/main/java/com/blog/web/dto/ArticlePublicDto.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.blog.web.dto; - -import com.blog.web.models.Article; - -import java.time.LocalDateTime; - -public class ArticlePublicDto { - private Long id; - private String title; - private String photoUrl; - private String content; - private LocalDateTime createdOn; - private LocalDateTime updatedOn; - private String createdBy; - - public ArticlePublicDto(long id, String title, String photoUrl, String content, String createdBy, LocalDateTime createdOn, LocalDateTime updatedOn) { - this.id = id; - this.title = title; - this.photoUrl = photoUrl; - this.content = content; - this.createdBy = createdBy; - this.createdOn = createdOn; - this.updatedOn = updatedOn; - } - - public ArticlePublicDto() { - } - - ; - - public ArticlePublicDto(Article article) { - this.id = article.getId(); - this.title = article.getTitle(); - this.photoUrl = article.getPhotoUrl(); - this.content = article.getContent(); - this.createdBy = article.getCreatedBy().getUsername(); - this.createdOn = article.getCreatedOn(); - this.updatedOn = article.getUpdatedOn(); - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle( String title) { - this.title = title; - } - - public String getPhotoUrl() { - return photoUrl; - } - - public void setPhotoUrl(String photoUrl) { - this.photoUrl = photoUrl; - } - - public String getContent() { - return content; - } - - public void setContent( String content) { - this.content = content; - } - - public LocalDateTime getCreatedOn() { - return createdOn; - } - - public void setCreatedOn(LocalDateTime createdOn) { - this.createdOn = createdOn; - } - - public LocalDateTime getUpdatedOn() { - return updatedOn; - } - - public void setUpdatedOn(LocalDateTime updatedOn) { - this.updatedOn = updatedOn; - } - - public String getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(String createdBy) { - this.createdBy = createdBy; - } -} diff --git a/src/main/java/com/blog/web/dto/RegistrationDto.java b/src/main/java/com/blog/web/dto/RegistrationDto.java deleted file mode 100644 index 5b86557..0000000 --- a/src/main/java/com/blog/web/dto/RegistrationDto.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.blog.web.dto; - -import jakarta.validation.constraints.NotEmpty; - -public class RegistrationDto { - private Long id; - @NotEmpty - private String username; - @NotEmpty - private String email; - - @NotEmpty - private String password; - - public RegistrationDto() { - } - - public RegistrationDto(String username, String email, String password) { - this.username = username; - this.email = email; - this.password = password; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public @NotEmpty String getUsername() { - return username; - } - - public void setUsername(@NotEmpty String username) { - this.username = username; - } - - public @NotEmpty String getEmail() { - return email; - } - - public void setEmail(@NotEmpty String email) { - this.email = email; - } - - public void setPassword(@NotEmpty String password) { - this.password = password; - } - - public @NotEmpty String getPassword() { - return password; - } -} diff --git a/src/main/java/com/blog/web/mappers/ArticleMapper.java b/src/main/java/com/blog/web/mappers/ArticleMapper.java deleted file mode 100644 index 8fe729e..0000000 --- a/src/main/java/com/blog/web/mappers/ArticleMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.blog.web.mappers; - -import com.blog.web.dto.ArticleDto; -import com.blog.web.dto.ArticlePublicDto; -import com.blog.web.models.Article; - -public class ArticleMapper { - private ArticleMapper() { - } - - public static Article mapToArticle(ArticleDto articleDto) { - return new Article(articleDto); - } - - public static ArticleDto mapToArticleDto(Article article) { - return new ArticleDto(article); - } - - public static ArticlePublicDto mapToArticlePublicDto(Article article) { - return new ArticlePublicDto(article); - } -} diff --git a/src/main/java/com/blog/web/models/Article.java b/src/main/java/com/blog/web/models/Article.java deleted file mode 100644 index b54907a..0000000 --- a/src/main/java/com/blog/web/models/Article.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.blog.web.models; - -import com.blog.web.dto.ArticleDto; -import jakarta.persistence.*; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; - -import java.time.LocalDateTime; - -@Entity -public class Article { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - private String title; - private String photoUrl; - private String content; - @CreationTimestamp - private LocalDateTime createdOn; - @UpdateTimestamp - private LocalDateTime updatedOn; - @ManyToOne - @JoinColumn(name = "created_by", nullable = false) - private UserEntity createdBy; - - public Article(long id, String title, String photoUrl, String content, UserEntity createdBy, LocalDateTime createdOn, LocalDateTime updatedOn) { - this.id = id; - this.title = title; - this.photoUrl = photoUrl; - this.content = content; - this.createdBy = createdBy; - this.createdOn = createdOn; - this.updatedOn = updatedOn; - } - - public Article() { - } - - public Article(ArticleDto articleDto) { - this.id = articleDto.getId(); - this.title = articleDto.getTitle(); - this.photoUrl = articleDto.getPhotoUrl(); - this.content = articleDto.getContent(); - this.createdBy = articleDto.getCreatedBy(); - this.createdOn = articleDto.getCreatedOn(); - this.updatedOn = articleDto.getUpdatedOn(); - } - - public long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getPhotoUrl() { - return photoUrl; - } - - public void setPhotoUrl(String photoUrl) { - this.photoUrl = photoUrl; - } - - public LocalDateTime getCreatedOn() { - return createdOn; - } - - public void setCreatedOn(LocalDateTime createdOn) { - this.createdOn = createdOn; - } - - public LocalDateTime getUpdatedOn() { - return updatedOn; - } - - public void setUpdatedOn(LocalDateTime updatedOn) { - this.updatedOn = updatedOn; - } - - public UserEntity getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(UserEntity createdBy) { - this.createdBy = createdBy; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } -} diff --git a/src/main/java/com/blog/web/models/Role.java b/src/main/java/com/blog/web/models/Role.java deleted file mode 100644 index 2b7143f..0000000 --- a/src/main/java/com/blog/web/models/Role.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.blog.web.models; - -import jakarta.persistence.*; - -import java.util.ArrayList; -import java.util.List; - -@Entity(name = "roles") -public class Role { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - private String name; - @ManyToMany(mappedBy = "roles") - private List<UserEntity> users = new ArrayList<>(); - - public long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List<UserEntity> getUsers() { - return users; - } -} diff --git a/src/main/java/com/blog/web/models/UserEntity.java b/src/main/java/com/blog/web/models/UserEntity.java deleted file mode 100644 index bf45b21..0000000 --- a/src/main/java/com/blog/web/models/UserEntity.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.blog.web.models; - -import jakarta.persistence.*; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -@Entity(name = "users") -// Named UserEntity to prevent conflicts with Java User object -public class UserEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - private String username; - private String email; - private String password; - @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) - @JoinTable(name = "user_roles", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")}) - private final List<Role> roles = new ArrayList<>(); - - public boolean equals(UserEntity user) { - return this.id == user.getId(); - } - - public User toSecurityUser() { - return new User(this.getEmail(), this.getPassword(), this.getRoles().stream().map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList())); - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public List<Role> getRoles() { - return roles; - } - - public void setRoles(List<Role> roles) { - this.roles.clear(); - this.roles.addAll(roles); - } -} diff --git a/src/main/java/com/blog/web/repository/ArticleRepository.java b/src/main/java/com/blog/web/repository/ArticleRepository.java deleted file mode 100644 index 594cb15..0000000 --- a/src/main/java/com/blog/web/repository/ArticleRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.blog.web.repository; - -import com.blog.web.models.Article; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import java.util.List; - -public interface ArticleRepository extends JpaRepository<Article, Long> { - @Query("SELECT a from Article a WHERE a.title LIKE CONCAT('%', :search, '%')") - List<Article> searchArticles(String search); -} diff --git a/src/main/java/com/blog/web/repository/RoleRepository.java b/src/main/java/com/blog/web/repository/RoleRepository.java deleted file mode 100644 index f271bf1..0000000 --- a/src/main/java/com/blog/web/repository/RoleRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.blog.web.repository; - -import com.blog.web.models.Role; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface RoleRepository extends JpaRepository<Role, Long> { - Optional<Role> findByName(String name); -} diff --git a/src/main/java/com/blog/web/repository/UserRepository.java b/src/main/java/com/blog/web/repository/UserRepository.java deleted file mode 100644 index 30eefc5..0000000 --- a/src/main/java/com/blog/web/repository/UserRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.blog.web.repository; - -import com.blog.web.models.UserEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface UserRepository extends JpaRepository<UserEntity, Long> { - Optional<UserEntity> findByEmail(String email); - - Optional<UserEntity> findByUsername(String username); - - Optional<UserEntity> findFirstByUsername(String username); -} diff --git a/src/main/java/com/blog/web/security/CustomUserDetailsService.java b/src/main/java/com/blog/web/security/CustomUserDetailsService.java deleted file mode 100644 index ee3e950..0000000 --- a/src/main/java/com/blog/web/security/CustomUserDetailsService.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.blog.web.security; - -import com.blog.web.models.UserEntity; -import com.blog.web.repository.UserRepository; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import java.util.stream.Collectors; - -@Service -public class CustomUserDetailsService implements UserDetailsService { - private final UserRepository userRepository; - - public CustomUserDetailsService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - UserEntity userEntity = userRepository.findFirstByUsername(username).orElse(new UserEntity()); - if (userEntity.getUsername() != null) { - return userEntity.toSecurityUser(); - } else { - throw new UsernameNotFoundException("Invalid username"); - } - } -} diff --git a/src/main/java/com/blog/web/security/SecurityConfig.java b/src/main/java/com/blog/web/security/SecurityConfig.java deleted file mode 100644 index 17e09c7..0000000 --- a/src/main/java/com/blog/web/security/SecurityConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.blog.web.security; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; - -@Configuration -@EnableWebSecurity -@EnableMethodSecurity(securedEnabled = true) -public class SecurityConfig { - private CustomUserDetailsService userDetailsService; - - public SecurityConfig(CustomUserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; - } - - @Bean - public static PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // disabling csrf leaves us vulnerable, in a real production app do not do this - http.csrf(c -> c.disable()).cors(c -> c.disable()).authorizeHttpRequests(auths -> auths.anyRequest().permitAll()).formLogin(form -> form.loginPage("/userlogin").usernameParameter("username").passwordParameter("password").defaultSuccessUrl("/articles").loginProcessingUrl("/userlogin").failureUrl("/userlogin?error=true").permitAll()).logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/articles")); - return http.build(); - } - - public void configure(AuthenticationManagerBuilder builder) throws Exception { - builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); - } -} diff --git a/src/main/java/com/blog/web/security/SecurityUtil.java b/src/main/java/com/blog/web/security/SecurityUtil.java deleted file mode 100644 index ef0b3d9..0000000 --- a/src/main/java/com/blog/web/security/SecurityUtil.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.blog.web.security; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -public class SecurityUtil { - public static String getSessionUser() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return !(authentication instanceof AnonymousAuthenticationToken) ? authentication.getName() : null; - } -} diff --git a/src/main/java/com/blog/web/services/ArticleService.java b/src/main/java/com/blog/web/services/ArticleService.java deleted file mode 100644 index 1bfe38f..0000000 --- a/src/main/java/com/blog/web/services/ArticleService.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.blog.web.services; - -import com.blog.web.dto.ArticleDto; -import com.blog.web.dto.ArticlePublicDto; -import com.blog.web.models.Article; - -import java.util.List; -import java.util.Optional; - -public interface ArticleService { - List<ArticlePublicDto> findAllArticles(); - - Optional<Article> saveArticle(ArticleDto article); - - ArticleDto findArticleById(long articleId); - - void updateArticle(ArticleDto articleDto); - - boolean delete(Long articleId); - - List<ArticleDto> searchArticles(String search); -} diff --git a/src/main/java/com/blog/web/services/UserService.java b/src/main/java/com/blog/web/services/UserService.java deleted file mode 100644 index b9c231b..0000000 --- a/src/main/java/com/blog/web/services/UserService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.blog.web.services; - -import com.blog.web.dto.RegistrationDto; -import com.blog.web.models.UserEntity; - -import java.util.Optional; - -public interface UserService { - void saveUser(RegistrationDto registrationDto); - - Optional<UserEntity> findByEmail(String email); - - Optional<UserEntity> findByUsername(String username); - - public Optional<UserEntity> getLoggedInUser(); -} diff --git a/src/main/java/com/blog/web/services/impl/ArticleServiceImpl.java b/src/main/java/com/blog/web/services/impl/ArticleServiceImpl.java deleted file mode 100644 index 7073073..0000000 --- a/src/main/java/com/blog/web/services/impl/ArticleServiceImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.blog.web.services.impl; - -import com.blog.web.dto.ArticleDto; -import com.blog.web.dto.ArticlePublicDto; -import com.blog.web.models.Article; -import com.blog.web.models.UserEntity; -import com.blog.web.repository.ArticleRepository; -import com.blog.web.repository.UserRepository; -import com.blog.web.security.SecurityUtil; -import com.blog.web.services.ArticleService; -import com.blog.web.services.UserService; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import com.blog.web.mappers.ArticleMapper; - -import static com.blog.web.mappers.ArticleMapper.mapToArticle; -import static com.blog.web.mappers.ArticleMapper.mapToArticleDto; - -@Service -public class ArticleServiceImpl implements ArticleService { - final private ArticleRepository articleRepository; - final private UserRepository userRepository; - final private UserService userService; - - public ArticleServiceImpl(ArticleRepository articleRepository, UserRepository userRepository, UserService userService) { - this.userRepository = userRepository; - this.articleRepository = articleRepository; - this.userService = userService; - } - - - @Override - public List<ArticlePublicDto> findAllArticles() { - List<Article> articles = articleRepository.findAll(); - return articles.stream().map(ArticleMapper::mapToArticlePublicDto).collect(Collectors.toList()); - } - - @Override - public Optional<Article> saveArticle(ArticleDto articleDto) { - String username = SecurityUtil.getSessionUser(); - UserEntity user = userRepository.findByUsername(username).orElse(null); - if (user == null) { - return null; - } - Article article = mapToArticle(articleDto); - article.setCreatedBy(user); - return Optional.of(articleRepository.save(article)); - } - - @Override - public ArticleDto findArticleById(long articleId) { - Article article = articleRepository.findById(articleId).get(); - return mapToArticleDto(article); - } - - @Override - public void updateArticle(ArticleDto articleDto) { - String username = SecurityUtil.getSessionUser(); - UserEntity user = userRepository.findByUsername(username).orElse(null); - if (user == null) { - return; - } - Article article = mapToArticle(articleDto); - article.setCreatedBy(user); - articleRepository.save(article); - } - - @Override - public boolean delete(Long articleId) { - final UserEntity user = userService.getLoggedInUser().orElse(null); - if (user == null) { - return false; - } - String userId = user.getUsername(); - ArticleDto article = this.findArticleById(articleId); - String ownerId = article.getUsername(); - if (ownerId.equals(userId)) { - articleRepository.deleteById(articleId); - return true; - } else { - return false; - } - } - - @Override - public List<ArticleDto> searchArticles(String search) { - List<Article> articles = articleRepository.searchArticles(search); - return articles.stream().map(article -> mapToArticleDto(article)).collect(Collectors.toList()); - } -} diff --git a/src/main/java/com/blog/web/services/impl/UserServiceImpl.java b/src/main/java/com/blog/web/services/impl/UserServiceImpl.java deleted file mode 100644 index 859e72c..0000000 --- a/src/main/java/com/blog/web/services/impl/UserServiceImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.blog.web.services.impl; - -import com.blog.web.dto.RegistrationDto; -import com.blog.web.models.Role; -import com.blog.web.models.UserEntity; -import com.blog.web.repository.RoleRepository; -import com.blog.web.repository.UserRepository; -import com.blog.web.security.SecurityUtil; -import com.blog.web.services.UserService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -import java.util.Arrays; -import java.util.Optional; - -@Service -public class UserServiceImpl implements UserService { - private final UserRepository userRepository; - private final RoleRepository roleRepository; - private final PasswordEncoder passwordEncoder; - - public UserServiceImpl(UserRepository userRepository, RoleRepository roleRepository, PasswordEncoder passwordEncoder) { - this.userRepository = userRepository; - this.roleRepository = roleRepository; - this.passwordEncoder = passwordEncoder; - } - - @Override - public void saveUser(RegistrationDto registrationDto) { - UserEntity user = new UserEntity(); - user.setUsername(registrationDto.getUsername()); - user.setEmail(registrationDto.getEmail()); - user.setPassword(passwordEncoder.encode(registrationDto.getPassword())); - - final Role role = roleRepository.findByName("User").orElse(new Role()); - user.setRoles(Arrays.asList(role)); - userRepository.save(user); - } - - @Override - public Optional<UserEntity> findByEmail(String email) { - return userRepository.findByEmail(email); - } - - @Override - public Optional<UserEntity> findByUsername(String username) { - return userRepository.findByUsername(username); - } - - public Optional<UserEntity> getLoggedInUser() { - final Optional<UserEntity> user; - String username = SecurityUtil.getSessionUser(); - if (username != null) { - user = this.findByUsername(username); - } else { - user = Optional.of(new UserEntity()); - } - return user; - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 102e2c2..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,10 +0,0 @@ -spring.application.name=web -spring.datasource.url=jdbc:postgresql://localhost:5432/spring -spring.datasource.username=tradam -spring.datasource.driver-class-name=org.postgresql.Driver - -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true - -spring.security.user.name=zxcv -spring.security.user.password=zxcv diff --git a/src/main/resources/static/abstract-polygonal-banner-background-vector.jpg b/src/main/resources/static/abstract-polygonal-banner-background-vector.jpg Binary files differdeleted file mode 100644 index 1c6746e..0000000 --- a/src/main/resources/static/abstract-polygonal-banner-background-vector.jpg +++ /dev/null diff --git a/src/main/resources/templates/articles-list.html b/src/main/resources/templates/articles-list.html deleted file mode 100644 index 799c751..0000000 --- a/src/main/resources/templates/articles-list.html +++ /dev/null @@ -1,16 +0,0 @@ -<!DOCTYPE html> -<html lang="en" xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Title</title> -</head> -<body> - <h1>blah blah</h1> - <div th:each="article :${articles}" style=""> - <div>my aga article</div> - <h2 th:text="${article.title}"></h2> - <img th:src="${article.photoUrl}" alt="..."/> - <p th:text="${article.content}"></p> - </div> -</body> -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/articles/edit.html b/src/main/resources/templates/articles/edit.html deleted file mode 100644 index f4faee9..0000000 --- a/src/main/resources/templates/articles/edit.html +++ /dev/null @@ -1,63 +0,0 @@ -<!DOCTYPE html> -<html lang="en" - xmlns:th="http://www.thymeleaf.org" - xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" - layout:decorate="~{layout}" -> -<body layout:fragment="content"> - -<div class="flex justify-center bg-white p-12"> - <form th:action="@{/articles/edit/{articleId}(articleId=${article.id})}" th:object="${article}" method="post" class="w-full max-w-lg"> - <input type="hidden" th:field="*{id}"> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="title"> - Title - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" - id="title" - type="text" - name="title" - th:field="*{title}" - placeholder="Yep"> - <p th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - <div class="w-full md:w-1/2 px-3"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="photoUrl"> - Photo URL - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" - id="photoUrl" - type="text" - name="photoUrl" - th:field="*{photoUrl}" - placeholder="Doe"> - <p th:if="${#fields.hasErrors('photoUrl')}" th:errors="*{photoUrl}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - </div> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full px-3"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="content"> - Content - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" - id="content" - type="text" - name="content" - th:field="*{content}" - placeholder="Doe"> - <p th:if="${#fields.hasErrors('content')}" th:errors="*{content}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - </div> - <div class="flex flex-wrap mb-2"> - </div> - <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Update</button> - - </form> -</div> - -</body> -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/articles/new.html b/src/main/resources/templates/articles/new.html deleted file mode 100644 index f5924d6..0000000 --- a/src/main/resources/templates/articles/new.html +++ /dev/null @@ -1,62 +0,0 @@ -<!DOCTYPE html> -<html lang="en" - xmlns:th="http://www.thymeleaf.org" - xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" - layout:decorate="~{layout}" -> -<body layout:fragment="content"> - -<div class="flex justify-center bg-white p-12"> -<form th:action="@{/articles/new}" th:object="${article}" method="post" class="w-full max-w-lg"> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="title"> - Title - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-red-500 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" - id="title" - type="text" - name="title" - th:field="*{title}" - placeholder="Yep"> - <p th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - <div class="w-full md:w-1/2 px-3"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="photoUrl"> - Photo URL - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" - id="photoUrl" - type="text" - name="photoUrl" - th:field="*{photoUrl}" - placeholder="Doe"> - <p th:if="${#fields.hasErrors('photoUrl')}" th:errors="*{photoUrl}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - </div> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full px-3"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="content"> - Content - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" - id="content" - type="text" - name="content" - th:field="*{content}" - placeholder="Doe"> - <p th:if="${#fields.hasErrors('content')}" th:errors="*{content}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - </div> - <div class="flex flex-wrap mb-2"> - </div> - <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Create</button> - -</form> -</div> - -</body> -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/articles/show.html b/src/main/resources/templates/articles/show.html deleted file mode 100644 index 206aaae..0000000 --- a/src/main/resources/templates/articles/show.html +++ /dev/null @@ -1,87 +0,0 @@ -<!DOCTYPE html> -<html lang="en" - xmlns:th="http://www.thymeleaf.org" - xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" - layout:decorate="~{layout}" -> -<body layout:fragment="content"> - - - -<!--Title--> -<div class="text-center pt-16 md:pt-32"> - <p th:text="${article.createdOn}" class="text-sm md:text-base text-green-500 font-bold"></p> - <h1 th:text="${article.title}" class="font-bold break-normal text-3xl md:text-5xl">Welcome to Ghostwind CSS</h1> -</div> - -<!--image--> -<div class="container w-full max-w-6xl mx-auto bg-white bg-cover mt-8 rounded" th:style="'background:url(' + @{${article.photoUrl}} + '); height: 75vh;'"></div> - -<!--Container--> -<div class="container max-w-5xl mx-auto -mt-32"> - - <div class="mx-0 sm:mx-6"> - - <div class="bg-white w-full p-8 md:p-24 text-xl md:text-2xl text-gray-800 leading-normal" style="font-family:Georgia,serif;"> - - <!--Post Content--> - - <div th:text="${article.content}"></div> - - <!--/ Post Content--> - - </div> - - - <!--Subscribe--> - <div class="container font-sans bg-green-100 rounded mt-8 p-4 md:p-24 text-center"> - <h2 class="font-bold break-normal text-2xl md:text-4xl">Subscribe to Ghostwind CSS</h2> - <h3 class="font-bold break-normal font-normal text-gray-600 text-base md:text-xl">Get the latest posts delivered right to your inbox</h3> - <div class="w-full text-center pt-4"> - <form action="#"> - <div class="max-w-sm mx-auto p-1 pr-0 flex flex-wrap items-center"> - <input type="email" placeholder="[email protected]" class="flex-1 appearance-none rounded shadow p-3 text-gray-600 mr-2 focus:outline-none"> - <button type="submit" class="flex-1 mt-4 md:mt-0 block md:inline-block appearance-none bg-green-500 text-white text-base font-semibold tracking-wider uppercase py-4 rounded shadow hover:bg-green-400">Subscribe</button> - </div> - </form> - </div> - </div> - <!-- /Subscribe--> - - - <!--Author--> - <div class="flex w-full items-center font-sans p-8 md:p-24"> - <img class="w-10 h-10 rounded-full mr-4" src="http://i.pravatar.cc/300" alt="Avatar of Author"> - <div class="flex-1"> - <p class="text-base font-bold text-base md:text-xl leading-none">Ghostwind CSS</p> - <p class="text-gray-600 text-xs md:text-base">Tailwind CSS version of Ghost's Casper theme by <a class="text-gray-800 hover:text-green-500 no-underline border-b-2 border-green-500" href="https://www.tailwindtoolbox.com">TailwindToolbox.com</a></p> - </div> - <div class="justify-end"> - - </div> - <!--/Author--> - - </div> - - - </div> - -</div> - - -<!-- Scroll Top Button --> -<button class="btn-toggle-round scroll-top js-scroll-top" type="button" title="Scroll to top"> - <svg class="progress-circle" width="100%" height="100%" viewBox="-1 -1 102 102"> - <path d="M50,1 a49,49 0 0,1 0,98 a49,49 0 0,1 0,-98"/> - </svg> - <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="cuurentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> - <path stroke="none" d="M0 0h24v24H0z" fill="none"/> - <line x1="12" y1="5" x2="12" y2="19" /> - <line x1="18" y1="11" x2="12" y2="5" /> - <line x1="6" y1="11" x2="12" y2="5" /> - </svg> -</button> - - -</body> -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/auth/login.html b/src/main/resources/templates/auth/login.html deleted file mode 100644 index 47bc63e..0000000 --- a/src/main/resources/templates/auth/login.html +++ /dev/null @@ -1,48 +0,0 @@ -<!DOCTYPE html> -<html lang="en" - xmlns:th="http://www.thymeleaf.org" - xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" - layout:decorate="~{layout}" -> -<body layout:fragment="content"> - -<div th:if="${param.error}" class="text-xl p-4 bg-black text-red-500">Invalid Username/Password</div> -<div th:if="${param.logout}" class="text-xl p-4 bg-black text-red-500">You have been logged out</div> - -<div class="flex h-full justify-center bg-white p-12"> - <form th:action="@{/userlogin}" method="post" class="w-full max-w-lg"> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="username"> - Title - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" - id="username" - type="text" - name="username" - placeholder="Ted"> - </div> - </div> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full px-3"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="password"> - Password - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" - id="password" - type="password" - name="password" - placeholder="Doe"> - </div> - </div> - <div class="flex flex-wrap mb-2"> - </div> - <button type="submit" value="Log in" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Log In</button> - - </form> -</div> - -</body> -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/auth/register.html b/src/main/resources/templates/auth/register.html deleted file mode 100644 index dc57ca4..0000000 --- a/src/main/resources/templates/auth/register.html +++ /dev/null @@ -1,63 +0,0 @@ -<!DOCTYPE html> -<html lang="en" - xmlns:th="http://www.thymeleaf.org" - xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" - layout:decorate="~{layout}" -> -<body layout:fragment="content"> - -<div class="flex justify-center bg-white p-12"> - <div th:if="${param.fail}" class="text-xl p-4 bg-black text-red-500">Username or Email already exists</div> - <form th:action="@{/register/save}" th:object="${user}" role="form" method="post" class="w-full max-w-lg"> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="username"> - Title - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" - id="username" - type="text" - name="username" - th:field="*{username}" - placeholder="Ted"> - <p th:if="${#fields.hasErrors('username')}" th:errors="*{username}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - <div class="w-full md:w-1/2 px-3"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="email"> - Email - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" - id="email" - type="text" - name="email" - th:field="*{email}" - placeholder="[email protected]"> - <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - </div> - <div class="flex flex-wrap -mx-3 mb-6"> - <div class="w-full px-3"> - <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" - for="password"> - Password - </label> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" - id="password" - type="password" - name="password" - th:field="*{password}" - placeholder="Doe"> - <p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="text-red-500 text-xs italic">Please fill out this field.</p> - </div> - </div> - <div class="flex flex-wrap mb-2"> - </div> - <button th:href="@{/register}" type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Register</button> - - </form> -</div> - -</body> -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html deleted file mode 100644 index 0c42c21..0000000 --- a/src/main/resources/templates/index.html +++ /dev/null @@ -1,146 +0,0 @@ - -<!DOCTYPE html> -<html lang="en" - xmlns:th="http://www.thymeleaf.org" - xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" - layout:decorate="~{layout}" -> -<body layout:fragment="content"> - -<!--Container--> -<div class="container px-4 md:px-0 max-w-6xl mx-auto -mt-32"> - - <div class="mx-0 sm:mx-6"> - - <!--Nav--> - <nav class="mt-0 w-full"> - <div class="container mx-auto flex items-center"> - - <div class="flex w-1/2 pl-4 text-sm"> - <ul class="list-reset flex justify-between flex-1 md:flex-none items-center"> - <li class="mr-2"> - <a class="inline-block py-2 px-2 text-white no-underline hover:underline" href="post.html">POST</a> - </li> - <li class="mr-2"> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-2" href="multimenu post.html">MULTIMENU POST</a> - </li> - <li class="mr-2"> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-2" href="#">LINK</a> - </li> - <li class="mr-2"> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-2" href="post_vue.html">POST_VUE</a> - </li> - </ul> - </div> - - - <div class="flex w-1/2 justify-end content-center"> - <a class="inline-block text-gray-500 no-underline hover:text-white hover:text-underline text-center h-10 p-2 md:h-auto md:p-4 avatar" data-tippy-content="@twitter_handle" href="https://twitter.com/intent/tweet?url=#"> - <svg class="fill-current h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M30.063 7.313c-.813 1.125-1.75 2.125-2.875 2.938v.75c0 1.563-.188 3.125-.688 4.625a15.088 15.088 0 0 1-2.063 4.438c-.875 1.438-2 2.688-3.25 3.813a15.015 15.015 0 0 1-4.625 2.563c-1.813.688-3.75 1-5.75 1-3.25 0-6.188-.875-8.875-2.625.438.063.875.125 1.375.125 2.688 0 5.063-.875 7.188-2.5-1.25 0-2.375-.375-3.375-1.125s-1.688-1.688-2.063-2.875c.438.063.813.125 1.125.125.5 0 1-.063 1.5-.25-1.313-.25-2.438-.938-3.313-1.938a5.673 5.673 0 0 1-1.313-3.688v-.063c.813.438 1.688.688 2.625.688a5.228 5.228 0 0 1-1.875-2c-.5-.875-.688-1.813-.688-2.75 0-1.063.25-2.063.75-2.938 1.438 1.75 3.188 3.188 5.25 4.25s4.313 1.688 6.688 1.813a5.579 5.579 0 0 1 1.5-5.438c1.125-1.125 2.5-1.688 4.125-1.688s3.063.625 4.188 1.813a11.48 11.48 0 0 0 3.688-1.375c-.438 1.375-1.313 2.438-2.563 3.188 1.125-.125 2.188-.438 3.313-.875z"></path></svg> - </a> - <a class="inline-block text-gray-500 no-underline hover:text-white hover:text-underline text-center h-10 p-2 md:h-auto md:p-4 avatar" data-tippy-content="#facebook_id" href="https://www.facebook.com/sharer/sharer.php?u=#"> - <svg class="fill-current h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M19 6h5V0h-5c-3.86 0-7 3.14-7 7v3H8v6h4v16h6V16h5l1-6h-6V7c0-.542.458-1 1-1z"></path></svg> - </a> - </div> - - </div> - </nav> - - <div class="bg-gray-200 w-full text-xl md:text-2xl text-gray-800 leading-normal rounded-t"> - - <!--Lead Card--> - <div class="flex h-full bg-contain rounded overflow-hidden shadow-lg" style="background-image: url('/abstract-polygonal-banner-background-vector.jpg');"> - <a href="post.html" class="flex flex-wrap no-underline hover:no-underline"> - <div class="w-full md:w-2/3 rounded-t"> - </div> - - <div class="w-full md:w-1/3 flex flex-col flex-grow flex-shrink"> - <div class="flex-1 bg-white rounded-t rounded-b-none overflow-hidden shadow-lg"> - <p class="w-full text-gray-600 text-xs md:text-sm pt-6 px-6">Spring Blog</p> - <div class="w-full font-bold text-xl text-gray-900 px-6">๐ Welcome to my Java Spring Blog!</div> - <p class="text-gray-800 font-serif text-base px-6 mb-5"> - This is a blog project I have created with the goal of learning and understand the ins and outs of the Java Spring framework. - </p> - </div> - - <div class="flex-none mt-auto bg-white rounded-b rounded-t-none overflow-hidden shadow-lg p-6"> - <div class="flex items-center justify-between"> - <p class="text-gray-600 text-xs md:text-sm"></p> - </div> - </div> - </div> - - </a> - </div> - <!--/Lead Card--> - - <div th:if="${param.success}" class="text-xl p-4 bg-black text-red-500">Successful Registration!</div> - - - <!--Posts Container--> - <div class="flex flex-wrap justify-between pt-12 -mx-6"> - <div th:each="article :${articles}" class="w-full md:w-1/2 p-6 flex flex-col flex-grow flex-shrink"> - <div class="flex-1 bg-white rounded-t rounded-b-none overflow-hidden shadow-lg"> - <a th:href="@{/articles/{articleId}(articleId=${article.id})}" class="flex flex-wrap no-underline hover:no-underline"> - <img th:src="${article.photoUrl}" class="h-full w-full rounded-t pb-6"> - <div th:text="${article.title}" class="w-full font-bold text-xl text-gray-900 px-6">Lorem ipsum dolor sit amet.</div> - <p class="text-gray-800 font-serif text-base px-6 mb-5"></p> - </a> - <div th:if="${user.id} == ${article.createdBy.id}"></div> - <a th:href="@{/articles/edit/{articleId}(articleId=${article.id})}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 ml-4 text-sm rounded">Edit</a> - <a th:href="@{/articles/delete/{articleId}(articleId=${article.id})}" class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 ml-4 text-sm rounded">Delete</a> - - </div> - <div class="flex-none mt-auto bg-white rounded-b rounded-t-none overflow-hidden shadow-lg p-6"> - <div class="flex items-center justify-between"> - <p class="text-gray-600 text-xs md:text-sm"></p> - </div> - </div> - </div> - </div> - <!--/ Post Content--> - - </div> - - - <!--Subscribe--> - <!-- - <div class="container font-sans bg-green-100 rounded mt-8 p-4 md:p-24 text-center"> - <h2 class="font-bold break-normal text-2xl md:text-4xl">Subscribe to Ghostwind CSS</h2> - <h3 class="font-bold break-normal font-normal text-gray-600 text-base md:text-xl">Get the latest posts delivered right to your inbox</h3> - <div class="w-full text-center pt-4"> - <form action="#"> - <div class="max-w-xl mx-auto p-1 pr-0 flex flex-wrap items-center"> - <input type="email" placeholder="[email protected]" class="flex-1 appearance-none rounded shadow p-3 text-gray-600 mr-2 focus:outline-none"> - <button type="submit" class="flex-1 mt-4 md:mt-0 block md:inline-block appearance-none bg-green-500 text-white text-base font-semibold tracking-wider uppercase py-4 rounded shadow hover:bg-green-400">Subscribe</button> - </div> - </form> - </div> - </div> - --> - <!-- /Subscribe--> - - - <!--Author--> - <!-- - <div class="flex w-full items-center font-sans p-8 md:p-24"> - <img class="w-10 h-10 rounded-full mr-4" src="http://i.pravatar.cc/300" alt="Avatar of Author"> - <div class="flex-1"> - <p class="text-base font-bold text-base md:text-xl leading-none">Ghostwind CSS</p> - <p class="text-gray-600 text-xs md:text-base">Tailwind CSS version of Ghost's Casper theme by <a class="text-gray-800 hover:text-green-500 no-underline border-b-2 border-green-500" href="https://www.tailwindtoolbox.com">TailwindToolbox.com</a></p> - </div> - <div class="justify-end"> - <a href="post.html" class="bg-transparent border border-gray-500 hover:border-green-500 text-xs text-gray-500 hover:text-green-500 font-bold py-2 px-4 rounded-full">Read More</a> - </div> - </div> - --> - <!--/Author--> - - </div> - - -</div> - -</body> - -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html deleted file mode 100644 index f3e0278..0000000 --- a/src/main/resources/templates/layout.html +++ /dev/null @@ -1,149 +0,0 @@ -<!DOCTYPE html> -<html lang="en" - xmlns:th="https://thymeleaf.org" - xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" -> -<head> - <meta charset="UTF-8"> - <link th:href="@{/css/styles.css}" rel="stylesheet"> - <title>Title Layout</title> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta http-equiv="X-UA-Compatible" content="ie=edge"> - <meta name="author" content="name"> - <meta name="description" content="description here"> - <meta name="keywords" content="keywords,here"> - <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/tailwind.min.css"/> <!--Replace with your tailwind.css once created--> - <link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet"> - <style>:root{ -::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;border-radius:6px}::-webkit-scrollbar-thumb{background:#d5d5d5;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#c4c4c4}</style> - -</head> - -<!DOCTYPE html> -<html lang="en" xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> -</head> -<body class="bg-gray-200 min-h-screen font-sans leading-normal tracking-normal"> - -<nav class="bg-gray-900 p-4 mt-0 w-full"> - <div class="container mx-auto flex items-center"> - <div class="flex text-white font-extrabold"> - <a th:if="${(user == null || user.username == null)}" class="flex text-white text-base no-underline hover:text-white hover:no-underline" href="#"> - โ<span class="hidden w-0 md:w-auto md:block pl-1">Spring!</span> - </a> - <a th:if="${!(user == null || user.username == null)}" class="flex text-white text-base no-underline hover:text-white hover:no-underline" href="#"> - โ<span th:text="'Logged in as: ' + ${user.username}" class="hidden w-0 md:w-auto md:block pl-1"></span> - </a> - </div> - <div class="flex pl-4 text-sm place-content-between w-full"> - <ul class="list-reset flex justify-between flex-1 md:flex-none items-center"> - <li class="mr-2"> - <a class="inline-block py-2 px-2 text-white no-underline" href="/">HOME</a> - </li> - <li class="mr-2"> - <a class="inline-block text-indigo-200 no-underline hover:text-gray-100 hover:text-underline py-2 px-2" href="/articles/new">NEW</a> - </li> - <li class="mr-2"> - <a class="inline-block text-indigo-200 no-underline hover:text-indigo-100 hover:text-underline py-2 px-2" href="/register">REGISTER</a> - </li> - <li class="mr-2"> - <a th:if="${(user == null || user.username == null)}" class="inline-block text-indigo-200 no-underline hover:text-indigo-100 hover:text-underline py-2 px-2" href="/userlogin">LOGIN</a> - <a th:if="${!(user == null || user.username == null)}" class="inline-block text-indigo-200 no-underline hover:text-indigo-100 hover:text-underline py-2 px-2" href="/logout">LOGOUT</a> - </li> - </ul> - <form th:action="@{/articles/search}" class="w-full max-w-md"> - <div class="flex flex-wrap -mx-3"> - <div class="w-full px-3"> - <input class="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white" - id="search" - type="search" - name="search" - placeholder="Search"> - </div> - </div> - </form> - </div> - </div> -</nav> - -<!--slide in nav--> -<div id="header" class="bg-white fixed w-full z-10 top-0 hidden animated" style="opacity: .95;"> - <div class="bg-white"> - <div class="flex flex-wrap items-center content-center"> - <div class="flex w-1/2 justify-start text-white font-extrabold"> - <a class="flex text-gray-100 no-underline hover:text-gray-900 hover:no-underline pl-2" href="#"> - ๐ป <span class="hidden w-0 md:w-auto md:block pl-1">Ghostwind CSS</span> - </a> - </div> - <div class="flex w-1/2 justify-end content-center"> - <p class="hidden sm:block mr-3 text-center h-14 p-4 text-xs"><span class="pr-2">Share this</span> ๐</p> - <a class="inline-block text-white no-underline hover:text-white hover:text-underline text-center h-10 w-10 p-2 md:h-auto md:w-16 md:p-4" href="https://twitter.com/intent/tweet?url=#" style="background-color:#33b1ff;"> - <svg class="fill-current text-white h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M30.063 7.313c-.813 1.125-1.75 2.125-2.875 2.938v.75c0 1.563-.188 3.125-.688 4.625a15.088 15.088 0 0 1-2.063 4.438c-.875 1.438-2 2.688-3.25 3.813a15.015 15.015 0 0 1-4.625 2.563c-1.813.688-3.75 1-5.75 1-3.25 0-6.188-.875-8.875-2.625.438.063.875.125 1.375.125 2.688 0 5.063-.875 7.188-2.5-1.25 0-2.375-.375-3.375-1.125s-1.688-1.688-2.063-2.875c.438.063.813.125 1.125.125.5 0 1-.063 1.5-.25-1.313-.25-2.438-.938-3.313-1.938a5.673 5.673 0 0 1-1.313-3.688v-.063c.813.438 1.688.688 2.625.688a5.228 5.228 0 0 1-1.875-2c-.5-.875-.688-1.813-.688-2.75 0-1.063.25-2.063.75-2.938 1.438 1.75 3.188 3.188 5.25 4.25s4.313 1.688 6.688 1.813a5.579 5.579 0 0 1 1.5-5.438c1.125-1.125 2.5-1.688 4.125-1.688s3.063.625 4.188 1.813a11.48 11.48 0 0 0 3.688-1.375c-.438 1.375-1.313 2.438-2.563 3.188 1.125-.125 2.188-.438 3.313-.875z"></path></svg> - </a> - <a class="inline-block text-white no-underline hover:text-white hover:text-underline text-center h-10 w-10 p-2 md:h-auto md:w-16 md:p-4" href="https://www.facebook.com/sharer/sharer.php?u=#" style="background-color:#005e99"> - <svg class="fill-current text-white h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M19 6h5V0h-5c-3.86 0-7 3.14-7 7v3H8v6h4v16h6V16h5l1-6h-6V7c0-.542.458-1 1-1z"></path></svg> - </a> - </div> - </div> - - </div> - <!--Progress bar--> - <div id="progress" class="h-1 bg-white shadow" style="background:linear-gradient(to right, #4dc0b5 var(--scroll), transparent 0);"></div> -</div> - -<!--Header--> -<div class="w-full m-0 p-0 bg-cover bg-bottom" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/6/65/Toronto_Skyline_Summer_2020.jpg'); height: 60vh; max-height:460px;"> - <div class="container max-w-4xl bg-black bg-opacity-50 pb-16 rounded-b-xl mx-auto pt-16 md:pt-32 text-center break-normal"> - <!--Title--> - <p class="text-white font-extrabold text-3xl md:text-5xl"> - โ Spring! - </p> - <p class="text-xl md:text-2xl text-gray-200">Welcome to my Blog</p> - </div> -</div> - - - -<div layout:fragment="content" ></div> - -<footer class="bg-gray-900"> - <div class="container max-w-6xl mx-auto flex items-center px-2 py-8"> - - <div class="w-full mx-auto flex flex-wrap items-center"> - <div class="flex w-full md:w-1/2 justify-center md:justify-start text-white font-extrabold"> - <a class="text-gray-900 no-underline hover:text-gray-900 hover:no-underline" href="#"> - <span class="text-base text-gray-200"></span> - </a> - </div> - <div class="flex w-full pt-2 content-center justify-between md:w-1/2 md:justify-end"> - <ul class="list-reset flex justify-center flex-1 md:flex-none items-center"> - <li> - <a class="inline-block py-2 px-3 text-white no-underline" href="#"></a> - </li> - <li> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-3" href="#"></a> - </li> - <li> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-3" href="#"></a> - </li> - <li> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-3" href="#"></a> - </li> - </ul> - </div> - </div> - - - - </div> -</footer> - -<script src="https://unpkg.com/@popperjs/core@2"></script> -<script src="https://unpkg.com/tippy.js@6"></script> -<script> - //Init tooltips - tippy('.avatar') -</script> -</body> -</html>
\ No newline at end of file diff --git a/src/main/resources/templates/post.html b/src/main/resources/templates/post.html deleted file mode 100644 index a775ff2..0000000 --- a/src/main/resources/templates/post.html +++ /dev/null @@ -1,266 +0,0 @@ -<!DOCTYPE html> -<html lang="en" xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta http-equiv="X-UA-Compatible" content="ie=edge"> - <title>Tailwind Starter Template - Ghostwind CSS : Tailwind Toolbox</title> - <meta name="author" content="name"> - <meta name="description" content="description here"> - <meta name="keywords" content="keywords,here"> - <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/tailwind.min.css"/> <!--Replace with your tailwind.css once created--> - <link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet"> - <style> - .smooth {transition: box-shadow 0.3s ease-in-out;} - ::selection{background-color: aliceblue} - :root{::-webkit-scrollbar{height:10px;width:10px;}::-webkit-scrollbar-track{background:#efefef;border-radius:6px} - ::-webkit-scrollbar-thumb{background:#d5d5d5;border-radius:6px} ::-webkit-scrollbar-thumb:hover{background:#c4c4c4}} - /*scroll to top*/ - .scroll-top {position: fixed;z-index: 50;padding: 0;right: 30px;bottom: 100px;opacity: 0;visibility: hidden;transform: translateY(15px);height: 46px;width: 46px;cursor: pointer;display: flex;align-items: center;justify-content: center;border-radius: 50%;transition: all .4s ease;border: none;box-shadow: inset 0 0 0 2px #ccc;color: #ccc;background-color: #fff;}.scroll-top.is-active {opacity: 1;visibility: visible;transform: translateY(0);}.scroll-top .icon-tabler-arrow-up {position: absolute;stroke-width: 2px;stroke: #333;}.scroll-top svg path {fill: none;}.scroll-top svg.progress-circle path {stroke: #333;stroke-width: 4;transition: all .4s ease;}.scroll-top:hover {color: var(--ghost-accent-color);}.scroll-top:hover .progress-circle path, .scroll-top:hover .icon-tabler-arrow-up {stroke: var(--ghost-accent-color);} - </style> -</head> -<body class="bg-white font-sans leading-normal tracking-normal"> - -<!--Nav--> - - - -<!--Title--> -<div class="text-center pt-16 md:pt-32"> - <p class="text-sm md:text-base text-green-500 font-bold">04 JULY 2023 <span class="text-gray-900">/</span> GETTING STARTED</p> - <h1 class="font-bold break-normal text-3xl md:text-5xl">Welcome to Ghostwind CSS</h1> -</div> - -<!--image--> -<div class="container w-full max-w-6xl mx-auto bg-white bg-cover mt-8 rounded" style="background-image:url('https://source.unsplash.com/collection/1118905/'); height: 75vh;"></div> - -<!--Container--> -<div class="container max-w-5xl mx-auto -mt-32"> - - <div class="mx-0 sm:mx-6"> - - <div class="bg-white w-full p-8 md:p-24 text-xl md:text-2xl text-gray-800 leading-normal" style="font-family:Georgia,serif;"> - - <!--Post Content--> - - - <!--Lead Para--> - <p class="text-2xl md:text-3xl mb-5"> - ๐ Welcome fellow <a class="text-gray-800 hover:text-green-500 no-underline border-b-2 border-green-500" href="https://www.tailwindcss.com">Tailwind CSS</a> and <a class="text-gray-800 hover:text-green-500 no-underline border-b-2 border-green-500" href="https://www.ghost.org">Ghost</a> fan. This starter template is an attempt to replicate the default Ghost theme <a class="text-gray-800 hover:text-green-500 no-underline border-b-2 border-green-500" href="https://demo.ghost.io/welcome">"Casper"</a> using Tailwind CSS and vanilla Javascript. - </p> - - <p class="py-6">The basic blog page layout is available and all using the default Tailwind CSS classes (although there are a few hardcoded style tags). If you are going to use this in your project, you will want to convert the classes into components.</p> - - <p class="py-6">Sed dignissim lectus ut tincidunt vulputate. Fusce tincidunt lacus purus, in mattis tortor sollicitudin pretium. Phasellus at diam posuere, scelerisque nisl sit amet, tincidunt urna. Cras nisi diam, pulvinar ut molestie eget, eleifend ac magna. Sed at lorem condimentum, dignissim lorem eu, blandit massa. Phasellus eleifend turpis vel erat bibendum scelerisque. Maecenas id risus dictum, rhoncus odio vitae, maximus purus. Etiam efficitur dolor in dolor molestie ornare. Aenean pulvinar diam nec neque tincidunt, vitae molestie quam fermentum. Donec ac pretium diam. Suspendisse sed odio risus. Nunc nec luctus nisi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis nec nulla eget sem dictum elementum.</p> - - <ol> - <li class="py-3">Maecenas accumsan lacus sit amet elementum porta. Aliquam eu libero lectus. Fusce vehicula dictum mi. In non dolor at sem ullamcorper venenatis ut sed dui. Ut ut est quam. Suspendisse quam quam, commodo sit amet placerat in, interdum a ipsum. Morbi sit amet tellus scelerisque tortor semper posuere.</li> - <li class="py-3">Morbi varius posuere blandit. Praesent gravida bibendum neque eget commodo. Duis auctor ornare mauris, eu accumsan odio viverra in. Proin sagittis maximus pharetra. Nullam lorem mauris, faucibus ut odio tempus, ultrices aliquet ex. Nam id quam eget ipsum luctus hendrerit. Ut eros magna, eleifend ac ornare vulputate, pretium nec felis.</li> - <li class="py-3">Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nunc vitae pretium elit. Cras leo mauris, tristique in risus ac, tristique rutrum velit. Mauris accumsan tempor felis vitae gravida. Cras egestas convallis malesuada. Etiam ac ante id tortor vulputate pretium. Maecenas vel sapien suscipit, elementum odio et, consequat tellus.</li> - </ol> - - <blockquote class="border-l-4 border-green-500 italic my-8 pl-8 md:pl-12">Example of blockquote - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula.</blockquote> - - <p class="py-6">Example code block:</p> - <pre class="bg-gray-900 rounded text-white font-mono text-base p-4"> - <code class="break-words whitespace-pre-wrap"> -<header class="site-header outer"> - <div class="inner"> - {{> "site-nav"}} - </div> -</header> - </code> - </pre> - - - <!--/ Post Content--> - - </div> - - - <!--Subscribe--> - <div class="container font-sans bg-green-100 rounded mt-8 p-4 md:p-24 text-center"> - <h2 class="font-bold break-normal text-2xl md:text-4xl">Subscribe to Ghostwind CSS</h2> - <h3 class="font-bold break-normal font-normal text-gray-600 text-base md:text-xl">Get the latest posts delivered right to your inbox</h3> - <div class="w-full text-center pt-4"> - <form action="#"> - <div class="max-w-sm mx-auto p-1 pr-0 flex flex-wrap items-center"> - <input type="email" placeholder="[email protected]" class="flex-1 appearance-none rounded shadow p-3 text-gray-600 mr-2 focus:outline-none"> - <button type="submit" class="flex-1 mt-4 md:mt-0 block md:inline-block appearance-none bg-green-500 text-white text-base font-semibold tracking-wider uppercase py-4 rounded shadow hover:bg-green-400">Subscribe</button> - </div> - </form> - </div> - </div> - <!-- /Subscribe--> - - - <!--Author--> - <div class="flex w-full items-center font-sans p-8 md:p-24"> - <img class="w-10 h-10 rounded-full mr-4" src="http://i.pravatar.cc/300" alt="Avatar of Author"> - <div class="flex-1"> - <p class="text-base font-bold text-base md:text-xl leading-none">Ghostwind CSS</p> - <p class="text-gray-600 text-xs md:text-base">Tailwind CSS version of Ghost's Casper theme by <a class="text-gray-800 hover:text-green-500 no-underline border-b-2 border-green-500" href="https://www.tailwindtoolbox.com">TailwindToolbox.com</a></p> - </div> - <div class="justify-end"> - - </div> - <!--/Author--> - - </div> - - - </div> - -</div> - - -<!-- Scroll Top Button --> -<button class="btn-toggle-round scroll-top js-scroll-top" type="button" title="Scroll to top"> - <svg class="progress-circle" width="100%" height="100%" viewBox="-1 -1 102 102"> - <path d="M50,1 a49,49 0 0,1 0,98 a49,49 0 0,1 0,-98"/> - </svg> - <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="cuurentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> - <path stroke="none" d="M0 0h24v24H0z" fill="none"/> - <line x1="12" y1="5" x2="12" y2="19" /> - <line x1="18" y1="11" x2="12" y2="5" /> - <line x1="6" y1="11" x2="12" y2="5" /> - </svg> -</button> - -<div class="bg-gray-200"> - - <div class="container w-full max-w-6xl mx-auto px-2 py-8"> - <div class="flex flex-wrap -mx-2"> - <div class="w-full md:w-1/3 px-2 pb-12"> - <div class="h-full bg-white rounded overflow-hidden shadow-md hover:shadow-lg relative smooth"> - <a href="#" class="no-underline hover:no-underline"> - <img src="https://source.unsplash.com/_AjqGGafofE/400x200" class="h-48 w-full rounded-t shadow-lg"> - <div class="p-6 h-auto md:h-48"> - <p class="text-gray-600 text-xs md:text-sm">GETTING STARTED</p> - <div class="font-bold text-xl text-gray-900">Aperture Science</div> - <p class="text-gray-800 font-serif text-base mb-5"> - Iโll be honest, weโre throwing science at the wall here to see what sticks. No idea what itโll do. Probably nothing. Best case scenario you might get some super powers. Worst case, some tumors, which weโll cut out. - </p> - </div> - <div class="flex items-center justify-between inset-x-0 bottom-0 p-6"> - <img class="w-8 h-8 rounded-full mr-4" src="http://i.pravatar.cc/300" alt="Avatar of Author"> - <p class="text-gray-600 text-xs md:text-sm">2 MIN READ</p> - </div> - </a> - </div> - </div> - <div class="w-full md:w-1/3 px-2 pb-12"> - <div class="h-full bg-white rounded overflow-hidden shadow-md hover:shadow-lg relative smooth"> - <a href="#" class="no-underline hover:no-underline"> - <img src="https://source.unsplash.com/_AjqGGafofE/400x200" class="h-48 w-full rounded-t shadow"> - <div class="p-6 h-auto md:h-48"> - <p class="text-gray-600 text-xs md:text-sm">UNDERWATER</p> - <div class="font-bold text-xl text-gray-900">Biolumini algae diatomeae ecology.</div> - <p class="text-gray-800 font-serif text-base mb-5"> - Lorem ipsum dolor sit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. - </p> - </div> - <div class="flex items-center justify-between inset-x-0 bottom-0 p-6"> - <img class="w-8 h-8 rounded-full mr-4" src="http://i.pravatar.cc/300" alt="Avatar of Author"> - <p class="text-gray-600 text-xs md:text-sm">4 MIN READ</p> - </div> - </a> - </div> - </div> - <div class="w-full md:w-1/3 px-2 pb-12"> - <div class="h-full bg-white rounded overflow-hidden shadow-md hover:shadow-lg relative smooth"> - <a href="#" class="no-underline hover:no-underline"> - <img src="https://source.unsplash.com/DEa8_vxKlEo/400x200" class="h-48 w-full rounded-t shadow"> - <div class="p-6 h-auto md:h-48"> - <p class="text-gray-600 text-xs md:text-sm">FOREST</p> - <div class="font-bold text-xl text-gray-900">What is life but a teardrop in the eye of infinity?</div> - <p class="text-gray-800 font-serif text-base mb-5"> - Mollis pretium integer eros et dui orci, lectus nec elit sagittis neque. Dignissim ac nullam semper aliquet volutpat, ut scelerisque. - </p> - </div> - <div class="flex items-center justify-between inset-x-0 bottom-0 p-6"> - <img class="w-8 h-8 rounded-full mr-4" src="http://i.pravatar.cc/300" alt="Avatar of Author"> - <p class="text-gray-600 text-xs md:text-sm">7 MIN READ</p> - </div> - </a> - </div> - </div> - </div> - </div> - - -</div> - -<footer class="bg-gray-900"> - <div class="container max-w-6xl mx-auto flex items-center px-2 py-8"> - - <div class="w-full mx-auto flex flex-wrap items-center"> - <div class="flex w-full md:w-1/2 justify-center md:justify-start text-white font-extrabold"> - <a class="text-gray-900 no-underline hover:text-gray-900 hover:no-underline" href="#"> - <span class="text-base text-gray-200">Ghostwind</span> - </a> - </div> - <div class="flex w-full pt-2 content-center justify-between md:w-1/2 md:justify-end"> - <ul class="list-reset flex justify-center flex-1 md:flex-none items-center"> - <li> - <a class="inline-block py-2 px-3 text-white no-underline" href="index.html">HOME</a> - </li> - <li> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-3" href="#">link</a> - </li> - <li> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-3" href="#">link</a> - </li> - <li> - <a class="inline-block text-gray-600 no-underline hover:text-gray-200 hover:underline py-2 px-3" href="#">link</a> - </li> - </ul> - </div> - </div> - - - - </div> -</footer> - -<script> - /* Progress bar */ - //Source: https://alligator.io/js/progress-bar-javascript-css-variables/ - var h = document.documentElement, - b = document.body, - st = 'scrollTop', - sh = 'scrollHeight', - progress = document.querySelector('#progress'), - scroll; - var scrollpos = window.scrollY; - var header = document.getElementById("header"); - - document.addEventListener('scroll', function() { - - /*Refresh scroll % width*/ - scroll = (h[st]||b[st]) / ((h[sh]||b[sh]) - h.clientHeight) * 100; - progress.style.setProperty('--scroll', scroll + '%'); - - /*Apply classes for slide in bar*/ - scrollpos = window.scrollY; - - if(scrollpos > 100){ - header.classList.remove("hidden"); - header.classList.remove("fadeOutUp"); - header.classList.add("slideInDown"); - } - else { - header.classList.remove("slideInDown"); - header.classList.add("fadeOutUp"); - header.classList.add("hidden"); - } - - }); - -// scroll to top -const t=document.querySelector(".js-scroll-top");if(t){t.onclick=()=>{window.scrollTo({top:0,behavior:"smooth"})};const e=document.querySelector(".scroll-top path"),o=e.getTotalLength();e.style.transition=e.style.WebkitTransition="none",e.style.strokeDasharray=`${o} ${o}`,e.style.strokeDashoffset=o,e.getBoundingClientRect(),e.style.transition=e.style.WebkitTransition="stroke-dashoffset 10ms linear";const n=function(){const t=window.scrollY||window.scrollTopBtn||document.documentElement.scrollTopBtn,n=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight),s=Math.max(document.documentElement.clientHeight,window.innerHeight||0);var l=o-t*o/(n-s);e.style.strokeDashoffset=l};n();const s=100;window.addEventListener("scroll",(function(e){n();(window.scrollY||window.scrollTopBtn||document.getElementsByTagName("html")[0].scrollTopBtn)>s?t.classList.add("is-active"):t.classList.remove("is-active")}),!1)} - -</script> - -</body> -</html>
\ No newline at end of file |
