├── sonar-project.properties ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Dockerfile ├── src ├── main │ ├── java │ │ └── com │ │ │ └── sopromadze │ │ │ └── blogapi │ │ │ ├── model │ │ │ ├── role │ │ │ │ ├── RoleName.java │ │ │ │ └── Role.java │ │ │ ├── audit │ │ │ │ ├── UserDateAudit.java │ │ │ │ └── DateAudit.java │ │ │ ├── Todo.java │ │ │ ├── Category.java │ │ │ ├── Photo.java │ │ │ ├── Album.java │ │ │ ├── Tag.java │ │ │ ├── Comment.java │ │ │ ├── user │ │ │ │ ├── Geo.java │ │ │ │ ├── Company.java │ │ │ │ ├── Address.java │ │ │ │ └── User.java │ │ │ └── Post.java │ │ │ ├── payload │ │ │ ├── UserIdentityAvailability.java │ │ │ ├── LoginRequest.java │ │ │ ├── UserSummary.java │ │ │ ├── UserDateAuditPayload.java │ │ │ ├── JwtAuthenticationResponse.java │ │ │ ├── CommentRequest.java │ │ │ ├── PhotoResponse.java │ │ │ ├── PhotoRequest.java │ │ │ ├── DateAuditPayload.java │ │ │ ├── InfoRequest.java │ │ │ ├── PostResponse.java │ │ │ ├── SignUpRequest.java │ │ │ ├── UserProfile.java │ │ │ ├── request │ │ │ │ └── AlbumRequest.java │ │ │ ├── PostRequest.java │ │ │ ├── ExceptionResponse.java │ │ │ ├── AlbumResponse.java │ │ │ ├── ApiResponse.java │ │ │ └── PagedResponse.java │ │ │ ├── repository │ │ │ ├── CategoryRepository.java │ │ │ ├── TagRepository.java │ │ │ ├── RoleRepository.java │ │ │ ├── PhotoRepository.java │ │ │ ├── TodoRepository.java │ │ │ ├── AlbumRepository.java │ │ │ ├── CommentRepository.java │ │ │ ├── PostRepository.java │ │ │ └── UserRepository.java │ │ │ ├── service │ │ │ ├── CustomUserDetailsService.java │ │ │ ├── TagService.java │ │ │ ├── TodoService.java │ │ │ ├── CommentService.java │ │ │ ├── PhotoService.java │ │ │ ├── CategoryService.java │ │ │ ├── AlbumService.java │ │ │ ├── PostService.java │ │ │ ├── UserService.java │ │ │ └── impl │ │ │ │ ├── CustomUserDetailsServiceImpl.java │ │ │ │ ├── TagServiceImpl.java │ │ │ │ ├── CategoryServiceImpl.java │ │ │ │ ├── CommentServiceImpl.java │ │ │ │ ├── AlbumServiceImpl.java │ │ │ │ ├── TodoServiceImpl.java │ │ │ │ ├── PhotoServiceImpl.java │ │ │ │ └── UserServiceImpl.java │ │ │ ├── exception │ │ │ ├── AppException.java │ │ │ ├── ResponseEntityErrorException.java │ │ │ ├── BlogapiException.java │ │ │ ├── BadRequestException.java │ │ │ ├── AccessDeniedException.java │ │ │ ├── UnauthorizedException.java │ │ │ ├── ResourceNotFoundException.java │ │ │ └── RestControllerExceptionHandler.java │ │ │ ├── security │ │ │ ├── CurrentUser.java │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ ├── JwtTokenProvider.java │ │ │ ├── JwtAuthenticationFilter.java │ │ │ └── UserPrincipal.java │ │ │ ├── utils │ │ │ ├── AppUtils.java │ │ │ └── AppConstants.java │ │ │ ├── config │ │ │ ├── WebMvcConfig.java │ │ │ ├── AuditingConfig.java │ │ │ └── SecutiryConfig.java │ │ │ ├── BlogApiApplication.java │ │ │ └── controller │ │ │ ├── CategoryController.java │ │ │ ├── TagController.java │ │ │ ├── PhotoController.java │ │ │ ├── CommentController.java │ │ │ ├── TodoController.java │ │ │ ├── AlbumController.java │ │ │ ├── PostController.java │ │ │ ├── AuthController.java │ │ │ └── UserController.java │ └── resources │ │ ├── _application.properties │ │ ├── application.yml │ │ └── blogapi.sql └── test │ └── java │ └── com │ └── sopromadze │ └── blogapi │ └── BlogApiApplicationTests.java ├── .gitignore ├── .github ├── workflows │ └── greetings.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── SECURITY.md ├── docker-compose.yml ├── .travis.yml ├── docs ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── pom.xml ├── mvnw.cmd ├── mvnw └── data └── blogapi.sql /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.branch.name=development 2 | sonar.sources=src/main/ 3 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RameshMF/Spring-Boot-Blog-REST-API/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk11:alpine-jre 2 | VOLUME /tmp 3 | ARG JAR_FILE=target/*.jar 4 | COPY ${JAR_FILE} app.jar 5 | ENTRYPOINT ["java","-jar","/app.jar"] -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/role/RoleName.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.role; 2 | 3 | public enum RoleName { 4 | ROLE_ADMIN, 5 | ROLE_USER, 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/UserIdentityAvailability.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class UserIdentityAvailability { 9 | private Boolean available; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | 7 | @Data 8 | public class LoginRequest { 9 | @NotBlank 10 | private String usernameOrEmail; 11 | 12 | @NotBlank 13 | private String password; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/UserSummary.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class UserSummary { 9 | private Long id; 10 | private String username; 11 | private String firstName; 12 | private String lastName; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/UserDateAuditPayload.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | @EqualsAndHashCode(callSuper = true) 7 | @Data 8 | public abstract class UserDateAuditPayload extends DateAuditPayload { 9 | private Long createdBy; 10 | 11 | private Long updatedBy; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.Category; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CategoryRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/JwtAuthenticationResponse.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class JwtAuthenticationResponse { 7 | private String accessToken; 8 | private String tokenType = "Bearer"; 9 | 10 | public JwtAuthenticationResponse(String accessToken) { 11 | this.accessToken = accessToken; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/CommentRequest.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | import javax.validation.constraints.Size; 7 | 8 | @Data 9 | public class CommentRequest { 10 | @NotBlank 11 | @Size(min = 10, message = "Comment body must be minimum 10 characters") 12 | private String body; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/TagRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.Tag; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface TagRepository extends JpaRepository { 9 | Tag findByName(String name); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.role.Role; 4 | import com.sopromadze.blogapi.model.role.RoleName; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.Optional; 8 | 9 | public interface RoleRepository extends JpaRepository { 10 | Optional findByName(RoleName name); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | /bin/ 13 | /build/ 14 | *.jar 15 | *.war 16 | *.ear 17 | *.db 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | .mvn 25 | 26 | ### NetBeans ### 27 | /nbproject/private/ 28 | /build/ 29 | /nbbuild/ 30 | /dist/ 31 | /nbdist/ 32 | /.nb-gradle/ -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/CustomUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import org.springframework.security.core.userdetails.UserDetails; 4 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 5 | 6 | public interface CustomUserDetailsService { 7 | 8 | UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException; 9 | 10 | UserDetails loadUserById(Long id); 11 | 12 | } -------------------------------------------------------------------------------- /src/test/java/com/sopromadze/blogapi/BlogApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest(classes = BlogApiApplication.class) 10 | public class BlogApiApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Welcome to Spring-Boot-Blog-REST-API community! Thanks so much for creating your first issue :)' 13 | pr-message: 'Thanks so much for creating your first PR, the Spring-Boot-Blog-REST-API community thanks you :)' 14 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/PhotoRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.Photo; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public interface PhotoRepository extends JpaRepository { 11 | Page findByAlbumId(Long albumId, Pageable pageable); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/TodoRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.Todo; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public interface TodoRepository extends JpaRepository { 11 | Page findByCreatedBy(Long userId, Pageable pageable); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/AlbumRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.Album; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public interface AlbumRepository extends JpaRepository { 11 | Page findByCreatedBy(Long userId, Pageable pageable); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.Comment; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public interface CommentRepository extends JpaRepository { 11 | Page findByPostId(Long postId, Pageable pageable); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/AppException.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 7 | public class AppException extends RuntimeException { 8 | private static final long serialVersionUID = 1L; 9 | 10 | public AppException(String message) { 11 | super(message); 12 | } 13 | 14 | public AppException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/PhotoResponse.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class PhotoResponse { 7 | private Long id; 8 | private String title; 9 | private String url; 10 | private String thumbnailUrl; 11 | private Long albumId; 12 | 13 | public PhotoResponse(Long id, String title, String url, String thumbnailUrl, Long albumId) { 14 | this.id = id; 15 | this.title = title; 16 | this.url = url; 17 | this.thumbnailUrl = thumbnailUrl; 18 | this.albumId = albumId; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/PhotoRequest.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | import javax.validation.constraints.NotNull; 7 | import javax.validation.constraints.Size; 8 | 9 | @Data 10 | public class PhotoRequest { 11 | 12 | @NotBlank 13 | @Size(min = 3) 14 | private String title; 15 | 16 | @NotBlank 17 | @Size(min = 10) 18 | private String url; 19 | 20 | @NotBlank 21 | @Size(min = 10) 22 | private String thumbnailUrl; 23 | 24 | @NotNull 25 | private Long albumId; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/_application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.hibernate.ddl-auto=none 2 | spring.datasource.url=jdbc:mysql://localhost:3306/blogapi?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true 3 | spring.datasource.username=root 4 | spring.datasource.password=root 5 | spring.jpa.show-sql=true 6 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect 7 | app.jwtSecret=secret 8 | # Expiration in milliseconds - 1 Hour 9 | app.jwtExpirationInMs=3600000 10 | spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false 11 | spring.jackson.time-zone=UTC 12 | cors.allowedOrings=* 13 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | We take security very seriously and try to patch each discovered security vulnerability as soon as possible. Also we use 4 | [automated tools](https://dependabot.com/) to continually monitor vulnerable dependencies. 5 | 6 | ## Reporting a Vulnerability 7 | 8 | We use [full disclosure]() for reporting vulnerabilities. Therefore please 9 | submit vulnerability reports through our [issues section](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues/new?assignees=&labels=&template=bug_report.md&title=). 10 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/security/CurrentUser.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.security; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Documented 14 | @AuthenticationPrincipal 15 | public @interface CurrentUser { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:mysql://blogapi-db:3306/blogapi?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true 4 | username: root 5 | password: root 6 | jpa: 7 | hibernate: 8 | ddl-auto: none 9 | show-sql: true 10 | properties: 11 | hibernate: 12 | dialect: org.hibernate.dialect.MySQL5Dialect 13 | jackson: 14 | serialization: 15 | WRITE_DATES_AS_TIMESTAMPS: false 16 | time-zone: UTC 17 | 18 | app: 19 | jwtSecret: secret 20 | jwtExpirationInMs: 3600000 21 | 22 | cors: 23 | allowedOrings: '*' 24 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/DateAuditPayload.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import java.time.Instant; 4 | 5 | public abstract class DateAuditPayload { 6 | 7 | private Instant createdAt; 8 | 9 | private Instant updatedAt; 10 | 11 | public Instant getCreatedAt() { 12 | return createdAt; 13 | } 14 | 15 | public void setCreatedAt(Instant createdAt) { 16 | this.createdAt = createdAt; 17 | } 18 | 19 | public Instant getUpdatedAt() { 20 | return updatedAt; 21 | } 22 | 23 | public void setUpdatedAt(Instant updatedAt) { 24 | this.updatedAt = updatedAt; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/TagService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.model.Tag; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.PagedResponse; 6 | import com.sopromadze.blogapi.security.UserPrincipal; 7 | 8 | public interface TagService { 9 | 10 | PagedResponse getAllTags(int page, int size); 11 | 12 | Tag getTag(Long id); 13 | 14 | Tag addTag(Tag tag, UserPrincipal currentUser); 15 | 16 | Tag updateTag(Long id, Tag newTag, UserPrincipal currentUser); 17 | 18 | ApiResponse deleteTag(Long id, UserPrincipal currentUser); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/InfoRequest.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | 7 | @Data 8 | public class InfoRequest { 9 | 10 | @NotBlank 11 | private String street; 12 | 13 | @NotBlank 14 | private String suite; 15 | 16 | @NotBlank 17 | private String city; 18 | 19 | @NotBlank 20 | private String zipcode; 21 | 22 | private String companyName; 23 | 24 | private String catchPhrase; 25 | 26 | private String bs; 27 | 28 | private String website; 29 | 30 | private String phone; 31 | 32 | private String lat; 33 | 34 | private String lng; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/ResponseEntityErrorException.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import org.springframework.http.ResponseEntity; 5 | 6 | public class ResponseEntityErrorException extends RuntimeException { 7 | private static final long serialVersionUID = -3156815846745801694L; 8 | 9 | private transient ResponseEntity apiResponse; 10 | 11 | public ResponseEntityErrorException(ResponseEntity apiResponse) { 12 | this.apiResponse = apiResponse; 13 | } 14 | 15 | public ResponseEntity getApiResponse() { 16 | return apiResponse; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/PostResponse.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | @Data 10 | public class PostResponse { 11 | private String title; 12 | private String body; 13 | private String category; 14 | private List tags; 15 | 16 | 17 | 18 | public List getTags() { 19 | 20 | return tags == null ? null : new ArrayList<>(tags); 21 | } 22 | 23 | public void setTags(List tags) { 24 | 25 | if (tags == null) { 26 | this.tags = null; 27 | } else { 28 | this.tags = Collections.unmodifiableList(tags); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/SignUpRequest.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Email; 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.Size; 8 | 9 | @Data 10 | public class SignUpRequest { 11 | @NotBlank 12 | @Size(min = 4, max = 40) 13 | private String firstName; 14 | 15 | @NotBlank 16 | @Size(min = 4, max = 40) 17 | private String lastName; 18 | 19 | @NotBlank 20 | @Size(min = 3, max = 15) 21 | private String username; 22 | 23 | @NotBlank 24 | @Size(max = 40) 25 | @Email 26 | private String email; 27 | 28 | @NotBlank 29 | @Size(min = 6, max = 20) 30 | private String password; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/UserProfile.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import com.sopromadze.blogapi.model.user.Address; 4 | import com.sopromadze.blogapi.model.user.Company; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.time.Instant; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UserProfile { 15 | private Long id; 16 | private String username; 17 | private String firstName; 18 | private String lastName; 19 | private Instant joinedAt; 20 | private String email; 21 | private Address address; 22 | private String phone; 23 | private String website; 24 | private Company company; 25 | private Long postCount; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/utils/AppUtils.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.utils; 2 | 3 | import com.sopromadze.blogapi.exception.BlogapiException; 4 | import org.springframework.http.HttpStatus; 5 | 6 | public class AppUtils { 7 | public static void validatePageNumberAndSize(int page, int size) { 8 | if (page < 0) { 9 | throw new BlogapiException(HttpStatus.BAD_REQUEST, "Page number cannot be less than zero."); 10 | } 11 | 12 | if (size < 0) { 13 | throw new BlogapiException(HttpStatus.BAD_REQUEST, "Size number cannot be less than zero."); 14 | } 15 | 16 | if (size > AppConstants.MAX_PAGE_SIZE) { 17 | throw new BlogapiException(HttpStatus.BAD_REQUEST, "Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/PostRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.model.Post; 4 | import com.sopromadze.blogapi.model.Tag; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.List; 11 | 12 | @Repository 13 | public interface PostRepository extends JpaRepository { 14 | Page findByCreatedBy(Long userId, Pageable pageable); 15 | 16 | Page findByCategory(Long categoryId, Pageable pageable); 17 | 18 | Page findByTags(List tags, Pageable pageable); 19 | 20 | Long countByCreatedBy(Long userId); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | public class WebMvcConfig implements WebMvcConfigurer { 10 | 11 | @Value("cors.allowedOrings") 12 | private String allowedOrigins; 13 | 14 | public void addCorsMappings(CorsRegistry registry) { 15 | final long MAX_AGE_SECS = 3600; 16 | 17 | registry.addMapping("/**") 18 | .allowedOrigins(allowedOrigins) 19 | .allowedMethods("GET", "POST", "PUT", "DELETE") 20 | .allowedHeaders("*") 21 | .maxAge(MAX_AGE_SECS); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/TodoService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.model.Todo; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.PagedResponse; 6 | import com.sopromadze.blogapi.security.UserPrincipal; 7 | 8 | public interface TodoService { 9 | 10 | Todo completeTodo(Long id, UserPrincipal currentUser); 11 | 12 | Todo unCompleteTodo(Long id, UserPrincipal currentUser); 13 | 14 | PagedResponse getAllTodos(UserPrincipal currentUser, int page, int size); 15 | 16 | Todo addTodo(Todo todo, UserPrincipal currentUser); 17 | 18 | Todo getTodo(Long id, UserPrincipal currentUser); 19 | 20 | Todo updateTodo(Long id, Todo newTodo, UserPrincipal currentUser); 21 | 22 | ApiResponse deleteTodo(Long id, UserPrincipal currentUser); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/BlogapiException.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BlogapiException extends RuntimeException { 6 | 7 | private static final long serialVersionUID = -6593330219878485669L; 8 | 9 | private final HttpStatus status; 10 | private final String message; 11 | 12 | public BlogapiException(HttpStatus status, String message) { 13 | super(); 14 | this.status = status; 15 | this.message = message; 16 | } 17 | 18 | public BlogapiException(HttpStatus status, String message, Throwable exception) { 19 | super(exception); 20 | this.status = status; 21 | this.message = message; 22 | } 23 | 24 | public HttpStatus getStatus() { 25 | return status; 26 | } 27 | 28 | public String getMessage() { 29 | return message; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.model.Comment; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.CommentRequest; 6 | import com.sopromadze.blogapi.payload.PagedResponse; 7 | import com.sopromadze.blogapi.security.UserPrincipal; 8 | 9 | public interface CommentService { 10 | 11 | PagedResponse getAllComments(Long postId, int page, int size); 12 | 13 | Comment addComment(CommentRequest commentRequest, Long postId, UserPrincipal currentUser); 14 | 15 | Comment getComment(Long postId, Long id); 16 | 17 | Comment updateComment(Long postId, Long id, CommentRequest commentRequest, UserPrincipal currentUser); 18 | 19 | ApiResponse deleteComment(Long postId, Long id, UserPrincipal currentUser); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/audit/UserDateAudit.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.audit; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import org.springframework.data.annotation.CreatedBy; 7 | import org.springframework.data.annotation.LastModifiedBy; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.MappedSuperclass; 11 | 12 | @EqualsAndHashCode(callSuper = true) 13 | @MappedSuperclass 14 | @Data 15 | @JsonIgnoreProperties( 16 | value = { "createdBY", "updatedBy" }, 17 | allowGetters = true 18 | ) 19 | public abstract class UserDateAudit extends DateAudit { 20 | private static final long serialVersionUID = 1L; 21 | 22 | @CreatedBy 23 | @Column(updatable = false) 24 | private Long createdBy; 25 | 26 | @LastModifiedBy 27 | private Long updatedBy; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/PhotoService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import com.sopromadze.blogapi.payload.PagedResponse; 5 | import com.sopromadze.blogapi.payload.PhotoRequest; 6 | import com.sopromadze.blogapi.payload.PhotoResponse; 7 | import com.sopromadze.blogapi.security.UserPrincipal; 8 | 9 | public interface PhotoService { 10 | 11 | PagedResponse getAllPhotos(int page, int size); 12 | 13 | PhotoResponse getPhoto(Long id); 14 | 15 | PhotoResponse updatePhoto(Long id, PhotoRequest photoRequest, UserPrincipal currentUser); 16 | 17 | PhotoResponse addPhoto(PhotoRequest photoRequest, UserPrincipal currentUser); 18 | 19 | ApiResponse deletePhoto(Long id, UserPrincipal currentUser); 20 | 21 | PagedResponse getAllPhotosByAlbum(Long albumId, int page, int size); 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | @ResponseStatus(HttpStatus.BAD_REQUEST) 8 | public class BadRequestException extends RuntimeException { 9 | private static final long serialVersionUID = 1L; 10 | 11 | private ApiResponse apiResponse; 12 | 13 | public BadRequestException(ApiResponse apiResponse) { 14 | super(); 15 | this.apiResponse = apiResponse; 16 | } 17 | 18 | public BadRequestException(String message) { 19 | super(message); 20 | } 21 | 22 | public BadRequestException(String message, Throwable cause) { 23 | super(message, cause); 24 | } 25 | 26 | public ApiResponse getApiResponse() { 27 | return apiResponse; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/role/Role.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.role; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.hibernate.annotations.NaturalId; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.EnumType; 10 | import javax.persistence.Enumerated; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.Table; 15 | 16 | @Entity 17 | @Data 18 | @NoArgsConstructor 19 | @Table(name = "roles") 20 | public class Role { 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.IDENTITY) 23 | private Long id; 24 | 25 | @Enumerated(EnumType.STRING) 26 | @NaturalId 27 | @Column(name = "name") 28 | private RoleName name; 29 | 30 | public Role(RoleName name) { 31 | this.name = name; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/request/AlbumRequest.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload.request; 2 | 3 | import com.sopromadze.blogapi.model.Photo; 4 | import com.sopromadze.blogapi.model.user.User; 5 | import com.sopromadze.blogapi.payload.UserDateAuditPayload; 6 | import lombok.Data; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | @Data 13 | public class AlbumRequest extends UserDateAuditPayload { 14 | 15 | private Long id; 16 | 17 | private String title; 18 | 19 | private User user; 20 | 21 | private List photo; 22 | 23 | public List getPhoto() { 24 | 25 | return photo == null ? null : new ArrayList<>(photo); 26 | } 27 | 28 | public void setPhoto(List photo) { 29 | 30 | if (photo == null) { 31 | this.photo = null; 32 | } else { 33 | this.photo = Collections.unmodifiableList(photo); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | db: 4 | image: mysql 5 | container_name: blogapi-db 6 | restart: always 7 | environment: 8 | MYSQL_DATABASE: 'blogapi' 9 | MYSQL_PASSWORD: 'root' 10 | MYSQL_ROOT_PASSWORD: 'root' 11 | ports: 12 | - '3306:3306' 13 | networks: 14 | - blogapi-network 15 | healthcheck: 16 | test: "/usr/bin/mysql --user=root --password=root --execute \"SHOW DATABASES;\"" 17 | interval: 2s 18 | timeout: 20s 19 | retries: 10 20 | volumes: 21 | - ./data:/docker-entrypoint-initdb.d 22 | application: 23 | container_name: blogapi-application 24 | build: 25 | context: ./ 26 | dockerfile: Dockerfile 27 | ports: 28 | - "8080:8080" 29 | networks: 30 | - blogapi-network 31 | depends_on: 32 | - "db" 33 | networks: 34 | blogapi-network: 35 | name: blogapi-network 36 | driver: bridge 37 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/PostRequest.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | import javax.validation.constraints.NotNull; 7 | import javax.validation.constraints.Size; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | @Data 13 | public class PostRequest { 14 | 15 | @NotBlank 16 | @Size(min = 10) 17 | private String title; 18 | 19 | @NotBlank 20 | @Size(min = 50) 21 | private String body; 22 | 23 | @NotNull 24 | private Long categoryId; 25 | 26 | private List tags; 27 | 28 | public List getTags() { 29 | 30 | return tags == null ? Collections.emptyList() : new ArrayList<>(tags); 31 | } 32 | 33 | public void setTags(List tags) { 34 | 35 | if (tags == null) { 36 | this.tags = null; 37 | } else { 38 | this.tags = Collections.unmodifiableList(tags); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.exception.UnauthorizedException; 4 | import com.sopromadze.blogapi.model.Category; 5 | import com.sopromadze.blogapi.payload.ApiResponse; 6 | import com.sopromadze.blogapi.payload.PagedResponse; 7 | import com.sopromadze.blogapi.security.UserPrincipal; 8 | import org.springframework.http.ResponseEntity; 9 | 10 | public interface CategoryService { 11 | 12 | PagedResponse getAllCategories(int page, int size); 13 | 14 | ResponseEntity getCategory(Long id); 15 | 16 | ResponseEntity addCategory(Category category, UserPrincipal currentUser); 17 | 18 | ResponseEntity updateCategory(Long id, Category newCategory, UserPrincipal currentUser) 19 | throws UnauthorizedException; 20 | 21 | ResponseEntity deleteCategory(Long id, UserPrincipal currentUser) throws UnauthorizedException; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.utils; 2 | 3 | public class AppConstants { 4 | public static final String DEFAULT_PAGE_NUMBER = "0"; 5 | 6 | public static final String DEFAULT_PAGE_SIZE = "30"; 7 | 8 | public static final int MAX_PAGE_SIZE = 30; 9 | 10 | public static final String CREATED_AT = "createdAt"; 11 | 12 | public static final String ID = "id"; 13 | 14 | public static final String PHOTO = "Photo"; 15 | 16 | public static final String ALBUM = "Album"; 17 | 18 | public static final String USERNAME = "username"; 19 | 20 | public static final String USER = "User"; 21 | 22 | public static final String CATEGORY = "Category"; 23 | 24 | public static final String TAG = "Tag"; 25 | 26 | public static final String POST = "Post"; 27 | 28 | public static final String TODO = "ToDo"; 29 | 30 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION = "You don't have permission to make this operation"; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/ExceptionResponse.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.Instant; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | @Data 11 | public class ExceptionResponse { 12 | private String error; 13 | private Integer status; 14 | private List messages; 15 | private Instant timestamp; 16 | 17 | public ExceptionResponse(List messages, String error, Integer status) { 18 | setMessages(messages); 19 | this.error = error; 20 | this.status = status; 21 | this.timestamp = Instant.now(); 22 | } 23 | 24 | public List getMessages() { 25 | 26 | return messages == null ? null : new ArrayList<>(messages); 27 | } 28 | 29 | public final void setMessages(List messages) { 30 | 31 | if (messages == null) { 32 | this.messages = null; 33 | } else { 34 | this.messages = Collections.unmodifiableList(messages); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/AlbumService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.model.Album; 4 | import com.sopromadze.blogapi.payload.AlbumResponse; 5 | import com.sopromadze.blogapi.payload.ApiResponse; 6 | import com.sopromadze.blogapi.payload.PagedResponse; 7 | import com.sopromadze.blogapi.payload.request.AlbumRequest; 8 | import com.sopromadze.blogapi.security.UserPrincipal; 9 | import org.springframework.http.ResponseEntity; 10 | 11 | public interface AlbumService { 12 | 13 | PagedResponse getAllAlbums(int page, int size); 14 | 15 | ResponseEntity addAlbum(AlbumRequest albumRequest, UserPrincipal currentUser); 16 | 17 | ResponseEntity getAlbum(Long id); 18 | 19 | ResponseEntity updateAlbum(Long id, AlbumRequest newAlbum, UserPrincipal currentUser); 20 | 21 | ResponseEntity deleteAlbum(Long id, UserPrincipal currentUser); 22 | 23 | PagedResponse getUserAlbums(String username, int page, int size); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | #### Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | #### Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | #### How Has This Been Tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. 19 | 20 | #### Checklist: 21 | 22 | - [ ] I have performed a self-review of my own code 23 | - [ ] I have made corresponding changes to the documentation 24 | - [ ] My changes generate no new warnings or errors 25 | - [ ] New and existing unit tests pass locally with my changes 26 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/PostService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.model.Post; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.PagedResponse; 6 | import com.sopromadze.blogapi.payload.PostRequest; 7 | import com.sopromadze.blogapi.payload.PostResponse; 8 | import com.sopromadze.blogapi.security.UserPrincipal; 9 | 10 | public interface PostService { 11 | 12 | PagedResponse getAllPosts(int page, int size); 13 | 14 | PagedResponse getPostsByCreatedBy(String username, int page, int size); 15 | 16 | PagedResponse getPostsByCategory(Long id, int page, int size); 17 | 18 | PagedResponse getPostsByTag(Long id, int page, int size); 19 | 20 | Post updatePost(Long id, PostRequest newPostRequest, UserPrincipal currentUser); 21 | 22 | ApiResponse deletePost(Long id, UserPrincipal currentUser); 23 | 24 | PostResponse addPost(PostRequest postRequest, UserPrincipal currentUser); 25 | 26 | Post getPost(Long id); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/AlbumResponse.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 5 | import com.sopromadze.blogapi.model.Photo; 6 | import com.sopromadze.blogapi.model.user.User; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | @EqualsAndHashCode(callSuper = true) 15 | @Data 16 | @JsonInclude(Include.NON_NULL) 17 | public class AlbumResponse extends UserDateAuditPayload { 18 | private Long id; 19 | 20 | private String title; 21 | 22 | private User user; 23 | 24 | private List photo; 25 | 26 | public List getPhoto() { 27 | 28 | return photo == null ? null : new ArrayList<>(photo); 29 | } 30 | 31 | public void setPhoto(List photo) { 32 | 33 | if (photo == null) { 34 | this.photo = null; 35 | } else { 36 | this.photo = Collections.unmodifiableList(photo); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/audit/DateAudit.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.audit; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Data; 5 | import org.springframework.data.annotation.CreatedDate; 6 | import org.springframework.data.annotation.LastModifiedDate; 7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.EntityListeners; 11 | import javax.persistence.MappedSuperclass; 12 | import java.io.Serializable; 13 | import java.time.Instant; 14 | 15 | @MappedSuperclass 16 | @Data 17 | @EntityListeners(AuditingEntityListener.class) 18 | @JsonIgnoreProperties( 19 | value = { "createdAt", "updatedAt" }, 20 | allowGetters = true 21 | ) 22 | public abstract class DateAudit implements Serializable { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | @CreatedDate 27 | @Column(nullable = false, updatable = false) 28 | private Instant createdAt; 29 | 30 | @LastModifiedDate 31 | @Column(nullable = false) 32 | private Instant updatedAt; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/ApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import lombok.Data; 7 | import org.springframework.http.HttpStatus; 8 | 9 | import java.io.Serializable; 10 | 11 | @Data 12 | @JsonPropertyOrder({ 13 | "success", 14 | "message" 15 | }) 16 | public class ApiResponse implements Serializable { 17 | 18 | @JsonIgnore 19 | private static final long serialVersionUID = 7702134516418120340L; 20 | 21 | @JsonProperty("success") 22 | private Boolean success; 23 | 24 | @JsonProperty("message") 25 | private String message; 26 | 27 | @JsonIgnore 28 | private HttpStatus status; 29 | 30 | public ApiResponse() { 31 | 32 | } 33 | 34 | public ApiResponse(Boolean success, String message) { 35 | this.success = success; 36 | this.message = message; 37 | } 38 | 39 | public ApiResponse(Boolean success, String message, HttpStatus httpStatus) { 40 | this.success = success; 41 | this.message = message; 42 | this.status = httpStatus; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.security; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.security.core.AuthenticationException; 6 | import org.springframework.security.web.AuthenticationEntryPoint; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | 14 | @Component 15 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 16 | private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); 17 | 18 | @Override 19 | public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) 20 | throws IOException, ServletException { 21 | LOGGER.error("Responding with unauthorized error. Message - {}", e.getMessage()); 22 | httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Sorry, You're not authorized to access this resource."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/payload/PagedResponse.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.payload; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | @Data 10 | public class PagedResponse { 11 | private List content; 12 | private int page; 13 | private int size; 14 | private long totalElements; 15 | private int totalPages; 16 | private boolean last; 17 | 18 | public PagedResponse() { 19 | 20 | } 21 | 22 | public PagedResponse(List content, int page, int size, long totalElements, int totalPages, boolean last) { 23 | setContent(content); 24 | this.page = page; 25 | this.size = size; 26 | this.totalElements = totalElements; 27 | this.totalPages = totalPages; 28 | this.last = last; 29 | } 30 | 31 | public List getContent() { 32 | return content == null ? null : new ArrayList<>(content); 33 | } 34 | 35 | public final void setContent(List content) { 36 | if (content == null) { 37 | this.content = null; 38 | } else { 39 | this.content = Collections.unmodifiableList(content); 40 | } 41 | } 42 | 43 | 44 | 45 | public boolean isLast() { 46 | return last; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service; 2 | 3 | import com.sopromadze.blogapi.model.user.User; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.InfoRequest; 6 | import com.sopromadze.blogapi.payload.UserIdentityAvailability; 7 | import com.sopromadze.blogapi.payload.UserProfile; 8 | import com.sopromadze.blogapi.payload.UserSummary; 9 | import com.sopromadze.blogapi.security.UserPrincipal; 10 | 11 | public interface UserService { 12 | 13 | UserSummary getCurrentUser(UserPrincipal currentUser); 14 | 15 | UserIdentityAvailability checkUsernameAvailability(String username); 16 | 17 | UserIdentityAvailability checkEmailAvailability(String email); 18 | 19 | UserProfile getUserProfile(String username); 20 | 21 | User addUser(User user); 22 | 23 | User updateUser(User newUser, String username, UserPrincipal currentUser); 24 | 25 | ApiResponse deleteUser(String username, UserPrincipal currentUser); 26 | 27 | ApiResponse giveAdmin(String username); 28 | 29 | ApiResponse removeAdmin(String username); 30 | 31 | UserProfile setOrUpdateInfo(UserPrincipal currentUser, InfoRequest infoRequest); 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/BlogApiApplication.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi; 2 | 3 | import com.sopromadze.blogapi.security.JwtAuthenticationFilter; 4 | import org.modelmapper.ModelMapper; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.autoconfigure.domain.EntityScan; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.data.convert.Jsr310Converters; 10 | 11 | import javax.annotation.PostConstruct; 12 | import java.util.TimeZone; 13 | 14 | @SpringBootApplication 15 | @EntityScan(basePackageClasses = { BlogApiApplication.class, Jsr310Converters.class }) 16 | 17 | public class BlogApiApplication { 18 | 19 | public static void main(String[] args) { 20 | SpringApplication.run(BlogApiApplication.class, args); 21 | } 22 | 23 | @PostConstruct 24 | void init() { 25 | TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 26 | } 27 | 28 | @Bean 29 | public JwtAuthenticationFilter jwtAuthenticationFilter() { 30 | return new JwtAuthenticationFilter(); 31 | } 32 | 33 | @Bean 34 | public ModelMapper modelMapper() { 35 | return new ModelMapper(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.repository; 2 | 3 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 4 | import com.sopromadze.blogapi.model.user.User; 5 | import com.sopromadze.blogapi.security.UserPrincipal; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import javax.validation.constraints.NotBlank; 10 | import java.util.Optional; 11 | 12 | @Repository 13 | public interface UserRepository extends JpaRepository { 14 | Optional findByUsername(@NotBlank String username); 15 | 16 | Optional findByEmail(@NotBlank String email); 17 | 18 | Boolean existsByUsername(@NotBlank String username); 19 | 20 | Boolean existsByEmail(@NotBlank String email); 21 | 22 | Optional findByUsernameOrEmail(String username, String email); 23 | 24 | default User getUser(UserPrincipal currentUser) { 25 | return getUserByName(currentUser.getUsername()); 26 | } 27 | 28 | default User getUserByName(String username) { 29 | return findByUsername(username) 30 | .orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | dist: trusty 3 | os: linux 4 | addons: 5 | sonarcloud: 6 | organization: "coma123" 7 | token: 8 | secure: "fVilZ8rDtzqhxL7nEXCksKTnxdQXL2Ab5pSmpL0zZHw3zKsiKMWtuzMzIO11g+LIy6P+5sT+3ZAdcEwFeFM02R/CMpGmCIwHqNw7AvPeK8Vl6zeUEmAiV4yboIGL/7AksmSHx2nHZiQagUrLZKFhHaM3YpMmyhhbxuSxJNkZ95NPr43I5TfT7mqDWiaY/Hhmg29N8qjAO51j21j4eZwQLJIDlfnCUcGbTMHmlKsfJIuMcVPUQIE5w5rDaiMDyDdOZxlY/IKXi6JDGHP/TRthFk8zddqVj5qtzkCNKpl0K7ReMHdJb6aTSIsn6IVD+fHRev5ik2FF7HZ8DF0lC/0S62bVcmzTuF0WEWaWNre3l69dB0HBKY0GDxA4hskhVjRSMKjGKrH5Bx/LVXAo2a+U+uIO8ZmB4P7S4rwNF34ZnTke/yw7OmtesZWCDXZNzoidq59BRPL4RkkG0J1FMcWC47AsdnJC1JOX/bdunJ8Nmj2Lxil0RcLgDKK/L0La3UnNE8iKH8gE2E+rwHpKfF0wNt/mzpJYzXJ/l9DwRFyTsJoaVxDshIyM709Hp91CGy2JuNI7FeZZJK3laDmE4dHpr88MOvlPNHhvuxQG1MXUNNI1H2n1ZQlH8p9ZBgouG1LmRtY3GeyiuoSbQWmtUq4iAhb5F5G5/uHwS+BrLLNZSHo=" 9 | script: 10 | # the following command line builds the project, runs the tests with coverage and then execute the SonarCloud analysis 11 | - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.projectKey=coma123_Spring-Boot-Blog-REST-API 12 | 13 | before_install: 14 | - chmod +x mvnw 15 | cache: 16 | directories: 17 | - '$HOME/.m2/repository' 18 | - '$HOME/.sonar/cache' 19 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/AccessDeniedException.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | @ResponseStatus(code = HttpStatus.UNAUTHORIZED) 8 | public class AccessDeniedException extends RuntimeException { 9 | private static final long serialVersionUID = 1L; 10 | 11 | private ApiResponse apiResponse; 12 | 13 | private String message; 14 | 15 | public AccessDeniedException(ApiResponse apiResponse) { 16 | super(); 17 | this.apiResponse = apiResponse; 18 | } 19 | 20 | public AccessDeniedException(String message) { 21 | super(message); 22 | this.message = message; 23 | } 24 | 25 | public AccessDeniedException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | public ApiResponse getApiResponse() { 30 | return apiResponse; 31 | } 32 | 33 | public void setApiResponse(ApiResponse apiResponse) { 34 | this.apiResponse = apiResponse; 35 | } 36 | 37 | public String getMessage() { 38 | return message; 39 | } 40 | 41 | public void setMessage(String message) { 42 | this.message = message; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/UnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | @ResponseStatus(code = HttpStatus.UNAUTHORIZED) 8 | public class UnauthorizedException extends RuntimeException { 9 | private static final long serialVersionUID = 1L; 10 | 11 | private ApiResponse apiResponse; 12 | 13 | private String message; 14 | 15 | public UnauthorizedException(ApiResponse apiResponse) { 16 | super(); 17 | this.apiResponse = apiResponse; 18 | } 19 | 20 | public UnauthorizedException(String message) { 21 | super(message); 22 | this.message = message; 23 | } 24 | 25 | public UnauthorizedException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | public ApiResponse getApiResponse() { 30 | return apiResponse; 31 | } 32 | 33 | public void setApiResponse(ApiResponse apiResponse) { 34 | this.apiResponse = apiResponse; 35 | } 36 | 37 | public String getMessage() { 38 | return message; 39 | } 40 | 41 | public void setMessage(String message) { 42 | this.message = message; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | @ResponseStatus(value = HttpStatus.NOT_FOUND) 8 | public class ResourceNotFoundException extends RuntimeException { 9 | private static final long serialVersionUID = 1L; 10 | 11 | private transient ApiResponse apiResponse; 12 | 13 | private String resourceName; 14 | private String fieldName; 15 | private Object fieldValue; 16 | 17 | public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) { 18 | super(); 19 | this.resourceName = resourceName; 20 | this.fieldName = fieldName; 21 | this.fieldValue = fieldValue; 22 | } 23 | 24 | public String getResourceName() { 25 | return resourceName; 26 | } 27 | 28 | public String getFieldName() { 29 | return fieldName; 30 | } 31 | 32 | public Object getFieldValue() { 33 | return fieldValue; 34 | } 35 | 36 | public ApiResponse getApiResponse() { 37 | return apiResponse; 38 | } 39 | 40 | private void setApiResponse() { 41 | String message = String.format("%s not found with %s: '%s'", resourceName, fieldName, fieldValue); 42 | 43 | apiResponse = new ApiResponse(Boolean.FALSE, message); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/config/AuditingConfig.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.config; 2 | 3 | import com.sopromadze.blogapi.security.UserPrincipal; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.domain.AuditorAware; 7 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 8 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.core.context.SecurityContextHolder; 11 | 12 | import java.util.Optional; 13 | 14 | @Configuration 15 | @EnableJpaAuditing 16 | public class AuditingConfig { 17 | 18 | @Bean 19 | public AuditorAware auditorProvider() { 20 | return new SpringSecurityAuditAwareImpl(); 21 | } 22 | } 23 | 24 | class SpringSecurityAuditAwareImpl implements AuditorAware { 25 | 26 | @Override 27 | public Optional getCurrentAuditor() { 28 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 29 | 30 | if (authentication == null || !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken) { 31 | return Optional.empty(); 32 | } 33 | 34 | UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); 35 | 36 | return Optional.ofNullable(userPrincipal.getId()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/Todo.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import com.sopromadze.blogapi.model.user.User; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.FetchType; 12 | import javax.persistence.GeneratedValue; 13 | import javax.persistence.GenerationType; 14 | import javax.persistence.Id; 15 | import javax.persistence.JoinColumn; 16 | import javax.persistence.ManyToOne; 17 | import javax.persistence.Table; 18 | import javax.persistence.UniqueConstraint; 19 | import javax.validation.constraints.NotBlank; 20 | 21 | @EqualsAndHashCode(callSuper = true) 22 | @Entity 23 | @Data 24 | @Table(name = "todos", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) 25 | public class Todo extends UserDateAudit { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | @Id 30 | @GeneratedValue(strategy = GenerationType.IDENTITY) 31 | private Long id; 32 | 33 | @NotBlank 34 | @Column(name = "title") 35 | private String title; 36 | 37 | @Column(name = "completed") 38 | private Boolean completed; 39 | 40 | @ManyToOne(fetch = FetchType.LAZY) 41 | @JoinColumn(name = "user_id") 42 | private User user; 43 | 44 | @JsonIgnore 45 | public User getUser() { 46 | return user; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/CustomUserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.model.user.User; 4 | import com.sopromadze.blogapi.repository.UserRepository; 5 | import com.sopromadze.blogapi.security.UserPrincipal; 6 | import com.sopromadze.blogapi.service.CustomUserDetailsService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Service; 12 | 13 | import javax.transaction.Transactional; 14 | 15 | @Service 16 | public class CustomUserDetailsServiceImpl implements UserDetailsService, CustomUserDetailsService { 17 | @Autowired 18 | private UserRepository userRepository; 19 | 20 | @Override 21 | @Transactional 22 | public UserDetails loadUserByUsername(String usernameOrEmail) { 23 | User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail) 24 | .orElseThrow(() -> new UsernameNotFoundException(String.format("User not found with this username or email: %s", usernameOrEmail))); 25 | return UserPrincipal.create(user); 26 | } 27 | 28 | @Override 29 | @Transactional 30 | public UserDetails loadUserById(Long id) { 31 | User user = userRepository.findById(id).orElseThrow(() -> new UsernameNotFoundException(String.format("User not found with id: %s", id))); 32 | 33 | return UserPrincipal.create(user); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/Category.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 5 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import javax.persistence.CascadeType; 11 | import javax.persistence.Column; 12 | import javax.persistence.Entity; 13 | import javax.persistence.GeneratedValue; 14 | import javax.persistence.GenerationType; 15 | import javax.persistence.Id; 16 | import javax.persistence.OneToMany; 17 | import javax.persistence.Table; 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | @EqualsAndHashCode(callSuper = true) 23 | @Entity 24 | @Data 25 | @NoArgsConstructor 26 | @Table(name = "categories") 27 | @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") 28 | public class Category extends UserDateAudit { 29 | private static final long serialVersionUID = 1L; 30 | 31 | @Id 32 | @GeneratedValue(strategy = GenerationType.IDENTITY) 33 | private Long id; 34 | 35 | @Column(name = "name") 36 | private String name; 37 | 38 | @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) 39 | private List posts; 40 | 41 | public Category(String name) { 42 | super(); 43 | this.name = name; 44 | } 45 | 46 | public List getPosts() { 47 | return this.posts == null ? null : new ArrayList<>(this.posts); 48 | } 49 | 50 | public void setPosts(List posts) { 51 | if (posts == null) { 52 | this.posts = null; 53 | } else { 54 | this.posts = Collections.unmodifiableList(posts); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/Photo.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.FetchType; 12 | import javax.persistence.GeneratedValue; 13 | import javax.persistence.GenerationType; 14 | import javax.persistence.Id; 15 | import javax.persistence.JoinColumn; 16 | import javax.persistence.ManyToOne; 17 | import javax.persistence.Table; 18 | import javax.persistence.UniqueConstraint; 19 | import javax.validation.constraints.NotBlank; 20 | 21 | @EqualsAndHashCode(callSuper = true) 22 | @Entity 23 | @Data 24 | @NoArgsConstructor 25 | @Table(name = "photos", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) 26 | public class Photo extends UserDateAudit { 27 | private static final long serialVersionUID = 1L; 28 | 29 | @Id 30 | @GeneratedValue(strategy = GenerationType.IDENTITY) 31 | private Long id; 32 | 33 | @NotBlank 34 | @Column(name = "title") 35 | private String title; 36 | 37 | @NotBlank 38 | @Column(name = "url") 39 | private String url; 40 | 41 | @NotBlank 42 | @Column(name = "thumbnail_url") 43 | private String thumbnailUrl; 44 | 45 | @ManyToOne(fetch = FetchType.LAZY) 46 | @JoinColumn(name = "album_id") 47 | private Album album; 48 | 49 | public Photo(@NotBlank String title, @NotBlank String url, @NotBlank String thumbnailUrl, Album album) { 50 | this.title = title; 51 | this.url = url; 52 | this.thumbnailUrl = thumbnailUrl; 53 | this.album = album; 54 | } 55 | 56 | @JsonIgnore 57 | public Album getAlbum() { 58 | return album; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # :snowman: How to Contribute :snowman: 2 | 3 | We are quite glad you made it here! We can only keep our project in good health thanks to contributors like you! There are some points 4 | we would like to highlight in terms of contributing to this repository. 5 | 6 | ## Bug Reports and New Features 7 | 8 | - If you have any issue please report that using [Github Issues](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues). 9 | Currently there's two kinds; Bug Reports and New Features. 10 | 11 | - When writing a Bug Report or asking for a New Feature, please follow the template provided. Fill in the sections as much as you could so 12 | we have all teh information we need. 13 | 14 | ## Coding Contributions 15 | 16 | - All coding contributions should be done through [pull requests](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork). We do not respond to emails or coding posted via our Gitter channel. If you have any suggessions or want to contribute please open a [issue](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues) first. This way we could discuss about the change and once finalized you could do a pull request. 17 | 18 | - We don't have a comprehensive style guide for now, but we expect you code with due deligence with standard practices. Your code 19 | should pass the standard unit and integration tests (automatically run by Travis). 20 | 21 | - Also please make sure your tests pass the [Sonar](https://sonarcloud.io/dashboard?id=coma123_Spring-Boot-Blog-REST-API) quality 22 | analysis. We strive to maintain A grade for all sections. 23 | 24 | :snowman: This is all for now. Hope to update this document as we go along. If you have any suggestions please feel free to open 25 | an issue in our [issue tracker](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues). :snowman: 26 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/Album.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import com.sopromadze.blogapi.model.user.User; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | import javax.persistence.CascadeType; 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.FetchType; 13 | import javax.persistence.GeneratedValue; 14 | import javax.persistence.GenerationType; 15 | import javax.persistence.Id; 16 | import javax.persistence.JoinColumn; 17 | import javax.persistence.ManyToOne; 18 | import javax.persistence.OneToMany; 19 | import javax.persistence.Table; 20 | import javax.persistence.UniqueConstraint; 21 | import javax.validation.constraints.NotBlank; 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | @EqualsAndHashCode(callSuper = true) 27 | @Entity 28 | @Data 29 | @Table(name = "albums", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) 30 | public class Album extends UserDateAudit { 31 | private static final long serialVersionUID = 1L; 32 | 33 | @Id 34 | @GeneratedValue(strategy = GenerationType.IDENTITY) 35 | private Long id; 36 | 37 | @NotBlank 38 | @Column(name = "title") 39 | private String title; 40 | 41 | @ManyToOne(fetch = FetchType.LAZY) 42 | @JoinColumn(name = "user_id") 43 | private User user; 44 | 45 | @OneToMany(mappedBy = "album", cascade = CascadeType.ALL, orphanRemoval = true) 46 | private List photo; 47 | 48 | @JsonIgnore 49 | public User getUser() { 50 | return user; 51 | } 52 | 53 | public List getPhoto() { 54 | return this.photo == null ? null : new ArrayList<>(this.photo); 55 | } 56 | 57 | public void setPhoto(List photo) { 58 | if (photo == null) { 59 | this.photo = null; 60 | } else { 61 | this.photo = Collections.unmodifiableList(photo); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/Tag.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import com.sopromadze.blogapi.model.Post; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.FetchType; 13 | import javax.persistence.GeneratedValue; 14 | import javax.persistence.GenerationType; 15 | import javax.persistence.Id; 16 | import javax.persistence.JoinColumn; 17 | import javax.persistence.JoinTable; 18 | import javax.persistence.ManyToMany; 19 | import javax.persistence.Table; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | @EqualsAndHashCode(callSuper = true) 25 | @Entity 26 | @Data 27 | @NoArgsConstructor 28 | @Table(name = "tags") 29 | //@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") 30 | public class Tag extends UserDateAudit { 31 | 32 | private static final long serialVersionUID = -5298707266367331514L; 33 | 34 | @Id 35 | @GeneratedValue(strategy = GenerationType.IDENTITY) 36 | private Long id; 37 | 38 | @Column(name = "name") 39 | private String name; 40 | 41 | @JsonIgnore 42 | @ManyToMany(fetch = FetchType.EAGER) 43 | @JoinTable(name = "post_tag", joinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "post_id", referencedColumnName = "id")) 44 | private List posts; 45 | 46 | public Tag(String name) { 47 | super(); 48 | this.name = name; 49 | } 50 | 51 | public List getPosts() { 52 | return posts == null ? null : new ArrayList<>(posts); 53 | } 54 | 55 | public void setPosts(List posts) { 56 | if (posts == null) { 57 | this.posts = null; 58 | } else { 59 | this.posts = Collections.unmodifiableList(posts); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/Comment.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import com.sopromadze.blogapi.model.user.User; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.FetchType; 13 | import javax.persistence.GeneratedValue; 14 | import javax.persistence.GenerationType; 15 | import javax.persistence.Id; 16 | import javax.persistence.JoinColumn; 17 | import javax.persistence.ManyToOne; 18 | import javax.persistence.Table; 19 | import javax.validation.constraints.Email; 20 | import javax.validation.constraints.NotBlank; 21 | import javax.validation.constraints.Size; 22 | 23 | @EqualsAndHashCode(callSuper = true) 24 | @Entity 25 | @Data 26 | @NoArgsConstructor 27 | @Table(name = "comments") 28 | public class Comment extends UserDateAudit { 29 | private static final long serialVersionUID = 1L; 30 | 31 | @Id 32 | @GeneratedValue(strategy = GenerationType.IDENTITY) 33 | private Long id; 34 | 35 | @Column(name = "name") 36 | @NotBlank 37 | @Size(min = 4, max = 50) 38 | private String name; 39 | 40 | @Column(name = "email") 41 | @NotBlank 42 | @Email 43 | @Size(min = 4, max = 50) 44 | private String email; 45 | 46 | @Column(name = "body") 47 | @NotBlank 48 | @Size(min = 10, message = "Comment body must be minimum 10 characters") 49 | private String body; 50 | 51 | @ManyToOne(fetch = FetchType.LAZY) 52 | @JoinColumn(name = "post_id") 53 | private Post post; 54 | 55 | @ManyToOne(fetch = FetchType.LAZY) 56 | @JoinColumn(name = "user_id") 57 | private User user; 58 | 59 | public Comment(@NotBlank @Size(min = 10, message = "Comment body must be minimum 10 characters") String body) { 60 | this.body = body; 61 | } 62 | 63 | @JsonIgnore 64 | public Post getPost() { 65 | return post; 66 | } 67 | 68 | @JsonIgnore 69 | public User getUser() { 70 | return user; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/user/Geo.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.OneToOne; 15 | import javax.persistence.Table; 16 | import java.time.Instant; 17 | 18 | @EqualsAndHashCode(callSuper = true) 19 | @Entity 20 | @Data 21 | @NoArgsConstructor 22 | @Table(name = "geo") 23 | public class Geo extends UserDateAudit { 24 | private static final long serialVersionUID = 1L; 25 | 26 | @JsonIgnore 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.IDENTITY) 29 | private Long id; 30 | 31 | @Column(name = "lat") 32 | private String lat; 33 | 34 | @Column(name = "lng") 35 | private String lng; 36 | 37 | @OneToOne(mappedBy = "geo") 38 | private Address address; 39 | 40 | public Geo(String lat, String lng) { 41 | this.lat = lat; 42 | this.lng = lng; 43 | } 44 | 45 | @JsonIgnore 46 | @Override 47 | public Long getCreatedBy() { 48 | return super.getCreatedBy(); 49 | } 50 | 51 | @JsonIgnore 52 | @Override 53 | public void setCreatedBy(Long createdBy) { 54 | super.setCreatedBy(createdBy); 55 | } 56 | 57 | @JsonIgnore 58 | @Override 59 | public Long getUpdatedBy() { 60 | return super.getUpdatedBy(); 61 | } 62 | 63 | @JsonIgnore 64 | @Override 65 | public void setUpdatedBy(Long updatedBy) { 66 | super.setUpdatedBy(updatedBy); 67 | } 68 | 69 | @JsonIgnore 70 | @Override 71 | public Instant getCreatedAt() { 72 | return super.getCreatedAt(); 73 | } 74 | 75 | @JsonIgnore 76 | @Override 77 | public void setCreatedAt(Instant createdAt) { 78 | super.setCreatedAt(createdAt); 79 | } 80 | 81 | @JsonIgnore 82 | @Override 83 | public Instant getUpdatedAt() { 84 | return super.getUpdatedAt(); 85 | } 86 | 87 | @JsonIgnore 88 | @Override 89 | public void setUpdatedAt(Instant updatedAt) { 90 | super.setUpdatedAt(updatedAt); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/security/JwtTokenProvider.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.security; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.ExpiredJwtException; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.MalformedJwtException; 7 | import io.jsonwebtoken.SignatureAlgorithm; 8 | import io.jsonwebtoken.SignatureException; 9 | import io.jsonwebtoken.UnsupportedJwtException; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.security.core.Authentication; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.util.Date; 17 | 18 | @Component 19 | public class JwtTokenProvider { 20 | private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenProvider.class); 21 | 22 | @Value(value = "${app.jwtSecret}") 23 | private String jwtSecret; 24 | 25 | @Value(value = "${app.jwtExpirationInMs}") 26 | private int jwtExpirationInMs; 27 | 28 | public String generateToken(Authentication authentication) { 29 | UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); 30 | 31 | Date now = new Date(); 32 | Date expiryDate = new Date(now.getTime() + jwtExpirationInMs); 33 | 34 | return Jwts.builder() 35 | .setSubject(Long.toString(userPrincipal.getId())) 36 | .setIssuedAt(new Date()) 37 | .setExpiration(expiryDate) 38 | .signWith(SignatureAlgorithm.HS512, jwtSecret) 39 | .compact(); 40 | } 41 | 42 | public Long getUserIdFromJWT(String token) { 43 | Claims claims = Jwts.parser() 44 | .setSigningKey(jwtSecret) 45 | .parseClaimsJws(token) 46 | .getBody(); 47 | 48 | return Long.valueOf(claims.getSubject()); 49 | } 50 | 51 | public boolean validateToken(String authToken) { 52 | try { 53 | Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); 54 | return true; 55 | } catch (SignatureException ex) { 56 | LOGGER.error("Invalid JWT signature"); 57 | } catch (MalformedJwtException ex) { 58 | LOGGER.error("Invalid JWT token"); 59 | } catch (ExpiredJwtException ex) { 60 | LOGGER.error("Expired JWT token"); 61 | } catch (UnsupportedJwtException ex) { 62 | LOGGER.error("Unsupported JWT token"); 63 | } catch (IllegalArgumentException ex) { 64 | LOGGER.error("JWT claims string is empty"); 65 | } 66 | return false; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.security; 2 | 3 | import com.sopromadze.blogapi.service.CustomUserDetailsService; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 11 | import org.springframework.util.StringUtils; 12 | import org.springframework.web.filter.OncePerRequestFilter; 13 | 14 | import javax.servlet.FilterChain; 15 | import javax.servlet.ServletException; 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.io.IOException; 19 | 20 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationFilter.class); 22 | @Autowired 23 | private JwtTokenProvider tokenProvider; 24 | @Autowired 25 | private CustomUserDetailsService customUserDetailsService; 26 | 27 | @Override 28 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 29 | throws ServletException, IOException { 30 | try { 31 | String jwt = getJwtFromRequest(request); 32 | 33 | if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { 34 | Long userId = tokenProvider.getUserIdFromJWT(jwt); 35 | 36 | UserDetails userDetails = customUserDetailsService.loadUserById(userId); 37 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, 38 | userDetails.getAuthorities()); 39 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 40 | 41 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 42 | } 43 | } catch (Exception ex) { 44 | LOGGER.error("Could not set user authentication in security context", ex); 45 | } 46 | 47 | filterChain.doFilter(request, response); 48 | } 49 | 50 | private String getJwtFromRequest(HttpServletRequest request) { 51 | String bearerToken = request.getHeader("Authorization"); 52 | if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { 53 | return bearerToken.substring(7, bearerToken.length()); 54 | } 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/user/Company.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.OneToOne; 15 | import javax.persistence.Table; 16 | import java.time.Instant; 17 | 18 | @EqualsAndHashCode(callSuper = true) 19 | @Entity 20 | @Data 21 | @NoArgsConstructor 22 | @Table(name = "company") 23 | public class Company extends UserDateAudit { 24 | private static final long serialVersionUID = 1L; 25 | 26 | @Id 27 | @GeneratedValue(strategy = GenerationType.IDENTITY) 28 | private Long id; 29 | 30 | @Column(name = "name") 31 | private String name; 32 | 33 | @Column(name = "catch_phrase") 34 | private String catchPhrase; 35 | 36 | @Column(name = "bs") 37 | private String bs; 38 | 39 | @OneToOne(mappedBy = "company") 40 | private User user; 41 | 42 | 43 | public Company(String name, String catchPhrase, String bs) { 44 | this.name = name; 45 | this.catchPhrase = catchPhrase; 46 | this.bs = bs; 47 | } 48 | 49 | @JsonIgnore 50 | public Long getId() { 51 | return id; 52 | } 53 | 54 | public void setId(Long id) { 55 | this.id = id; 56 | } 57 | 58 | @JsonIgnore 59 | @Override 60 | public Long getCreatedBy() { 61 | return super.getCreatedBy(); 62 | } 63 | 64 | @JsonIgnore 65 | @Override 66 | public void setCreatedBy(Long createdBy) { 67 | super.setCreatedBy(createdBy); 68 | } 69 | 70 | @JsonIgnore 71 | @Override 72 | public Long getUpdatedBy() { 73 | return super.getUpdatedBy(); 74 | } 75 | 76 | @JsonIgnore 77 | @Override 78 | public void setUpdatedBy(Long updatedBy) { 79 | super.setUpdatedBy(updatedBy); 80 | } 81 | 82 | @JsonIgnore 83 | @Override 84 | public Instant getCreatedAt() { 85 | return super.getCreatedAt(); 86 | } 87 | 88 | @JsonIgnore 89 | @Override 90 | public void setCreatedAt(Instant createdAt) { 91 | super.setCreatedAt(createdAt); 92 | } 93 | 94 | @JsonIgnore 95 | @Override 96 | public Instant getUpdatedAt() { 97 | return super.getUpdatedAt(); 98 | } 99 | 100 | @JsonIgnore 101 | @Override 102 | public void setUpdatedAt(Instant updatedAt) { 103 | super.setUpdatedAt(updatedAt); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/user/Address.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | 10 | import javax.persistence.CascadeType; 11 | import javax.persistence.Column; 12 | import javax.persistence.Entity; 13 | import javax.persistence.GeneratedValue; 14 | import javax.persistence.GenerationType; 15 | import javax.persistence.Id; 16 | import javax.persistence.JoinColumn; 17 | import javax.persistence.OneToOne; 18 | import javax.persistence.Table; 19 | import java.time.Instant; 20 | 21 | @EqualsAndHashCode(callSuper = true) 22 | @Entity 23 | @Data 24 | @NoArgsConstructor 25 | @Table(name = "address") 26 | public class Address extends UserDateAudit { 27 | private static final long serialVersionUID = 1L; 28 | 29 | @Id 30 | @GeneratedValue(strategy = GenerationType.IDENTITY) 31 | private Long id; 32 | 33 | @Column(name = "street") 34 | private String street; 35 | 36 | @Column(name = "suite") 37 | private String suite; 38 | 39 | @Column(name = "city") 40 | private String city; 41 | 42 | @Column(name = "zipcode") 43 | private String zipcode; 44 | 45 | @OneToOne(cascade = CascadeType.ALL) 46 | @JoinColumn(name = "geo_id") 47 | private Geo geo; 48 | 49 | @OneToOne(mappedBy = "address") 50 | private User user; 51 | 52 | public Address(String street, String suite, String city, String zipcode, Geo geo) { 53 | this.street = street; 54 | this.suite = suite; 55 | this.city = city; 56 | this.zipcode = zipcode; 57 | this.geo = geo; 58 | } 59 | 60 | @JsonIgnore 61 | public Long getId() { 62 | return id; 63 | } 64 | 65 | @JsonIgnore 66 | @Override 67 | public Long getCreatedBy() { 68 | return super.getCreatedBy(); 69 | } 70 | 71 | @JsonIgnore 72 | @Override 73 | public void setCreatedBy(Long createdBy) { 74 | super.setCreatedBy(createdBy); 75 | } 76 | 77 | @JsonIgnore 78 | @Override 79 | public Long getUpdatedBy() { 80 | return super.getUpdatedBy(); 81 | } 82 | 83 | @JsonIgnore 84 | @Override 85 | public void setUpdatedBy(Long updatedBy) { 86 | super.setUpdatedBy(updatedBy); 87 | } 88 | 89 | @JsonIgnore 90 | @Override 91 | public Instant getCreatedAt() { 92 | return super.getCreatedAt(); 93 | } 94 | 95 | @JsonIgnore 96 | @Override 97 | public void setCreatedAt(Instant createdAt) { 98 | super.setCreatedAt(createdAt); 99 | } 100 | 101 | @JsonIgnore 102 | @Override 103 | public Instant getUpdatedAt() { 104 | return super.getUpdatedAt(); 105 | } 106 | 107 | @JsonIgnore 108 | @Override 109 | public void setUpdatedAt(Instant updatedAt) { 110 | super.setUpdatedAt(updatedAt); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/CategoryController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.exception.UnauthorizedException; 4 | import com.sopromadze.blogapi.model.Category; 5 | import com.sopromadze.blogapi.payload.ApiResponse; 6 | import com.sopromadze.blogapi.payload.PagedResponse; 7 | import com.sopromadze.blogapi.security.CurrentUser; 8 | import com.sopromadze.blogapi.security.UserPrincipal; 9 | import com.sopromadze.blogapi.service.CategoryService; 10 | import com.sopromadze.blogapi.utils.AppConstants; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.access.prepost.PreAuthorize; 14 | import org.springframework.web.bind.annotation.DeleteMapping; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.PutMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RequestParam; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | import javax.validation.Valid; 25 | 26 | @RestController 27 | @RequestMapping("/api/categories") 28 | public class CategoryController { 29 | @Autowired 30 | private CategoryService categoryService; 31 | 32 | @GetMapping 33 | public PagedResponse getAllCategories( 34 | @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 35 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 36 | return categoryService.getAllCategories(page, size); 37 | } 38 | 39 | @PostMapping 40 | @PreAuthorize("hasRole('USER')") 41 | public ResponseEntity addCategory(@Valid @RequestBody Category category, 42 | @CurrentUser UserPrincipal currentUser) { 43 | 44 | return categoryService.addCategory(category, currentUser); 45 | } 46 | 47 | @GetMapping("/{id}") 48 | public ResponseEntity getCategory(@PathVariable(name = "id") Long id) { 49 | return categoryService.getCategory(id); 50 | } 51 | 52 | @PutMapping("/{id}") 53 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 54 | public ResponseEntity updateCategory(@PathVariable(name = "id") Long id, 55 | @Valid @RequestBody Category category, @CurrentUser UserPrincipal currentUser) throws UnauthorizedException { 56 | return categoryService.updateCategory(id, category, currentUser); 57 | } 58 | 59 | @DeleteMapping("/{id}") 60 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 61 | public ResponseEntity deleteCategory(@PathVariable(name = "id") Long id, 62 | @CurrentUser UserPrincipal currentUser) throws UnauthorizedException { 63 | return categoryService.deleteCategory(id, currentUser); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/Post.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 6 | import com.sopromadze.blogapi.model.audit.UserDateAudit; 7 | import com.sopromadze.blogapi.model.user.User; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | 11 | import javax.persistence.CascadeType; 12 | import javax.persistence.Column; 13 | import javax.persistence.Entity; 14 | import javax.persistence.FetchType; 15 | import javax.persistence.GeneratedValue; 16 | import javax.persistence.GenerationType; 17 | import javax.persistence.Id; 18 | import javax.persistence.JoinColumn; 19 | import javax.persistence.JoinTable; 20 | import javax.persistence.ManyToMany; 21 | import javax.persistence.ManyToOne; 22 | import javax.persistence.OneToMany; 23 | import javax.persistence.Table; 24 | import javax.persistence.UniqueConstraint; 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.List; 28 | 29 | @EqualsAndHashCode(callSuper = true) 30 | @Entity 31 | @Data 32 | @Table(name = "posts", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) 33 | @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") 34 | public class Post extends UserDateAudit { 35 | private static final long serialVersionUID = 1L; 36 | 37 | @Id 38 | @GeneratedValue(strategy = GenerationType.IDENTITY) 39 | private Long id; 40 | 41 | @Column(name = "title") 42 | private String title; 43 | 44 | @Column(name = "body") 45 | private String body; 46 | 47 | @ManyToOne(fetch = FetchType.LAZY) 48 | @JoinColumn(name = "user_id") 49 | private User user; 50 | 51 | @ManyToOne(fetch = FetchType.LAZY) 52 | @JoinColumn(name = "category_id") 53 | private Category category; 54 | 55 | @JsonIgnore 56 | @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) 57 | private List comments; 58 | 59 | @ManyToMany(fetch = FetchType.LAZY) 60 | @JoinTable(name = "post_tag", joinColumns = @JoinColumn(name = "post_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")) 61 | private List tags; 62 | 63 | @JsonIgnore 64 | public User getUser() { 65 | return user; 66 | } 67 | 68 | public void setUser(User user) { 69 | this.user = user; 70 | } 71 | 72 | public List getComments() { 73 | return comments == null ? null : new ArrayList<>(comments); 74 | } 75 | 76 | public void setComments(List comments) { 77 | if (comments == null) { 78 | this.comments = null; 79 | } else { 80 | this.comments = Collections.unmodifiableList(comments); 81 | } 82 | } 83 | 84 | public List getTags() { 85 | return tags == null ? null : new ArrayList<>(tags); 86 | } 87 | 88 | public void setTags(List tags) { 89 | if (tags == null) { 90 | this.tags = null; 91 | } else { 92 | this.tags = Collections.unmodifiableList(tags); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/TagController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.model.Tag; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.PagedResponse; 6 | import com.sopromadze.blogapi.security.CurrentUser; 7 | import com.sopromadze.blogapi.security.UserPrincipal; 8 | import com.sopromadze.blogapi.service.TagService; 9 | import com.sopromadze.blogapi.utils.AppConstants; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.access.prepost.PreAuthorize; 14 | import org.springframework.web.bind.annotation.DeleteMapping; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.PutMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RequestParam; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | import javax.validation.Valid; 25 | 26 | @RestController 27 | @RequestMapping("/api/tags") 28 | public class TagController { 29 | @Autowired 30 | private TagService tagService; 31 | 32 | @GetMapping 33 | public ResponseEntity> getAllTags( 34 | @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 35 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 36 | 37 | PagedResponse response = tagService.getAllTags(page, size); 38 | 39 | return new ResponseEntity< >(response, HttpStatus.OK); 40 | } 41 | 42 | @PostMapping 43 | @PreAuthorize("hasRole('USER')") 44 | public ResponseEntity addTag(@Valid @RequestBody Tag tag, @CurrentUser UserPrincipal currentUser) { 45 | Tag newTag = tagService.addTag(tag, currentUser); 46 | 47 | return new ResponseEntity< >(newTag, HttpStatus.CREATED); 48 | } 49 | 50 | @GetMapping("/{id}") 51 | public ResponseEntity getTag(@PathVariable(name = "id") Long id) { 52 | Tag tag = tagService.getTag(id); 53 | 54 | return new ResponseEntity< >(tag, HttpStatus.OK); 55 | } 56 | 57 | @PutMapping("/{id}") 58 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 59 | public ResponseEntity updateTag(@PathVariable(name = "id") Long id, @Valid @RequestBody Tag tag, @CurrentUser UserPrincipal currentUser) { 60 | 61 | Tag updatedTag = tagService.updateTag(id, tag, currentUser); 62 | 63 | return new ResponseEntity< >(updatedTag, HttpStatus.OK); 64 | } 65 | 66 | @DeleteMapping("/{id}") 67 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 68 | public ResponseEntity deleteTag(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { 69 | ApiResponse apiResponse = tagService.deleteTag(id, currentUser); 70 | 71 | return new ResponseEntity< >(apiResponse, HttpStatus.OK); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/PhotoController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import com.sopromadze.blogapi.payload.PagedResponse; 5 | import com.sopromadze.blogapi.payload.PhotoRequest; 6 | import com.sopromadze.blogapi.payload.PhotoResponse; 7 | import com.sopromadze.blogapi.security.CurrentUser; 8 | import com.sopromadze.blogapi.security.UserPrincipal; 9 | import com.sopromadze.blogapi.service.PhotoService; 10 | import com.sopromadze.blogapi.utils.AppConstants; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.security.access.prepost.PreAuthorize; 15 | import org.springframework.web.bind.annotation.DeleteMapping; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.PutMapping; 20 | import org.springframework.web.bind.annotation.RequestBody; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RequestParam; 23 | import org.springframework.web.bind.annotation.RestController; 24 | 25 | import javax.validation.Valid; 26 | 27 | @RestController 28 | @RequestMapping("/api/photos") 29 | public class PhotoController { 30 | @Autowired 31 | private PhotoService photoService; 32 | 33 | @GetMapping 34 | public PagedResponse getAllPhotos( 35 | @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 36 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 37 | return photoService.getAllPhotos(page, size); 38 | } 39 | 40 | @PostMapping 41 | @PreAuthorize("hasRole('USER')") 42 | public ResponseEntity addPhoto(@Valid @RequestBody PhotoRequest photoRequest, 43 | @CurrentUser UserPrincipal currentUser) { 44 | PhotoResponse photoResponse = photoService.addPhoto(photoRequest, currentUser); 45 | 46 | return new ResponseEntity< >(photoResponse, HttpStatus.OK); 47 | } 48 | 49 | @GetMapping("/{id}") 50 | public ResponseEntity getPhoto(@PathVariable(name = "id") Long id) { 51 | PhotoResponse photoResponse = photoService.getPhoto(id); 52 | 53 | return new ResponseEntity< >(photoResponse, HttpStatus.OK); 54 | } 55 | 56 | @PutMapping("/{id}") 57 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 58 | public ResponseEntity updatePhoto(@PathVariable(name = "id") Long id, 59 | @Valid @RequestBody PhotoRequest photoRequest, @CurrentUser UserPrincipal currentUser) { 60 | 61 | PhotoResponse photoResponse = photoService.updatePhoto(id, photoRequest, currentUser); 62 | 63 | return new ResponseEntity< >(photoResponse, HttpStatus.OK); 64 | } 65 | 66 | @DeleteMapping("/{id}") 67 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 68 | public ResponseEntity deletePhoto(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { 69 | ApiResponse apiResponse = photoService.deletePhoto(id, currentUser); 70 | 71 | return new ResponseEntity< >(apiResponse, HttpStatus.OK); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/security/UserPrincipal.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.security; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.sopromadze.blogapi.model.user.User; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.stream.Collectors; 14 | 15 | public class UserPrincipal implements UserDetails { 16 | private static final long serialVersionUID = 1L; 17 | 18 | private Long id; 19 | 20 | private String firstName; 21 | 22 | private String lastName; 23 | 24 | private String username; 25 | 26 | @JsonIgnore 27 | private String email; 28 | 29 | @JsonIgnore 30 | private String password; 31 | 32 | private Collection authorities; 33 | 34 | public UserPrincipal(Long id, String firstName, String lastName, String username, String email, String password, 35 | Collection authorities) { 36 | this.id = id; 37 | this.firstName = firstName; 38 | this.lastName = lastName; 39 | this.username = username; 40 | this.email = email; 41 | this.password = password; 42 | 43 | if (authorities == null) { 44 | this.authorities = null; 45 | } else { 46 | this.authorities = new ArrayList<>(authorities); 47 | } 48 | } 49 | 50 | public static UserPrincipal create(User user) { 51 | List authorities = user.getRoles().stream() 52 | .map(role -> new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList()); 53 | 54 | return new UserPrincipal(user.getId(), user.getFirstName(), user.getLastName(), user.getUsername(), 55 | user.getEmail(), user.getPassword(), authorities); 56 | } 57 | 58 | public Long getId() { 59 | return id; 60 | } 61 | 62 | public String getEmail() { 63 | return email; 64 | } 65 | 66 | @Override 67 | public Collection getAuthorities() { 68 | return authorities == null ? null : new ArrayList<>(authorities); 69 | } 70 | 71 | @Override 72 | public String getPassword() { 73 | return password; 74 | } 75 | 76 | @Override 77 | public String getUsername() { 78 | return username; 79 | } 80 | 81 | @Override 82 | public boolean isAccountNonExpired() { 83 | return true; 84 | } 85 | 86 | @Override 87 | public boolean isAccountNonLocked() { 88 | return true; 89 | } 90 | 91 | @Override 92 | public boolean isCredentialsNonExpired() { 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean isEnabled() { 98 | return true; 99 | } 100 | 101 | public boolean equals(Object object) { 102 | if (this == object) 103 | return true; 104 | if (object == null || getClass() != object.getClass()) 105 | return false; 106 | UserPrincipal that = (UserPrincipal) object; 107 | return Objects.equals(id, that.id); 108 | } 109 | 110 | public int hashCode() { 111 | return Objects.hash(id); 112 | } 113 | 114 | public String getFirstName() { 115 | return firstName; 116 | } 117 | 118 | public String getLastName() { 119 | return lastName; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at coma.spurs@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/TagServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 4 | import com.sopromadze.blogapi.exception.UnauthorizedException; 5 | import com.sopromadze.blogapi.model.Tag; 6 | import com.sopromadze.blogapi.model.role.RoleName; 7 | import com.sopromadze.blogapi.payload.ApiResponse; 8 | import com.sopromadze.blogapi.payload.PagedResponse; 9 | import com.sopromadze.blogapi.repository.TagRepository; 10 | import com.sopromadze.blogapi.security.UserPrincipal; 11 | import com.sopromadze.blogapi.service.TagService; 12 | import com.sopromadze.blogapi.utils.AppUtils; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.data.domain.Page; 15 | import org.springframework.data.domain.PageRequest; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.data.domain.Sort; 18 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 19 | import org.springframework.stereotype.Service; 20 | 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | @Service 25 | public class TagServiceImpl implements TagService { 26 | 27 | @Autowired 28 | private TagRepository tagRepository; 29 | 30 | @Override 31 | public PagedResponse getAllTags(int page, int size) { 32 | AppUtils.validatePageNumberAndSize(page, size); 33 | 34 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); 35 | 36 | Page tags = tagRepository.findAll(pageable); 37 | 38 | List content = tags.getNumberOfElements() == 0 ? Collections.emptyList() : tags.getContent(); 39 | 40 | return new PagedResponse<>(content, tags.getNumber(), tags.getSize(), tags.getTotalElements(), tags.getTotalPages(), tags.isLast()); 41 | } 42 | 43 | @Override 44 | public Tag getTag(Long id) { 45 | return tagRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Tag", "id", id)); 46 | } 47 | 48 | @Override 49 | public Tag addTag(Tag tag, UserPrincipal currentUser) { 50 | return tagRepository.save(tag); 51 | } 52 | 53 | @Override 54 | public Tag updateTag(Long id, Tag newTag, UserPrincipal currentUser) { 55 | Tag tag = tagRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Tag", "id", id)); 56 | if (tag.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() 57 | .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 58 | tag.setName(newTag.getName()); 59 | return tagRepository.save(tag); 60 | } 61 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to edit this tag"); 62 | 63 | throw new UnauthorizedException(apiResponse); 64 | } 65 | 66 | @Override 67 | public ApiResponse deleteTag(Long id, UserPrincipal currentUser) { 68 | Tag tag = tagRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Tag", "id", id)); 69 | if (tag.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() 70 | .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 71 | tagRepository.deleteById(id); 72 | return new ApiResponse(Boolean.TRUE, "You successfully deleted tag"); 73 | } 74 | 75 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to delete this tag"); 76 | 77 | throw new UnauthorizedException(apiResponse); 78 | } 79 | } 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/config/SecutiryConfig.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.config; 2 | 3 | import com.sopromadze.blogapi.repository.UserRepository; 4 | import com.sopromadze.blogapi.security.JwtAuthenticationEntryPoint; 5 | import com.sopromadze.blogapi.security.JwtAuthenticationFilter; 6 | import com.sopromadze.blogapi.service.impl.CustomUserDetailsServiceImpl; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.config.BeanIds; 13 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 14 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 18 | import org.springframework.security.config.http.SessionCreationPolicy; 19 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 20 | import org.springframework.security.crypto.password.PasswordEncoder; 21 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 22 | 23 | @Configuration 24 | @EnableWebSecurity 25 | @EnableGlobalMethodSecurity( 26 | securedEnabled = true, 27 | jsr250Enabled = true, 28 | prePostEnabled = true) 29 | public class SecutiryConfig extends WebSecurityConfigurerAdapter { 30 | private final CustomUserDetailsServiceImpl customUserDetailsService; 31 | private final JwtAuthenticationEntryPoint unauthorizedHandler; 32 | private final JwtAuthenticationFilter jwtAuthenticationFilter; 33 | 34 | @Autowired 35 | public SecutiryConfig(UserRepository userRepository, CustomUserDetailsServiceImpl customUserDetailsService, 36 | JwtAuthenticationEntryPoint unauthorizedHandler, JwtAuthenticationFilter jwtAuthenticationFilter) { 37 | this.customUserDetailsService = customUserDetailsService; 38 | this.unauthorizedHandler = unauthorizedHandler; 39 | this.jwtAuthenticationFilter = jwtAuthenticationFilter; 40 | } 41 | 42 | @Override 43 | protected void configure(HttpSecurity http) throws Exception { 44 | 45 | http.cors().and().csrf().disable() 46 | .exceptionHandling() 47 | .authenticationEntryPoint(unauthorizedHandler) 48 | .and() 49 | .sessionManagement() 50 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 51 | .and() 52 | .authorizeRequests() 53 | .antMatchers(HttpMethod.GET, "/api/**").permitAll() 54 | .antMatchers(HttpMethod.POST, "/api/auth/**").permitAll() 55 | .antMatchers(HttpMethod.GET, "/api/users/checkUsernameAvailability", "/api/users/checkEmailAvailability").permitAll() 56 | .anyRequest().authenticated(); 57 | 58 | http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); 59 | 60 | } 61 | 62 | public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { 63 | authenticationManagerBuilder.userDetailsService(customUserDetailsService) 64 | .passwordEncoder(passwordEncoder()); 65 | } 66 | 67 | @Bean(BeanIds.AUTHENTICATION_MANAGER) 68 | public AuthenticationManager authenticationManagerBean() throws Exception { 69 | return super.authenticationManagerBean(); 70 | } 71 | 72 | @Bean 73 | public PasswordEncoder passwordEncoder() { 74 | return new BCryptPasswordEncoder(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/CommentController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.model.Comment; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.CommentRequest; 6 | import com.sopromadze.blogapi.payload.PagedResponse; 7 | import com.sopromadze.blogapi.security.CurrentUser; 8 | import com.sopromadze.blogapi.security.UserPrincipal; 9 | import com.sopromadze.blogapi.service.CommentService; 10 | import com.sopromadze.blogapi.utils.AppConstants; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.security.access.prepost.PreAuthorize; 15 | import org.springframework.web.bind.annotation.DeleteMapping; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.PutMapping; 20 | import org.springframework.web.bind.annotation.RequestBody; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RequestParam; 23 | import org.springframework.web.bind.annotation.RestController; 24 | 25 | import javax.validation.Valid; 26 | 27 | @RestController 28 | @RequestMapping("/api/posts/{postId}/comments") 29 | public class CommentController { 30 | @Autowired 31 | private CommentService commentService; 32 | 33 | @GetMapping 34 | public ResponseEntity> getAllComments(@PathVariable(name = "postId") Long postId, 35 | @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 36 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 37 | 38 | PagedResponse allComments = commentService.getAllComments(postId, page, size); 39 | 40 | return new ResponseEntity< >(allComments, HttpStatus.OK); 41 | } 42 | 43 | @PostMapping 44 | @PreAuthorize("hasRole('USER')") 45 | public ResponseEntity addComment(@Valid @RequestBody CommentRequest commentRequest, 46 | @PathVariable(name = "postId") Long postId, @CurrentUser UserPrincipal currentUser) { 47 | Comment newComment = commentService.addComment(commentRequest, postId, currentUser); 48 | 49 | return new ResponseEntity<>(newComment, HttpStatus.CREATED); 50 | } 51 | 52 | @GetMapping("/{id}") 53 | public ResponseEntity getComment(@PathVariable(name = "postId") Long postId, 54 | @PathVariable(name = "id") Long id) { 55 | Comment comment = commentService.getComment(postId, id); 56 | 57 | return new ResponseEntity<>(comment, HttpStatus.OK); 58 | } 59 | 60 | @PutMapping("/{id}") 61 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 62 | public ResponseEntity updateComment(@PathVariable(name = "postId") Long postId, 63 | @PathVariable(name = "id") Long id, @Valid @RequestBody CommentRequest commentRequest, 64 | @CurrentUser UserPrincipal currentUser) { 65 | 66 | Comment updatedComment = commentService.updateComment(postId, id, commentRequest, currentUser); 67 | 68 | return new ResponseEntity<>(updatedComment, HttpStatus.OK); 69 | } 70 | 71 | @DeleteMapping("/{id}") 72 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 73 | public ResponseEntity deleteComment(@PathVariable(name = "postId") Long postId, 74 | @PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { 75 | 76 | ApiResponse response = commentService.deleteComment(postId, id, currentUser); 77 | 78 | HttpStatus status = response.getSuccess() ? HttpStatus.OK : HttpStatus.BAD_REQUEST; 79 | 80 | return new ResponseEntity<>(response, status); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/TodoController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.model.Todo; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.PagedResponse; 6 | import com.sopromadze.blogapi.security.CurrentUser; 7 | import com.sopromadze.blogapi.security.UserPrincipal; 8 | import com.sopromadze.blogapi.service.TodoService; 9 | import com.sopromadze.blogapi.utils.AppConstants; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.access.prepost.PreAuthorize; 14 | import org.springframework.web.bind.annotation.DeleteMapping; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.PutMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RequestParam; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | import javax.validation.Valid; 25 | 26 | @RestController 27 | @RequestMapping("/api/todos") 28 | public class TodoController { 29 | 30 | @Autowired 31 | private TodoService todoService; 32 | 33 | @GetMapping 34 | @PreAuthorize("hasRole('USER')") 35 | public ResponseEntity> getAllTodos( 36 | @CurrentUser UserPrincipal currentUser, 37 | @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 38 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 39 | 40 | PagedResponse response = todoService.getAllTodos(currentUser, page, size); 41 | 42 | return new ResponseEntity< >(response, HttpStatus.OK); 43 | } 44 | 45 | @PostMapping 46 | @PreAuthorize("hasRole('USER')") 47 | public ResponseEntity addTodo(@Valid @RequestBody Todo todo, @CurrentUser UserPrincipal currentUser) { 48 | Todo newTodo = todoService.addTodo(todo, currentUser); 49 | 50 | return new ResponseEntity< >(newTodo, HttpStatus.CREATED); 51 | } 52 | 53 | @GetMapping("/{id}") 54 | @PreAuthorize("hasRole('USER')") 55 | public ResponseEntity getTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { 56 | Todo todo = todoService.getTodo(id, currentUser); 57 | 58 | return new ResponseEntity< >(todo, HttpStatus.OK); 59 | } 60 | 61 | @PutMapping("/{id}") 62 | @PreAuthorize("hasRole('USER')") 63 | public ResponseEntity updateTodo(@PathVariable(value = "id") Long id, @Valid @RequestBody Todo newTodo, 64 | @CurrentUser UserPrincipal currentUser) { 65 | Todo updatedTodo = todoService.updateTodo(id, newTodo, currentUser); 66 | 67 | return new ResponseEntity< >(updatedTodo, HttpStatus.OK); 68 | } 69 | 70 | @DeleteMapping("/{id}") 71 | @PreAuthorize("hasRole('USER')") 72 | public ResponseEntity deleteTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { 73 | ApiResponse apiResponse = todoService.deleteTodo(id, currentUser); 74 | 75 | return new ResponseEntity<>(apiResponse, HttpStatus.OK); 76 | } 77 | 78 | @PutMapping("/{id}/complete") 79 | @PreAuthorize("hasRole('USER')") 80 | public ResponseEntity completeTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { 81 | 82 | Todo todo = todoService.completeTodo(id, currentUser); 83 | 84 | return new ResponseEntity< >(todo, HttpStatus.OK); 85 | } 86 | 87 | @PutMapping("/{id}/unComplete") 88 | @PreAuthorize("hasRole('USER')") 89 | public ResponseEntity unCompleteTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { 90 | 91 | Todo todo = todoService.unCompleteTodo(id, currentUser); 92 | 93 | return new ResponseEntity< >(todo, HttpStatus.OK); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/AlbumController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.exception.ResponseEntityErrorException; 4 | import com.sopromadze.blogapi.model.Album; 5 | import com.sopromadze.blogapi.payload.AlbumResponse; 6 | import com.sopromadze.blogapi.payload.ApiResponse; 7 | import com.sopromadze.blogapi.payload.PagedResponse; 8 | import com.sopromadze.blogapi.payload.PhotoResponse; 9 | import com.sopromadze.blogapi.payload.request.AlbumRequest; 10 | import com.sopromadze.blogapi.security.CurrentUser; 11 | import com.sopromadze.blogapi.security.UserPrincipal; 12 | import com.sopromadze.blogapi.service.AlbumService; 13 | import com.sopromadze.blogapi.service.PhotoService; 14 | import com.sopromadze.blogapi.utils.AppConstants; 15 | import com.sopromadze.blogapi.utils.AppUtils; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.http.HttpStatus; 18 | import org.springframework.http.ResponseEntity; 19 | import org.springframework.security.access.prepost.PreAuthorize; 20 | import org.springframework.web.bind.annotation.DeleteMapping; 21 | import org.springframework.web.bind.annotation.ExceptionHandler; 22 | import org.springframework.web.bind.annotation.GetMapping; 23 | import org.springframework.web.bind.annotation.PathVariable; 24 | import org.springframework.web.bind.annotation.PostMapping; 25 | import org.springframework.web.bind.annotation.PutMapping; 26 | import org.springframework.web.bind.annotation.RequestBody; 27 | import org.springframework.web.bind.annotation.RequestMapping; 28 | import org.springframework.web.bind.annotation.RequestParam; 29 | import org.springframework.web.bind.annotation.RestController; 30 | 31 | import javax.validation.Valid; 32 | 33 | @RestController 34 | @RequestMapping("/api/albums") 35 | public class AlbumController { 36 | @Autowired 37 | private AlbumService albumService; 38 | 39 | @Autowired 40 | private PhotoService photoService; 41 | 42 | @ExceptionHandler(ResponseEntityErrorException.class) 43 | public ResponseEntity handleExceptions(ResponseEntityErrorException exception) { 44 | return exception.getApiResponse(); 45 | } 46 | 47 | @GetMapping 48 | public PagedResponse getAllAlbums( 49 | @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 50 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 51 | AppUtils.validatePageNumberAndSize(page, size); 52 | 53 | return albumService.getAllAlbums(page, size); 54 | } 55 | 56 | @PostMapping 57 | @PreAuthorize("hasRole('USER')") 58 | public ResponseEntity addAlbum(@Valid @RequestBody AlbumRequest albumRequest, @CurrentUser UserPrincipal currentUser) { 59 | return albumService.addAlbum(albumRequest, currentUser); 60 | } 61 | 62 | @GetMapping("/{id}") 63 | public ResponseEntity getAlbum(@PathVariable(name = "id") Long id) { 64 | return albumService.getAlbum(id); 65 | } 66 | 67 | @PutMapping("/{id}") 68 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 69 | public ResponseEntity updateAlbum(@PathVariable(name = "id") Long id, @Valid @RequestBody AlbumRequest newAlbum, 70 | @CurrentUser UserPrincipal currentUser) { 71 | return albumService.updateAlbum(id, newAlbum, currentUser); 72 | } 73 | 74 | @DeleteMapping("/{id}") 75 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 76 | public ResponseEntity deleteAlbum(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { 77 | return albumService.deleteAlbum(id, currentUser); 78 | } 79 | 80 | @GetMapping("/{id}/photos") 81 | public ResponseEntity> getAllPhotosByAlbum(@PathVariable(name = "id") Long id, 82 | @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 83 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 84 | 85 | PagedResponse response = photoService.getAllPhotosByAlbum(id, page, size); 86 | 87 | return new ResponseEntity<>(response, HttpStatus.OK); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/CategoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 4 | import com.sopromadze.blogapi.exception.UnauthorizedException; 5 | import com.sopromadze.blogapi.model.Category; 6 | import com.sopromadze.blogapi.model.role.RoleName; 7 | import com.sopromadze.blogapi.payload.ApiResponse; 8 | import com.sopromadze.blogapi.payload.PagedResponse; 9 | import com.sopromadze.blogapi.repository.CategoryRepository; 10 | import com.sopromadze.blogapi.security.UserPrincipal; 11 | import com.sopromadze.blogapi.service.CategoryService; 12 | import com.sopromadze.blogapi.utils.AppUtils; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.data.domain.Page; 15 | import org.springframework.data.domain.PageRequest; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.data.domain.Sort; 18 | import org.springframework.http.HttpStatus; 19 | import org.springframework.http.ResponseEntity; 20 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 21 | import org.springframework.stereotype.Service; 22 | 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | @Service 27 | public class CategoryServiceImpl implements CategoryService { 28 | 29 | @Autowired 30 | private CategoryRepository categoryRepository; 31 | 32 | @Override 33 | public PagedResponse getAllCategories(int page, int size) { 34 | AppUtils.validatePageNumberAndSize(page, size); 35 | 36 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); 37 | 38 | Page categories = categoryRepository.findAll(pageable); 39 | 40 | List content = categories.getNumberOfElements() == 0 ? Collections.emptyList() : categories.getContent(); 41 | 42 | return new PagedResponse<>(content, categories.getNumber(), categories.getSize(), categories.getTotalElements(), 43 | categories.getTotalPages(), categories.isLast()); 44 | } 45 | 46 | @Override 47 | public ResponseEntity getCategory(Long id) { 48 | Category category = categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Category", "id", id)); 49 | return new ResponseEntity<>(category, HttpStatus.OK); 50 | } 51 | 52 | @Override 53 | public ResponseEntity addCategory(Category category, UserPrincipal currentUser) { 54 | Category newCategory = categoryRepository.save(category); 55 | return new ResponseEntity<>(newCategory, HttpStatus.CREATED); 56 | } 57 | 58 | @Override 59 | public ResponseEntity updateCategory(Long id, Category newCategory, UserPrincipal currentUser) { 60 | Category category = categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Category", "id", id)); 61 | if (category.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() 62 | .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 63 | category.setName(newCategory.getName()); 64 | Category updatedCategory = categoryRepository.save(category); 65 | return new ResponseEntity<>(updatedCategory, HttpStatus.OK); 66 | } 67 | 68 | throw new UnauthorizedException("You don't have permission to edit this category"); 69 | } 70 | 71 | @Override 72 | public ResponseEntity deleteCategory(Long id, UserPrincipal currentUser) { 73 | Category category = categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("category", "id", id)); 74 | if (category.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() 75 | .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 76 | categoryRepository.deleteById(id); 77 | return new ResponseEntity<>(new ApiResponse(Boolean.TRUE, "You successfully deleted category"), HttpStatus.OK); 78 | } 79 | throw new UnauthorizedException("You don't have permission to delete this category"); 80 | } 81 | } 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/PostController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.model.Post; 4 | import com.sopromadze.blogapi.payload.ApiResponse; 5 | import com.sopromadze.blogapi.payload.PagedResponse; 6 | import com.sopromadze.blogapi.payload.PostRequest; 7 | import com.sopromadze.blogapi.payload.PostResponse; 8 | import com.sopromadze.blogapi.security.CurrentUser; 9 | import com.sopromadze.blogapi.security.UserPrincipal; 10 | import com.sopromadze.blogapi.service.PostService; 11 | import com.sopromadze.blogapi.utils.AppConstants; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.security.access.prepost.PreAuthorize; 16 | import org.springframework.web.bind.annotation.DeleteMapping; 17 | import org.springframework.web.bind.annotation.GetMapping; 18 | import org.springframework.web.bind.annotation.PathVariable; 19 | import org.springframework.web.bind.annotation.PostMapping; 20 | import org.springframework.web.bind.annotation.PutMapping; 21 | import org.springframework.web.bind.annotation.RequestBody; 22 | import org.springframework.web.bind.annotation.RequestMapping; 23 | import org.springframework.web.bind.annotation.RequestParam; 24 | import org.springframework.web.bind.annotation.RestController; 25 | 26 | import javax.validation.Valid; 27 | 28 | @RestController 29 | @RequestMapping("/api/posts") 30 | public class PostController { 31 | @Autowired 32 | private PostService postService; 33 | 34 | @GetMapping 35 | public ResponseEntity> getAllPosts( 36 | @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 37 | @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 38 | PagedResponse response = postService.getAllPosts(page, size); 39 | 40 | return new ResponseEntity< >(response, HttpStatus.OK); 41 | } 42 | 43 | @GetMapping("/category/{id}") 44 | public ResponseEntity> getPostsByCategory( 45 | @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 46 | @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size, 47 | @PathVariable(name = "id") Long id) { 48 | PagedResponse response = postService.getPostsByCategory(id, page, size); 49 | 50 | return new ResponseEntity< >(response, HttpStatus.OK); 51 | } 52 | 53 | @GetMapping("/tag/{id}") 54 | public ResponseEntity> getPostsByTag( 55 | @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 56 | @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size, 57 | @PathVariable(name = "id") Long id) { 58 | PagedResponse response = postService.getPostsByTag(id, page, size); 59 | 60 | return new ResponseEntity< >(response, HttpStatus.OK); 61 | } 62 | 63 | @PostMapping 64 | @PreAuthorize("hasRole('USER')") 65 | public ResponseEntity addPost(@Valid @RequestBody PostRequest postRequest, 66 | @CurrentUser UserPrincipal currentUser) { 67 | PostResponse postResponse = postService.addPost(postRequest, currentUser); 68 | 69 | return new ResponseEntity< >(postResponse, HttpStatus.CREATED); 70 | } 71 | 72 | @GetMapping("/{id}") 73 | public ResponseEntity getPost(@PathVariable(name = "id") Long id) { 74 | Post post = postService.getPost(id); 75 | 76 | return new ResponseEntity< >(post, HttpStatus.OK); 77 | } 78 | 79 | @PutMapping("/{id}") 80 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 81 | public ResponseEntity updatePost(@PathVariable(name = "id") Long id, 82 | @Valid @RequestBody PostRequest newPostRequest, @CurrentUser UserPrincipal currentUser) { 83 | Post post = postService.updatePost(id, newPostRequest, currentUser); 84 | 85 | return new ResponseEntity< >(post, HttpStatus.OK); 86 | } 87 | 88 | @DeleteMapping("/{id}") 89 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 90 | public ResponseEntity deletePost(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { 91 | ApiResponse apiResponse = postService.deletePost(id, currentUser); 92 | 93 | return new ResponseEntity< >(apiResponse, HttpStatus.OK); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.exception.AppException; 4 | import com.sopromadze.blogapi.exception.BlogapiException; 5 | import com.sopromadze.blogapi.model.role.Role; 6 | import com.sopromadze.blogapi.model.role.RoleName; 7 | import com.sopromadze.blogapi.model.user.User; 8 | import com.sopromadze.blogapi.payload.ApiResponse; 9 | import com.sopromadze.blogapi.payload.JwtAuthenticationResponse; 10 | import com.sopromadze.blogapi.payload.LoginRequest; 11 | import com.sopromadze.blogapi.payload.SignUpRequest; 12 | import com.sopromadze.blogapi.repository.RoleRepository; 13 | import com.sopromadze.blogapi.repository.UserRepository; 14 | import com.sopromadze.blogapi.security.JwtTokenProvider; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.security.authentication.AuthenticationManager; 19 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 20 | import org.springframework.security.core.Authentication; 21 | import org.springframework.security.core.context.SecurityContextHolder; 22 | import org.springframework.security.crypto.password.PasswordEncoder; 23 | import org.springframework.web.bind.annotation.PostMapping; 24 | import org.springframework.web.bind.annotation.RequestBody; 25 | import org.springframework.web.bind.annotation.RequestMapping; 26 | import org.springframework.web.bind.annotation.RestController; 27 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder; 28 | 29 | import javax.validation.Valid; 30 | import java.net.URI; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | 34 | @RestController 35 | @RequestMapping("/api/auth") 36 | public class AuthController { 37 | private static final String USER_ROLE_NOT_SET = "User role not set"; 38 | 39 | @Autowired 40 | private AuthenticationManager authenticationManager; 41 | 42 | @Autowired 43 | private UserRepository userRepository; 44 | 45 | @Autowired 46 | private RoleRepository roleRepository; 47 | 48 | @Autowired 49 | private PasswordEncoder passwordEncoder; 50 | 51 | @Autowired 52 | private JwtTokenProvider jwtTokenProvider; 53 | 54 | @PostMapping("/signin") 55 | public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { 56 | Authentication authentication = authenticationManager.authenticate( 57 | new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmail(), loginRequest.getPassword())); 58 | 59 | SecurityContextHolder.getContext().setAuthentication(authentication); 60 | 61 | String jwt = jwtTokenProvider.generateToken(authentication); 62 | return ResponseEntity.ok(new JwtAuthenticationResponse(jwt)); 63 | } 64 | 65 | @PostMapping("/signup") 66 | public ResponseEntity registerUser(@Valid @RequestBody SignUpRequest signUpRequest) { 67 | if (Boolean.TRUE.equals(userRepository.existsByUsername(signUpRequest.getUsername()))) { 68 | throw new BlogapiException(HttpStatus.BAD_REQUEST, "Username is already taken"); 69 | } 70 | 71 | if (Boolean.TRUE.equals(userRepository.existsByEmail(signUpRequest.getEmail()))) { 72 | throw new BlogapiException(HttpStatus.BAD_REQUEST, "Email is already taken"); 73 | } 74 | 75 | String firstName = signUpRequest.getFirstName().toLowerCase(); 76 | 77 | String lastName = signUpRequest.getLastName().toLowerCase(); 78 | 79 | String username = signUpRequest.getUsername().toLowerCase(); 80 | 81 | String email = signUpRequest.getEmail().toLowerCase(); 82 | 83 | String password = passwordEncoder.encode(signUpRequest.getPassword()); 84 | 85 | User user = new User(firstName, lastName, username, email, password); 86 | 87 | List roles = new ArrayList<>(); 88 | 89 | if (userRepository.count() == 0) { 90 | roles.add(roleRepository.findByName(RoleName.ROLE_USER) 91 | .orElseThrow(() -> new AppException(USER_ROLE_NOT_SET))); 92 | roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN) 93 | .orElseThrow(() -> new AppException(USER_ROLE_NOT_SET))); 94 | } else { 95 | roles.add(roleRepository.findByName(RoleName.ROLE_USER) 96 | .orElseThrow(() -> new AppException(USER_ROLE_NOT_SET))); 97 | } 98 | 99 | user.setRoles(roles); 100 | 101 | User result = userRepository.save(user); 102 | 103 | URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/users/{userId}") 104 | .buildAndExpand(result.getId()).toUri(); 105 | 106 | return ResponseEntity.created(location).body(new ApiResponse(Boolean.TRUE, "User registered successfully")); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.sopromadze 8 | blogapi 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | BlogAPI 13 | Blog API project with Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.1.8.RELEASE 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter 32 | 33 | 34 | 35 | mysql 36 | mysql-connector-java 37 | runtime 38 | 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | 1.18.12 44 | provided 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-web 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-aop 55 | 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | 65 | 66 | org.mockito 67 | mockito-all 68 | 2.0.2-beta 69 | test 70 | 71 | 72 | 73 | 74 | org.springframework.security 75 | spring-security-test 76 | test 77 | 78 | 79 | 80 | 81 | 82 | org.hamcrest 83 | hamcrest-library 84 | test 85 | 86 | 87 | 88 | 89 | org.modelmapper 90 | modelmapper 91 | 2.3.5 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-devtools 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-starter-data-jpa 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-starter-security 107 | 108 | 109 | 110 | org.springframework.security 111 | spring-security-jwt 112 | 1.0.9.RELEASE 113 | 114 | 115 | 116 | org.springframework.security.oauth 117 | spring-security-oauth2 118 | 2.3.4.RELEASE 119 | 120 | 121 | 122 | io.jsonwebtoken 123 | jjwt 124 | 0.9.1 125 | 126 | 127 | 128 | javax.xml.bind 129 | jaxb-api 130 | 131 | 132 | 133 | com.fasterxml.jackson.datatype 134 | jackson-datatype-jsr310 135 | 136 | 137 | 138 | org.apache.commons 139 | commons-lang3 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | org.springframework.boot 148 | spring-boot-maven-plugin 149 | 150 | com.sopromadze.blogapi.BlogApiApplication 151 | 152 | 153 | 154 | 155 | repackage 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/exception/RestControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.exception; 2 | 3 | import com.sopromadze.blogapi.payload.ApiResponse; 4 | import com.sopromadze.blogapi.payload.ExceptionResponse; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.http.converter.HttpMessageNotReadableException; 8 | import org.springframework.validation.FieldError; 9 | import org.springframework.web.HttpRequestMethodNotSupportedException; 10 | import org.springframework.web.bind.MethodArgumentNotValidException; 11 | import org.springframework.web.bind.annotation.ControllerAdvice; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | import org.springframework.web.bind.annotation.ResponseStatus; 15 | import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | @ControllerAdvice 22 | public class RestControllerExceptionHandler { 23 | 24 | public ResponseEntity resolveException(BlogapiException exception) { 25 | String message = exception.getMessage(); 26 | HttpStatus status = exception.getStatus(); 27 | 28 | ApiResponse apiResponse = new ApiResponse(); 29 | 30 | apiResponse.setSuccess(Boolean.FALSE); 31 | apiResponse.setMessage(message); 32 | 33 | return new ResponseEntity<>(apiResponse, status); 34 | } 35 | 36 | @ExceptionHandler(UnauthorizedException.class) 37 | @ResponseBody 38 | @ResponseStatus(code = HttpStatus.UNAUTHORIZED) 39 | public ResponseEntity resolveException(UnauthorizedException exception) { 40 | 41 | ApiResponse apiResponse = exception.getApiResponse(); 42 | 43 | return new ResponseEntity<>(apiResponse, HttpStatus.UNAUTHORIZED); 44 | } 45 | 46 | @ExceptionHandler(BadRequestException.class) 47 | @ResponseBody 48 | public ResponseEntity resolveException(BadRequestException exception) { 49 | ApiResponse apiResponse = exception.getApiResponse(); 50 | 51 | return new ResponseEntity<>(apiResponse, HttpStatus.BAD_REQUEST); 52 | } 53 | 54 | @ExceptionHandler(ResourceNotFoundException.class) 55 | @ResponseBody 56 | public ResponseEntity resolveException(ResourceNotFoundException exception) { 57 | ApiResponse apiResponse = exception.getApiResponse(); 58 | 59 | return new ResponseEntity<>(apiResponse, HttpStatus.NOT_FOUND); 60 | } 61 | 62 | @ExceptionHandler(AccessDeniedException.class) 63 | @ResponseBody 64 | public ResponseEntity resolveException(AccessDeniedException exception) { 65 | ApiResponse apiResponse = exception.getApiResponse(); 66 | 67 | return new ResponseEntity< >(apiResponse, HttpStatus.FORBIDDEN); 68 | } 69 | 70 | @ExceptionHandler({ MethodArgumentNotValidException.class }) 71 | @ResponseBody 72 | @ResponseStatus(HttpStatus.BAD_REQUEST) 73 | public ResponseEntity resolveException(MethodArgumentNotValidException ex) { 74 | List fieldErrors = ex.getBindingResult().getFieldErrors(); 75 | List messages = new ArrayList<>(fieldErrors.size()); 76 | for (FieldError error : fieldErrors) { 77 | messages.add(error.getField() + " - " + error.getDefaultMessage()); 78 | } 79 | return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), 80 | HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); 81 | } 82 | 83 | @ExceptionHandler({ MethodArgumentTypeMismatchException.class }) 84 | @ResponseBody 85 | @ResponseStatus(HttpStatus.BAD_REQUEST) 86 | public ResponseEntity resolveException(MethodArgumentTypeMismatchException ex) { 87 | String message = "Parameter '" + ex.getParameter().getParameterName() + "' must be '" 88 | + Objects.requireNonNull(ex.getRequiredType()).getSimpleName() + "'"; 89 | List messages = new ArrayList<>(1); 90 | messages.add(message); 91 | return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), 92 | HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); 93 | } 94 | 95 | @ExceptionHandler({ HttpRequestMethodNotSupportedException.class }) 96 | @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) 97 | @ResponseBody 98 | public ResponseEntity resolveException(HttpRequestMethodNotSupportedException ex) { 99 | String message = "Request method '" + ex.getMethod() + "' not supported. List of all supported methods - " 100 | + ex.getSupportedHttpMethods(); 101 | List messages = new ArrayList<>(1); 102 | messages.add(message); 103 | 104 | return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase(), 105 | HttpStatus.METHOD_NOT_ALLOWED.value()), HttpStatus.METHOD_NOT_ALLOWED); 106 | } 107 | 108 | @ExceptionHandler({ HttpMessageNotReadableException.class }) 109 | @ResponseBody 110 | @ResponseStatus(HttpStatus.BAD_REQUEST) 111 | public ResponseEntity resolveException(HttpMessageNotReadableException ex) { 112 | String message = "Please provide Request Body in valid JSON format"; 113 | List messages = new ArrayList<>(1); 114 | messages.add(message); 115 | return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), 116 | HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/model/user/User.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.model.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.sopromadze.blogapi.model.Album; 6 | import com.sopromadze.blogapi.model.audit.DateAudit; 7 | import com.sopromadze.blogapi.model.Comment; 8 | import com.sopromadze.blogapi.model.Post; 9 | import com.sopromadze.blogapi.model.role.Role; 10 | import com.sopromadze.blogapi.model.Todo; 11 | import lombok.Data; 12 | import lombok.EqualsAndHashCode; 13 | import lombok.NoArgsConstructor; 14 | import org.hibernate.annotations.NaturalId; 15 | 16 | import javax.persistence.CascadeType; 17 | import javax.persistence.Column; 18 | import javax.persistence.Entity; 19 | import javax.persistence.FetchType; 20 | import javax.persistence.GeneratedValue; 21 | import javax.persistence.GenerationType; 22 | import javax.persistence.Id; 23 | import javax.persistence.JoinColumn; 24 | import javax.persistence.JoinTable; 25 | import javax.persistence.ManyToMany; 26 | import javax.persistence.OneToMany; 27 | import javax.persistence.OneToOne; 28 | import javax.persistence.Table; 29 | import javax.persistence.UniqueConstraint; 30 | import javax.validation.constraints.Email; 31 | import javax.validation.constraints.NotBlank; 32 | import javax.validation.constraints.Size; 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | import java.util.List; 36 | 37 | @EqualsAndHashCode(callSuper = true) 38 | @Entity 39 | @Data 40 | @NoArgsConstructor 41 | @Table(name = "users", uniqueConstraints = { @UniqueConstraint(columnNames = { "username" }), 42 | @UniqueConstraint(columnNames = { "email" }) }) 43 | public class User extends DateAudit { 44 | private static final long serialVersionUID = 1L; 45 | 46 | @Id 47 | @GeneratedValue(strategy = GenerationType.IDENTITY) 48 | @Column(name = "id") 49 | private Long id; 50 | 51 | @NotBlank 52 | @Column(name = "first_name") 53 | @Size(max = 40) 54 | private String firstName; 55 | 56 | @NotBlank 57 | @Column(name = "last_name") 58 | @Size(max = 40) 59 | private String lastName; 60 | 61 | @NotBlank 62 | @Column(name = "username") 63 | @Size(max = 15) 64 | private String username; 65 | 66 | @NotBlank 67 | @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) 68 | @Size(max = 100) 69 | @Column(name = "password") 70 | private String password; 71 | 72 | @NotBlank 73 | @NaturalId 74 | @Size(max = 40) 75 | @Column(name = "email") 76 | @Email 77 | private String email; 78 | 79 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 80 | @JoinColumn(name = "address_id") 81 | private Address address; 82 | 83 | @Column(name = "phone") 84 | private String phone; 85 | 86 | @Column(name = "website") 87 | private String website; 88 | 89 | @ManyToMany(fetch = FetchType.EAGER) 90 | @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) 91 | private List roles; 92 | 93 | @JsonIgnore 94 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) 95 | private List todos; 96 | 97 | @JsonIgnore 98 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) 99 | private List albums; 100 | 101 | @JsonIgnore 102 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) 103 | private List posts; 104 | 105 | @JsonIgnore 106 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) 107 | private List comments; 108 | 109 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 110 | @JoinColumn(name = "company_id") 111 | private Company company; 112 | 113 | public User(String firstName, String lastName, String username, String email, String password) { 114 | this.firstName = firstName; 115 | this.lastName = lastName; 116 | this.username = username; 117 | this.email = email; 118 | this.password = password; 119 | } 120 | 121 | 122 | 123 | public List getTodos() { 124 | 125 | return todos == null ? null : new ArrayList<>(todos); 126 | } 127 | 128 | public void setTodos(List todos) { 129 | 130 | if (todos == null) { 131 | this.todos = null; 132 | } else { 133 | this.todos = Collections.unmodifiableList(todos); 134 | } 135 | } 136 | 137 | public List getAlbums() { 138 | 139 | return albums == null ? null : new ArrayList<>(albums); 140 | } 141 | 142 | public void setAlbums(List albums) { 143 | 144 | if (albums == null) { 145 | this.albums = null; 146 | } else { 147 | this.albums = Collections.unmodifiableList(albums); 148 | } 149 | } 150 | 151 | 152 | public List getPosts() { 153 | 154 | return posts == null ? null : new ArrayList<>(posts); 155 | } 156 | 157 | public void setPosts(List posts) { 158 | 159 | if (posts == null) { 160 | this.posts = null; 161 | } else { 162 | this.posts = Collections.unmodifiableList(posts); 163 | } 164 | } 165 | 166 | public List getRoles() { 167 | 168 | return roles == null ? null : new ArrayList<>(roles); 169 | } 170 | 171 | public void setRoles(List roles) { 172 | 173 | if (roles == null) { 174 | this.roles = null; 175 | } else { 176 | this.roles = Collections.unmodifiableList(roles); 177 | } 178 | } 179 | 180 | public List getComments() { 181 | return comments == null ? null : new ArrayList<>(comments); 182 | } 183 | 184 | public void setComments(List comments) { 185 | 186 | if (comments == null) { 187 | this.comments = null; 188 | } else { 189 | this.comments = Collections.unmodifiableList(comments); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/CommentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.exception.BlogapiException; 4 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 5 | import com.sopromadze.blogapi.model.Comment; 6 | import com.sopromadze.blogapi.model.Post; 7 | import com.sopromadze.blogapi.model.role.RoleName; 8 | import com.sopromadze.blogapi.model.user.User; 9 | import com.sopromadze.blogapi.payload.ApiResponse; 10 | import com.sopromadze.blogapi.payload.CommentRequest; 11 | import com.sopromadze.blogapi.payload.PagedResponse; 12 | import com.sopromadze.blogapi.repository.CommentRepository; 13 | import com.sopromadze.blogapi.repository.PostRepository; 14 | import com.sopromadze.blogapi.repository.UserRepository; 15 | import com.sopromadze.blogapi.security.UserPrincipal; 16 | import com.sopromadze.blogapi.service.CommentService; 17 | import com.sopromadze.blogapi.utils.AppUtils; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.data.domain.Page; 20 | import org.springframework.data.domain.PageRequest; 21 | import org.springframework.data.domain.Pageable; 22 | import org.springframework.data.domain.Sort; 23 | import org.springframework.http.HttpStatus; 24 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 25 | import org.springframework.stereotype.Service; 26 | 27 | @Service 28 | public class CommentServiceImpl implements CommentService { 29 | private static final String THIS_COMMENT = " this comment"; 30 | 31 | private static final String YOU_DON_T_HAVE_PERMISSION_TO = "You don't have permission to "; 32 | 33 | private static final String ID_STR = "id"; 34 | 35 | private static final String COMMENT_STR = "Comment"; 36 | 37 | private static final String POST_STR = "Post"; 38 | 39 | private static final String COMMENT_DOES_NOT_BELONG_TO_POST = "Comment does not belong to post"; 40 | 41 | @Autowired 42 | private CommentRepository commentRepository; 43 | 44 | @Autowired 45 | private PostRepository postRepository; 46 | 47 | @Autowired 48 | private UserRepository userRepository; 49 | 50 | @Override 51 | public PagedResponse getAllComments(Long postId, int page, int size) { 52 | AppUtils.validatePageNumberAndSize(page, size); 53 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); 54 | 55 | Page comments = commentRepository.findByPostId(postId, pageable); 56 | 57 | return new PagedResponse<>(comments.getContent(), comments.getNumber(), comments.getSize(), 58 | comments.getTotalElements(), comments.getTotalPages(), comments.isLast()); 59 | } 60 | 61 | @Override 62 | public Comment addComment(CommentRequest commentRequest, Long postId, UserPrincipal currentUser) { 63 | Post post = postRepository.findById(postId) 64 | .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); 65 | User user = userRepository.getUser(currentUser); 66 | Comment comment = new Comment(commentRequest.getBody()); 67 | comment.setUser(user); 68 | comment.setPost(post); 69 | comment.setName(currentUser.getUsername()); 70 | comment.setEmail(currentUser.getEmail()); 71 | return commentRepository.save(comment); 72 | } 73 | 74 | @Override 75 | public Comment getComment(Long postId, Long id) { 76 | Post post = postRepository.findById(postId) 77 | .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); 78 | Comment comment = commentRepository.findById(id) 79 | .orElseThrow(() -> new ResourceNotFoundException(COMMENT_STR, ID_STR, id)); 80 | if (comment.getPost().getId().equals(post.getId())) { 81 | return comment; 82 | } 83 | 84 | throw new BlogapiException(HttpStatus.BAD_REQUEST, COMMENT_DOES_NOT_BELONG_TO_POST); 85 | } 86 | 87 | @Override 88 | public Comment updateComment(Long postId, Long id, CommentRequest commentRequest, 89 | UserPrincipal currentUser) { 90 | Post post = postRepository.findById(postId) 91 | .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); 92 | Comment comment = commentRepository.findById(id) 93 | .orElseThrow(() -> new ResourceNotFoundException(COMMENT_STR, ID_STR, id)); 94 | 95 | if (!comment.getPost().getId().equals(post.getId())) { 96 | throw new BlogapiException(HttpStatus.BAD_REQUEST, COMMENT_DOES_NOT_BELONG_TO_POST); 97 | } 98 | 99 | if (comment.getUser().getId().equals(currentUser.getId()) 100 | || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 101 | comment.setBody(commentRequest.getBody()); 102 | return commentRepository.save(comment); 103 | } 104 | 105 | throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO + "update" + THIS_COMMENT); 106 | } 107 | 108 | @Override 109 | public ApiResponse deleteComment(Long postId, Long id, UserPrincipal currentUser) { 110 | Post post = postRepository.findById(postId) 111 | .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); 112 | Comment comment = commentRepository.findById(id) 113 | .orElseThrow(() -> new ResourceNotFoundException(COMMENT_STR, ID_STR, id)); 114 | 115 | if (!comment.getPost().getId().equals(post.getId())) { 116 | return new ApiResponse(Boolean.FALSE, COMMENT_DOES_NOT_BELONG_TO_POST); 117 | } 118 | 119 | if (comment.getUser().getId().equals(currentUser.getId()) 120 | || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 121 | commentRepository.deleteById(comment.getId()); 122 | return new ApiResponse(Boolean.TRUE, "You successfully deleted comment"); 123 | } 124 | 125 | throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO + "delete" + THIS_COMMENT); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/AlbumServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.exception.BlogapiException; 4 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 5 | import com.sopromadze.blogapi.model.Album; 6 | import com.sopromadze.blogapi.model.role.RoleName; 7 | import com.sopromadze.blogapi.model.user.User; 8 | import com.sopromadze.blogapi.payload.AlbumResponse; 9 | import com.sopromadze.blogapi.payload.ApiResponse; 10 | import com.sopromadze.blogapi.payload.PagedResponse; 11 | import com.sopromadze.blogapi.payload.request.AlbumRequest; 12 | import com.sopromadze.blogapi.repository.AlbumRepository; 13 | import com.sopromadze.blogapi.repository.UserRepository; 14 | import com.sopromadze.blogapi.security.UserPrincipal; 15 | import com.sopromadze.blogapi.service.AlbumService; 16 | import com.sopromadze.blogapi.utils.AppUtils; 17 | import org.modelmapper.ModelMapper; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.data.domain.Page; 20 | import org.springframework.data.domain.PageRequest; 21 | import org.springframework.data.domain.Pageable; 22 | import org.springframework.data.domain.Sort; 23 | import org.springframework.http.HttpStatus; 24 | import org.springframework.http.ResponseEntity; 25 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 26 | import org.springframework.stereotype.Service; 27 | 28 | import java.util.Arrays; 29 | import java.util.Collections; 30 | import java.util.List; 31 | 32 | import static com.sopromadze.blogapi.utils.AppConstants.ID; 33 | 34 | @Service 35 | public class AlbumServiceImpl implements AlbumService { 36 | private static final String CREATED_AT = "createdAt"; 37 | 38 | private static final String ALBUM_STR = "Album"; 39 | 40 | private static final String YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION = "You don't have permission to make this operation"; 41 | 42 | @Autowired 43 | private AlbumRepository albumRepository; 44 | 45 | @Autowired 46 | private UserRepository userRepository; 47 | 48 | @Autowired 49 | private ModelMapper modelMapper; 50 | 51 | @Override 52 | public PagedResponse getAllAlbums(int page, int size) { 53 | AppUtils.validatePageNumberAndSize(page, size); 54 | 55 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); 56 | 57 | Page albums = albumRepository.findAll(pageable); 58 | 59 | if (albums.getNumberOfElements() == 0) { 60 | return new PagedResponse<>(Collections.emptyList(), albums.getNumber(), albums.getSize(), albums.getTotalElements(), 61 | albums.getTotalPages(), albums.isLast()); 62 | } 63 | 64 | List albumResponses = Arrays.asList(modelMapper.map(albums.getContent(), AlbumResponse[].class)); 65 | 66 | return new PagedResponse<>(albumResponses, albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), 67 | albums.isLast()); 68 | } 69 | 70 | @Override 71 | public ResponseEntity addAlbum(AlbumRequest albumRequest, UserPrincipal currentUser) { 72 | User user = userRepository.getUser(currentUser); 73 | 74 | Album album = new Album(); 75 | 76 | modelMapper.map(albumRequest, album); 77 | 78 | album.setUser(user); 79 | Album newAlbum = albumRepository.save(album); 80 | return new ResponseEntity<>(newAlbum, HttpStatus.CREATED); 81 | } 82 | 83 | @Override 84 | public ResponseEntity getAlbum(Long id) { 85 | Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(ALBUM_STR, ID, id)); 86 | return new ResponseEntity<>(album, HttpStatus.OK); 87 | } 88 | 89 | @Override 90 | public ResponseEntity updateAlbum(Long id, AlbumRequest newAlbum, UserPrincipal currentUser) { 91 | Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(ALBUM_STR, ID, id)); 92 | User user = userRepository.getUser(currentUser); 93 | if (album.getUser().getId().equals(user.getId()) || currentUser.getAuthorities() 94 | .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 95 | album.setTitle(newAlbum.getTitle()); 96 | Album updatedAlbum = albumRepository.save(album); 97 | 98 | AlbumResponse albumResponse = new AlbumResponse(); 99 | 100 | modelMapper.map(updatedAlbum, albumResponse); 101 | 102 | return new ResponseEntity<>(albumResponse, HttpStatus.OK); 103 | } 104 | 105 | throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); 106 | } 107 | 108 | @Override 109 | public ResponseEntity deleteAlbum(Long id, UserPrincipal currentUser) { 110 | Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(ALBUM_STR, ID, id)); 111 | User user = userRepository.getUser(currentUser); 112 | if (album.getUser().getId().equals(user.getId()) || currentUser.getAuthorities() 113 | .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 114 | albumRepository.deleteById(id); 115 | return new ResponseEntity<>(new ApiResponse(Boolean.TRUE, "You successfully deleted album"), HttpStatus.OK); 116 | } 117 | 118 | throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); 119 | } 120 | 121 | @Override 122 | public PagedResponse getUserAlbums(String username, int page, int size) { 123 | User user = userRepository.getUserByName(username); 124 | 125 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); 126 | 127 | Page albums = albumRepository.findByCreatedBy(user.getId(), pageable); 128 | 129 | List content = albums.getNumberOfElements() > 0 ? albums.getContent() : Collections.emptyList(); 130 | 131 | return new PagedResponse<>(content, albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), albums.isLast()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/TodoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.exception.BadRequestException; 4 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 5 | import com.sopromadze.blogapi.exception.UnauthorizedException; 6 | import com.sopromadze.blogapi.model.Todo; 7 | import com.sopromadze.blogapi.model.user.User; 8 | import com.sopromadze.blogapi.payload.ApiResponse; 9 | import com.sopromadze.blogapi.payload.PagedResponse; 10 | import com.sopromadze.blogapi.repository.TodoRepository; 11 | import com.sopromadze.blogapi.repository.UserRepository; 12 | import com.sopromadze.blogapi.security.UserPrincipal; 13 | import com.sopromadze.blogapi.service.TodoService; 14 | import com.sopromadze.blogapi.utils.AppConstants; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.data.domain.Page; 17 | import org.springframework.data.domain.PageRequest; 18 | import org.springframework.data.domain.Pageable; 19 | import org.springframework.data.domain.Sort; 20 | import org.springframework.stereotype.Service; 21 | 22 | import java.util.Collections; 23 | import java.util.List; 24 | 25 | import static com.sopromadze.blogapi.utils.AppConstants.CREATED_AT; 26 | import static com.sopromadze.blogapi.utils.AppConstants.ID; 27 | import static com.sopromadze.blogapi.utils.AppConstants.TODO; 28 | import static com.sopromadze.blogapi.utils.AppConstants.YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION; 29 | 30 | @Service 31 | public class TodoServiceImpl implements TodoService { 32 | 33 | @Autowired 34 | private TodoRepository todoRepository; 35 | 36 | @Autowired 37 | private UserRepository userRepository; 38 | 39 | @Override 40 | public Todo completeTodo(Long id, UserPrincipal currentUser) { 41 | Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); 42 | 43 | User user = userRepository.getUser(currentUser); 44 | 45 | if (todo.getUser().getId().equals(user.getId())) { 46 | todo.setCompleted(Boolean.TRUE); 47 | return todoRepository.save(todo); 48 | } 49 | 50 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); 51 | 52 | throw new UnauthorizedException(apiResponse); 53 | } 54 | 55 | @Override 56 | public Todo unCompleteTodo(Long id, UserPrincipal currentUser) { 57 | Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); 58 | User user = userRepository.getUser(currentUser); 59 | if (todo.getUser().getId().equals(user.getId())) { 60 | todo.setCompleted(Boolean.FALSE); 61 | return todoRepository.save(todo); 62 | } 63 | 64 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); 65 | 66 | throw new UnauthorizedException(apiResponse); 67 | } 68 | 69 | @Override 70 | public PagedResponse getAllTodos(UserPrincipal currentUser, int page, int size) { 71 | validatePageNumberAndSize(page, size); 72 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); 73 | 74 | Page todos = todoRepository.findByCreatedBy(currentUser.getId(), pageable); 75 | 76 | List content = todos.getNumberOfElements() == 0 ? Collections.emptyList() : todos.getContent(); 77 | 78 | return new PagedResponse<>(content, todos.getNumber(), todos.getSize(), todos.getTotalElements(), 79 | todos.getTotalPages(), todos.isLast()); 80 | } 81 | 82 | @Override 83 | public Todo addTodo(Todo todo, UserPrincipal currentUser) { 84 | User user = userRepository.getUser(currentUser); 85 | todo.setUser(user); 86 | return todoRepository.save(todo); 87 | } 88 | 89 | @Override 90 | public Todo getTodo(Long id, UserPrincipal currentUser) { 91 | User user = userRepository.getUser(currentUser); 92 | Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); 93 | 94 | if (todo.getUser().getId().equals(user.getId())) { 95 | return todo; 96 | } 97 | 98 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); 99 | 100 | throw new UnauthorizedException(apiResponse); 101 | } 102 | 103 | @Override 104 | public Todo updateTodo(Long id, Todo newTodo, UserPrincipal currentUser) { 105 | User user = userRepository.getUser(currentUser); 106 | Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); 107 | if (todo.getUser().getId().equals(user.getId())) { 108 | todo.setTitle(newTodo.getTitle()); 109 | todo.setCompleted(newTodo.getCompleted()); 110 | return todoRepository.save(todo); 111 | } 112 | 113 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); 114 | 115 | throw new UnauthorizedException(apiResponse); 116 | } 117 | 118 | @Override 119 | public ApiResponse deleteTodo(Long id, UserPrincipal currentUser) { 120 | User user = userRepository.getUser(currentUser); 121 | Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); 122 | 123 | if (todo.getUser().getId().equals(user.getId())) { 124 | todoRepository.deleteById(id); 125 | return new ApiResponse(Boolean.TRUE, "You successfully deleted todo"); 126 | } 127 | 128 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); 129 | 130 | throw new UnauthorizedException(apiResponse); 131 | } 132 | 133 | private void validatePageNumberAndSize(int page, int size) { 134 | if (page < 0) { 135 | throw new BadRequestException("Page number cannot be less than zero."); 136 | } 137 | 138 | if (size < 0) { 139 | throw new BadRequestException("Size number cannot be less than zero."); 140 | } 141 | 142 | if (size > AppConstants.MAX_PAGE_SIZE) { 143 | throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.controller; 2 | 3 | import com.sopromadze.blogapi.model.Album; 4 | import com.sopromadze.blogapi.model.Post; 5 | import com.sopromadze.blogapi.model.user.User; 6 | import com.sopromadze.blogapi.payload.ApiResponse; 7 | import com.sopromadze.blogapi.payload.InfoRequest; 8 | import com.sopromadze.blogapi.payload.PagedResponse; 9 | import com.sopromadze.blogapi.payload.UserIdentityAvailability; 10 | import com.sopromadze.blogapi.payload.UserProfile; 11 | import com.sopromadze.blogapi.payload.UserSummary; 12 | import com.sopromadze.blogapi.security.CurrentUser; 13 | import com.sopromadze.blogapi.security.UserPrincipal; 14 | import com.sopromadze.blogapi.service.AlbumService; 15 | import com.sopromadze.blogapi.service.PostService; 16 | import com.sopromadze.blogapi.service.UserService; 17 | import com.sopromadze.blogapi.utils.AppConstants; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.http.HttpStatus; 20 | import org.springframework.http.ResponseEntity; 21 | import org.springframework.security.access.prepost.PreAuthorize; 22 | import org.springframework.web.bind.annotation.DeleteMapping; 23 | import org.springframework.web.bind.annotation.GetMapping; 24 | import org.springframework.web.bind.annotation.PathVariable; 25 | import org.springframework.web.bind.annotation.PostMapping; 26 | import org.springframework.web.bind.annotation.PutMapping; 27 | import org.springframework.web.bind.annotation.RequestBody; 28 | import org.springframework.web.bind.annotation.RequestMapping; 29 | import org.springframework.web.bind.annotation.RequestParam; 30 | import org.springframework.web.bind.annotation.RestController; 31 | 32 | import javax.validation.Valid; 33 | 34 | @RestController 35 | @RequestMapping("/api/users") 36 | public class UserController { 37 | @Autowired 38 | private UserService userService; 39 | 40 | @Autowired 41 | private PostService postService; 42 | 43 | @Autowired 44 | private AlbumService albumService; 45 | 46 | @GetMapping("/me") 47 | @PreAuthorize("hasRole('USER')") 48 | public ResponseEntity getCurrentUser(@CurrentUser UserPrincipal currentUser) { 49 | UserSummary userSummary = userService.getCurrentUser(currentUser); 50 | 51 | return new ResponseEntity< >(userSummary, HttpStatus.OK); 52 | } 53 | 54 | @GetMapping("/checkUsernameAvailability") 55 | public ResponseEntity checkUsernameAvailability(@RequestParam(value = "username") String username) { 56 | UserIdentityAvailability userIdentityAvailability = userService.checkUsernameAvailability(username); 57 | 58 | return new ResponseEntity< >(userIdentityAvailability, HttpStatus.OK); 59 | } 60 | 61 | @GetMapping("/checkEmailAvailability") 62 | public ResponseEntity checkEmailAvailability(@RequestParam(value = "email") String email) { 63 | UserIdentityAvailability userIdentityAvailability = userService.checkEmailAvailability(email); 64 | return new ResponseEntity< >(userIdentityAvailability, HttpStatus.OK); 65 | } 66 | 67 | @GetMapping("/{username}/profile") 68 | public ResponseEntity getUSerProfile(@PathVariable(value = "username") String username) { 69 | UserProfile userProfile = userService.getUserProfile(username); 70 | 71 | return new ResponseEntity< >(userProfile, HttpStatus.OK); 72 | } 73 | 74 | @GetMapping("/{username}/posts") 75 | public ResponseEntity> getPostsCreatedBy(@PathVariable(value = "username") String username, 76 | @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 77 | @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 78 | PagedResponse response = postService.getPostsByCreatedBy(username, page, size); 79 | 80 | return new ResponseEntity< >(response, HttpStatus.OK); 81 | } 82 | 83 | @GetMapping("/{username}/albums") 84 | public ResponseEntity> getUserAlbums(@PathVariable(name = "username") String username, 85 | @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, 86 | @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { 87 | 88 | PagedResponse response = albumService.getUserAlbums(username, page, size); 89 | 90 | return new ResponseEntity< >(response, HttpStatus.OK); 91 | } 92 | 93 | @PostMapping 94 | @PreAuthorize("hasRole('ADMIN')") 95 | public ResponseEntity addUser(@Valid @RequestBody User user) { 96 | User newUser = userService.addUser(user); 97 | 98 | return new ResponseEntity< >(newUser, HttpStatus.CREATED); 99 | } 100 | 101 | @PutMapping("/{username}") 102 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 103 | public ResponseEntity updateUser(@Valid @RequestBody User newUser, 104 | @PathVariable(value = "username") String username, @CurrentUser UserPrincipal currentUser) { 105 | User updatedUSer = userService.updateUser(newUser, username, currentUser); 106 | 107 | return new ResponseEntity< >(updatedUSer, HttpStatus.CREATED); 108 | } 109 | 110 | @DeleteMapping("/{username}") 111 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 112 | public ResponseEntity deleteUser(@PathVariable(value = "username") String username, 113 | @CurrentUser UserPrincipal currentUser) { 114 | ApiResponse apiResponse = userService.deleteUser(username, currentUser); 115 | 116 | return new ResponseEntity< >(apiResponse, HttpStatus.OK); 117 | } 118 | 119 | @PutMapping("/{username}/giveAdmin") 120 | @PreAuthorize("hasRole('ADMIN')") 121 | public ResponseEntity giveAdmin(@PathVariable(name = "username") String username) { 122 | ApiResponse apiResponse = userService.giveAdmin(username); 123 | 124 | return new ResponseEntity< >(apiResponse, HttpStatus.OK); 125 | } 126 | 127 | @PutMapping("/{username}/takeAdmin") 128 | @PreAuthorize("hasRole('ADMIN')") 129 | public ResponseEntity takeAdmin(@PathVariable(name = "username") String username) { 130 | ApiResponse apiResponse = userService.removeAdmin(username); 131 | 132 | return new ResponseEntity< >(apiResponse, HttpStatus.OK); 133 | } 134 | 135 | @PutMapping("/setOrUpdateInfo") 136 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 137 | public ResponseEntity setAddress(@CurrentUser UserPrincipal currentUser, 138 | @Valid @RequestBody InfoRequest infoRequest) { 139 | UserProfile userProfile = userService.setOrUpdateInfo(currentUser, infoRequest); 140 | 141 | return new ResponseEntity< >(userProfile, HttpStatus.OK); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/PhotoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 4 | import com.sopromadze.blogapi.exception.UnauthorizedException; 5 | import com.sopromadze.blogapi.model.Album; 6 | import com.sopromadze.blogapi.model.Photo; 7 | import com.sopromadze.blogapi.model.role.RoleName; 8 | import com.sopromadze.blogapi.payload.ApiResponse; 9 | import com.sopromadze.blogapi.payload.PagedResponse; 10 | import com.sopromadze.blogapi.payload.PhotoRequest; 11 | import com.sopromadze.blogapi.payload.PhotoResponse; 12 | import com.sopromadze.blogapi.repository.AlbumRepository; 13 | import com.sopromadze.blogapi.repository.PhotoRepository; 14 | import com.sopromadze.blogapi.security.UserPrincipal; 15 | import com.sopromadze.blogapi.service.PhotoService; 16 | import com.sopromadze.blogapi.utils.AppConstants; 17 | import com.sopromadze.blogapi.utils.AppUtils; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.data.domain.Page; 20 | import org.springframework.data.domain.PageRequest; 21 | import org.springframework.data.domain.Pageable; 22 | import org.springframework.data.domain.Sort; 23 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 24 | import org.springframework.stereotype.Service; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Collections; 28 | import java.util.List; 29 | 30 | import static com.sopromadze.blogapi.utils.AppConstants.ALBUM; 31 | import static com.sopromadze.blogapi.utils.AppConstants.CREATED_AT; 32 | import static com.sopromadze.blogapi.utils.AppConstants.ID; 33 | import static com.sopromadze.blogapi.utils.AppConstants.PHOTO; 34 | 35 | @Service 36 | public class PhotoServiceImpl implements PhotoService { 37 | 38 | @Autowired 39 | private PhotoRepository photoRepository; 40 | 41 | @Autowired 42 | private AlbumRepository albumRepository; 43 | 44 | @Override 45 | public PagedResponse getAllPhotos(int page, int size) { 46 | AppUtils.validatePageNumberAndSize(page, size); 47 | 48 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); 49 | Page photos = photoRepository.findAll(pageable); 50 | 51 | List photoResponses = new ArrayList<>(photos.getContent().size()); 52 | for (Photo photo : photos.getContent()) { 53 | photoResponses.add(new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), 54 | photo.getThumbnailUrl(), photo.getAlbum().getId())); 55 | } 56 | 57 | if (photos.getNumberOfElements() == 0) { 58 | return new PagedResponse<>(Collections.emptyList(), photos.getNumber(), photos.getSize(), 59 | photos.getTotalElements(), photos.getTotalPages(), photos.isLast()); 60 | } 61 | return new PagedResponse<>(photoResponses, photos.getNumber(), photos.getSize(), photos.getTotalElements(), 62 | photos.getTotalPages(), photos.isLast()); 63 | 64 | } 65 | 66 | @Override 67 | public PhotoResponse getPhoto(Long id) { 68 | Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(PHOTO, ID, id)); 69 | 70 | return new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), 71 | photo.getThumbnailUrl(), photo.getAlbum().getId()); 72 | } 73 | 74 | @Override 75 | public PhotoResponse updatePhoto(Long id, PhotoRequest photoRequest, UserPrincipal currentUser) { 76 | Album album = albumRepository.findById(photoRequest.getAlbumId()) 77 | .orElseThrow(() -> new ResourceNotFoundException(ALBUM, ID, photoRequest.getAlbumId())); 78 | Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(PHOTO, ID, id)); 79 | if (photo.getAlbum().getUser().getId().equals(currentUser.getId()) 80 | || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 81 | photo.setTitle(photoRequest.getTitle()); 82 | photo.setThumbnailUrl(photoRequest.getThumbnailUrl()); 83 | photo.setAlbum(album); 84 | Photo updatedPhoto = photoRepository.save(photo); 85 | return new PhotoResponse(updatedPhoto.getId(), updatedPhoto.getTitle(), 86 | updatedPhoto.getUrl(), updatedPhoto.getThumbnailUrl(), updatedPhoto.getAlbum().getId()); 87 | } 88 | 89 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to update this photo"); 90 | 91 | throw new UnauthorizedException(apiResponse); 92 | } 93 | 94 | @Override 95 | public PhotoResponse addPhoto(PhotoRequest photoRequest, UserPrincipal currentUser) { 96 | Album album = albumRepository.findById(photoRequest.getAlbumId()) 97 | .orElseThrow(() -> new ResourceNotFoundException(ALBUM, ID, photoRequest.getAlbumId())); 98 | if (album.getUser().getId().equals(currentUser.getId())) { 99 | Photo photo = new Photo(photoRequest.getTitle(), photoRequest.getUrl(), photoRequest.getThumbnailUrl(), 100 | album); 101 | Photo newPhoto = photoRepository.save(photo); 102 | return new PhotoResponse(newPhoto.getId(), newPhoto.getTitle(), newPhoto.getUrl(), 103 | newPhoto.getThumbnailUrl(), newPhoto.getAlbum().getId()); 104 | } 105 | 106 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to add photo in this album"); 107 | 108 | throw new UnauthorizedException(apiResponse); 109 | } 110 | 111 | @Override 112 | public ApiResponse deletePhoto(Long id, UserPrincipal currentUser) { 113 | Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(PHOTO, ID, id)); 114 | if (photo.getAlbum().getUser().getId().equals(currentUser.getId()) 115 | || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 116 | photoRepository.deleteById(id); 117 | return new ApiResponse(Boolean.TRUE, "Photo deleted successfully"); 118 | } 119 | 120 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to delete this photo"); 121 | 122 | throw new UnauthorizedException(apiResponse); 123 | } 124 | 125 | @Override 126 | public PagedResponse getAllPhotosByAlbum(Long albumId, int page, int size) { 127 | AppUtils.validatePageNumberAndSize(page, size); 128 | 129 | Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, AppConstants.CREATED_AT); 130 | 131 | Page photos = photoRepository.findByAlbumId(albumId, pageable); 132 | 133 | List photoResponses = new ArrayList<>(photos.getContent().size()); 134 | for (Photo photo : photos.getContent()) { 135 | photoResponses.add(new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), 136 | photo.getThumbnailUrl(), photo.getAlbum().getId())); 137 | } 138 | 139 | return new PagedResponse<>(photoResponses, photos.getNumber(), photos.getSize(), photos.getTotalElements(), 140 | photos.getTotalPages(), photos.isLast()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /src/main/java/com/sopromadze/blogapi/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.sopromadze.blogapi.service.impl; 2 | 3 | import com.sopromadze.blogapi.exception.AccessDeniedException; 4 | import com.sopromadze.blogapi.exception.AppException; 5 | import com.sopromadze.blogapi.exception.BadRequestException; 6 | import com.sopromadze.blogapi.exception.ResourceNotFoundException; 7 | import com.sopromadze.blogapi.exception.UnauthorizedException; 8 | import com.sopromadze.blogapi.model.role.Role; 9 | import com.sopromadze.blogapi.model.role.RoleName; 10 | import com.sopromadze.blogapi.model.user.Address; 11 | import com.sopromadze.blogapi.model.user.Company; 12 | import com.sopromadze.blogapi.model.user.Geo; 13 | import com.sopromadze.blogapi.model.user.User; 14 | import com.sopromadze.blogapi.payload.ApiResponse; 15 | import com.sopromadze.blogapi.payload.InfoRequest; 16 | import com.sopromadze.blogapi.payload.UserIdentityAvailability; 17 | import com.sopromadze.blogapi.payload.UserProfile; 18 | import com.sopromadze.blogapi.payload.UserSummary; 19 | import com.sopromadze.blogapi.repository.PostRepository; 20 | import com.sopromadze.blogapi.repository.RoleRepository; 21 | import com.sopromadze.blogapi.repository.UserRepository; 22 | import com.sopromadze.blogapi.security.UserPrincipal; 23 | import com.sopromadze.blogapi.service.UserService; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.http.HttpStatus; 26 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 27 | import org.springframework.security.crypto.password.PasswordEncoder; 28 | import org.springframework.stereotype.Service; 29 | 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | @Service 34 | public class UserServiceImpl implements UserService { 35 | @Autowired 36 | private UserRepository userRepository; 37 | 38 | @Autowired 39 | private PostRepository postRepository; 40 | 41 | @Autowired 42 | private RoleRepository roleRepository; 43 | 44 | @Autowired 45 | private PasswordEncoder passwordEncoder; 46 | 47 | @Override 48 | public UserSummary getCurrentUser(UserPrincipal currentUser) { 49 | return new UserSummary(currentUser.getId(), currentUser.getUsername(), currentUser.getFirstName(), 50 | currentUser.getLastName()); 51 | } 52 | 53 | @Override 54 | public UserIdentityAvailability checkUsernameAvailability(String username) { 55 | Boolean isAvailable = !userRepository.existsByUsername(username); 56 | return new UserIdentityAvailability(isAvailable); 57 | } 58 | 59 | @Override 60 | public UserIdentityAvailability checkEmailAvailability(String email) { 61 | Boolean isAvailable = !userRepository.existsByEmail(email); 62 | return new UserIdentityAvailability(isAvailable); 63 | } 64 | 65 | @Override 66 | public UserProfile getUserProfile(String username) { 67 | User user = userRepository.getUserByName(username); 68 | 69 | Long postCount = postRepository.countByCreatedBy(user.getId()); 70 | 71 | return new UserProfile(user.getId(), user.getUsername(), user.getFirstName(), user.getLastName(), 72 | user.getCreatedAt(), user.getEmail(), user.getAddress(), user.getPhone(), user.getWebsite(), 73 | user.getCompany(), postCount); 74 | } 75 | 76 | @Override 77 | public User addUser(User user) { 78 | if (userRepository.existsByUsername(user.getUsername())) { 79 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "Username is already taken"); 80 | throw new BadRequestException(apiResponse); 81 | } 82 | 83 | if (userRepository.existsByEmail(user.getEmail())) { 84 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "Email is already taken"); 85 | throw new BadRequestException(apiResponse); 86 | } 87 | 88 | List roles = new ArrayList<>(); 89 | roles.add( 90 | roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); 91 | user.setRoles(roles); 92 | 93 | user.setPassword(passwordEncoder.encode(user.getPassword())); 94 | return userRepository.save(user); 95 | } 96 | 97 | @Override 98 | public User updateUser(User newUser, String username, UserPrincipal currentUser) { 99 | User user = userRepository.getUserByName(username); 100 | if (user.getId().equals(currentUser.getId()) 101 | || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 102 | user.setFirstName(newUser.getFirstName()); 103 | user.setLastName(newUser.getLastName()); 104 | user.setPassword(passwordEncoder.encode(newUser.getPassword())); 105 | user.setAddress(newUser.getAddress()); 106 | user.setPhone(newUser.getPhone()); 107 | user.setWebsite(newUser.getWebsite()); 108 | user.setCompany(newUser.getCompany()); 109 | 110 | return userRepository.save(user); 111 | 112 | } 113 | 114 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to update profile of: " + username); 115 | throw new UnauthorizedException(apiResponse); 116 | 117 | } 118 | 119 | @Override 120 | public ApiResponse deleteUser(String username, UserPrincipal currentUser) { 121 | User user = userRepository.findByUsername(username) 122 | .orElseThrow(() -> new ResourceNotFoundException("User", "id", username)); 123 | if (!user.getId().equals(currentUser.getId()) || !currentUser.getAuthorities() 124 | .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 125 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to delete profile of: " + username); 126 | throw new AccessDeniedException(apiResponse); 127 | } 128 | 129 | userRepository.deleteById(user.getId()); 130 | 131 | return new ApiResponse(Boolean.TRUE, "You successfully deleted profile of: " + username); 132 | } 133 | 134 | @Override 135 | public ApiResponse giveAdmin(String username) { 136 | User user = userRepository.getUserByName(username); 137 | List roles = new ArrayList<>(); 138 | roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN) 139 | .orElseThrow(() -> new AppException("User role not set"))); 140 | roles.add( 141 | roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); 142 | user.setRoles(roles); 143 | userRepository.save(user); 144 | return new ApiResponse(Boolean.TRUE, "You gave ADMIN role to user: " + username); 145 | } 146 | 147 | @Override 148 | public ApiResponse removeAdmin(String username) { 149 | User user = userRepository.getUserByName(username); 150 | List roles = new ArrayList<>(); 151 | roles.add( 152 | roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); 153 | user.setRoles(roles); 154 | userRepository.save(user); 155 | return new ApiResponse(Boolean.TRUE, "You took ADMIN role from user: " + username); 156 | } 157 | 158 | @Override 159 | public UserProfile setOrUpdateInfo(UserPrincipal currentUser, InfoRequest infoRequest) { 160 | User user = userRepository.findByUsername(currentUser.getUsername()) 161 | .orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); 162 | Geo geo = new Geo(infoRequest.getLat(), infoRequest.getLng()); 163 | Address address = new Address(infoRequest.getStreet(), infoRequest.getSuite(), infoRequest.getCity(), 164 | infoRequest.getZipcode(), geo); 165 | Company company = new Company(infoRequest.getCompanyName(), infoRequest.getCatchPhrase(), infoRequest.getBs()); 166 | if (user.getId().equals(currentUser.getId()) 167 | || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { 168 | user.setAddress(address); 169 | user.setCompany(company); 170 | user.setWebsite(infoRequest.getWebsite()); 171 | user.setPhone(infoRequest.getPhone()); 172 | User updatedUser = userRepository.save(user); 173 | 174 | Long postCount = postRepository.countByCreatedBy(updatedUser.getId()); 175 | 176 | return new UserProfile(updatedUser.getId(), updatedUser.getUsername(), 177 | updatedUser.getFirstName(), updatedUser.getLastName(), updatedUser.getCreatedAt(), 178 | updatedUser.getEmail(), updatedUser.getAddress(), updatedUser.getPhone(), updatedUser.getWebsite(), 179 | updatedUser.getCompany(), postCount); 180 | } 181 | 182 | ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to update users profile", HttpStatus.FORBIDDEN); 183 | throw new AccessDeniedException(apiResponse); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/resources/blogapi.sql: -------------------------------------------------------------------------------- 1 | UNLOCK TABLES; 2 | 3 | DROP TABLE IF EXISTS `post_tag`; 4 | DROP TABLE IF EXISTS `tags`; 5 | DROP TABLE IF EXISTS `user_role`; 6 | DROP TABLE IF EXISTS `roles`; 7 | DROP TABLE IF EXISTS `comments`; 8 | DROP TABLE IF EXISTS `posts`; 9 | DROP TABLE IF EXISTS `photos`; 10 | DROP TABLE IF EXISTS `albums`; 11 | DROP TABLE IF EXISTS `todos`; 12 | DROP TABLE IF EXISTS `users`; 13 | DROP TABLE IF EXISTS `address`; 14 | DROP TABLE IF EXISTS `company`; 15 | DROP TABLE IF EXISTS `geo`; 16 | 17 | CREATE TABLE `tags` ( 18 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 19 | `name` varchar(255) NOT NULL, 20 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 21 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 22 | `created_by` bigint(19) unsigned NOT NULL, 23 | `updated_by` bigint(19) unsigned NOT NULL, 24 | PRIMARY KEY (`id`) 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 26 | 27 | CREATE TABLE `geo` ( 28 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 29 | `lat` varchar(255), 30 | `lng` varchar(255), 31 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 32 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 33 | `created_by` bigint(19) unsigned DEFAULT NULL, 34 | `updated_by` bigint(19) unsigned DEFAULT NULL, 35 | PRIMARY KEY (`id`) 36 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 37 | 38 | CREATE TABLE `company` ( 39 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 40 | `name` varchar(255), 41 | `catch_phrase` varchar(255), 42 | `bs` varchar(255), 43 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 44 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 45 | `created_by` bigint(19) unsigned DEFAULT NULL, 46 | `updated_by` bigint(19) unsigned DEFAULT NULL, 47 | PRIMARY KEY (`id`) 48 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 49 | 50 | CREATE TABLE `address` ( 51 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 52 | `street` varchar(255), 53 | `suite` varchar(255), 54 | `city` varchar(255), 55 | `zipcode` varchar(255), 56 | `geo_id` bigint(19) unsigned DEFAULT NULL, 57 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 58 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 59 | `created_by` bigint(19) unsigned DEFAULT NULL, 60 | `updated_by` bigint(19) unsigned DEFAULT NULL, 61 | PRIMARY KEY (`id`), 62 | KEY `fk_geo` (`geo_id`), 63 | CONSTRAINT `fk_geo` FOREIGN KEY (`geo_id`) REFERENCES `geo` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 64 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 65 | 66 | CREATE TABLE `users` ( 67 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 68 | `first_name` varchar(255) NOT NULL, 69 | `last_name` varchar(255) NOT NULL, 70 | `username` varchar(255) NOT NULL, 71 | `password` varchar(255) NOT NULL, 72 | `email` varchar(255) NOT NULL, 73 | `address_id` bigint(19) unsigned DEFAULT NULL, 74 | `phone` varchar(255), 75 | `website` varchar(255), 76 | `company_id` bigint(19) unsigned DEFAULT NULL, 77 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 78 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 79 | PRIMARY KEY (`id`), 80 | KEY `fk_address` (`address_id`), 81 | KEY `fk_company` (`company_id`), 82 | CONSTRAINT `fk_address` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 83 | CONSTRAINT `fk_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 84 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 85 | 86 | CREATE TABLE `todos` ( 87 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 88 | `title` varchar(255) NOT NULL, 89 | `completed` boolean default false, 90 | `user_id` bigint(19) unsigned DEFAULT NULL, 91 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 92 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 93 | `created_by` bigint(19) unsigned DEFAULT NULL, 94 | `updated_by` bigint(19) unsigned DEFAULT NULL, 95 | PRIMARY KEY (`id`), 96 | KEY `fk_user_todos` (`user_id`), 97 | CONSTRAINT `fk_user_todos` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 98 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 99 | 100 | CREATE TABLE `albums` ( 101 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 102 | `title` varchar(255) NOT NULL, 103 | `user_id` bigint(19) unsigned DEFAULT NULL, 104 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 105 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 106 | `created_by` bigint(19) unsigned DEFAULT NULL, 107 | `updated_by` bigint(19) unsigned DEFAULT NULL, 108 | PRIMARY KEY (`id`), 109 | KEY `fk_user_album` (`user_id`), 110 | CONSTRAINT `fk_user_album` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 111 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 112 | 113 | CREATE TABLE `photos` ( 114 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 115 | `title` varchar(255) NOT NULL, 116 | `url` varchar(255) NOT NULL, 117 | `thumbnail_url` varchar(255) NOT NULL, 118 | `album_id` bigint(19) unsigned DEFAULT NULL, 119 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 120 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 121 | `created_by` bigint(19) unsigned DEFAULT NULL, 122 | `updated_by` bigint(19) unsigned DEFAULT NULL, 123 | PRIMARY KEY (`id`), 124 | KEY `fk_album` (`album_id`), 125 | CONSTRAINT `fk_album` FOREIGN KEY (`album_id`) REFERENCES `albums` (`id`) 126 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 127 | 128 | CREATE TABLE `posts` ( 129 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 130 | `title` varchar(255) NOT NULL, 131 | `body` text NOT NULL, 132 | `user_id` bigint(19) unsigned DEFAULT NULL, 133 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 134 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 135 | `created_by` bigint(19) unsigned DEFAULT NULL, 136 | `updated_by` bigint(19) unsigned DEFAULT NULL, 137 | PRIMARY KEY (`id`), 138 | KEY `fk_user_post` (`user_id`), 139 | CONSTRAINT `fk_user_post` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 140 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 141 | 142 | CREATE TABLE `post_tag` ( 143 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 144 | `post_id` bigint(19) unsigned NOT NULL, 145 | `tag_id` bigint(19) unsigned NOT NULL, 146 | PRIMARY KEY (`id`), 147 | KEY `fk_posttag_post_id` (`post_id`), 148 | KEY `fk_posttag_tag_id` (`tag_id`), 149 | CONSTRAINT `fk_posttag_post_id` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`), 150 | CONSTRAINT `fk_posttag_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) 151 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 152 | 153 | CREATE TABLE `comments` ( 154 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 155 | `name` varchar(255) NOT NULL, 156 | `email` varchar(255) NOT NULL, 157 | `body` text NOT NULL, 158 | `post_id` bigint(19) unsigned DEFAULT NULL, 159 | `user_id` bigint(19) unsigned DEFAULT NULL, 160 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 161 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 162 | `created_by` bigint(19) unsigned NOT NULL, 163 | `updated_by` bigint(19) unsigned NOT NULL, 164 | PRIMARY KEY (`id`), 165 | KEY `fk_comment_post` (`post_id`), 166 | KEY `fk_comment_user` (`user_id`), 167 | CONSTRAINT `fk_comment_post` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`), 168 | CONSTRAINT `fk_comment_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 169 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 170 | 171 | CREATE TABLE `roles` ( 172 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 173 | `name` varchar(255) NOT NULL, 174 | PRIMARY KEY (`id`) 175 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 176 | 177 | CREATE TABLE `user_role` ( 178 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 179 | `user_id` bigint(19) unsigned NOT NULL, 180 | `role_id` bigint(19) unsigned NOT NULL, 181 | PRIMARY KEY (`id`), 182 | KEY `fk_security_user_id` (`user_id`), 183 | KEY `fk_security_role_id` (`role_id`), 184 | CONSTRAINT `fk_security_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), 185 | CONSTRAINT `fk_security_role_id` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) 186 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 187 | 188 | LOCK TABLES `roles` WRITE; 189 | INSERT INTO `roles` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER'); 190 | UNLOCK TABLES; 191 | -------------------------------------------------------------------------------- /data/blogapi.sql: -------------------------------------------------------------------------------- 1 | USE blogapi; 2 | 3 | UNLOCK TABLES; 4 | 5 | DROP TABLE IF EXISTS `post_tag`; 6 | DROP TABLE IF EXISTS `tags`; 7 | DROP TABLE IF EXISTS `user_role`; 8 | DROP TABLE IF EXISTS `roles`; 9 | DROP TABLE IF EXISTS `comments`; 10 | DROP TABLE IF EXISTS `posts`; 11 | DROP TABLE IF EXISTS `photos`; 12 | DROP TABLE IF EXISTS `albums`; 13 | DROP TABLE IF EXISTS `todos`; 14 | DROP TABLE IF EXISTS `users`; 15 | DROP TABLE IF EXISTS `address`; 16 | DROP TABLE IF EXISTS `company`; 17 | DROP TABLE IF EXISTS `geo`; 18 | 19 | CREATE TABLE `tags` ( 20 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 21 | `name` varchar(255) NOT NULL, 22 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 23 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 24 | `created_by` bigint(19) unsigned NOT NULL, 25 | `updated_by` bigint(19) unsigned NOT NULL, 26 | PRIMARY KEY (`id`) 27 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 28 | 29 | CREATE TABLE `geo` ( 30 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 31 | `lat` varchar(255), 32 | `lng` varchar(255), 33 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 34 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 35 | `created_by` bigint(19) unsigned DEFAULT NULL, 36 | `updated_by` bigint(19) unsigned DEFAULT NULL, 37 | PRIMARY KEY (`id`) 38 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 39 | 40 | CREATE TABLE `company` ( 41 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 42 | `name` varchar(255), 43 | `catch_phrase` varchar(255), 44 | `bs` varchar(255), 45 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 46 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 47 | `created_by` bigint(19) unsigned DEFAULT NULL, 48 | `updated_by` bigint(19) unsigned DEFAULT NULL, 49 | PRIMARY KEY (`id`) 50 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 51 | 52 | CREATE TABLE `address` ( 53 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 54 | `street` varchar(255), 55 | `suite` varchar(255), 56 | `city` varchar(255), 57 | `zipcode` varchar(255), 58 | `geo_id` bigint(19) unsigned DEFAULT NULL, 59 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 60 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 61 | `created_by` bigint(19) unsigned DEFAULT NULL, 62 | `updated_by` bigint(19) unsigned DEFAULT NULL, 63 | PRIMARY KEY (`id`), 64 | KEY `fk_geo` (`geo_id`), 65 | CONSTRAINT `fk_geo` FOREIGN KEY (`geo_id`) REFERENCES `geo` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 66 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 67 | 68 | CREATE TABLE `users` ( 69 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 70 | `first_name` varchar(255) NOT NULL, 71 | `last_name` varchar(255) NOT NULL, 72 | `username` varchar(255) NOT NULL, 73 | `password` varchar(255) NOT NULL, 74 | `email` varchar(255) NOT NULL, 75 | `address_id` bigint(19) unsigned DEFAULT NULL, 76 | `phone` varchar(255), 77 | `website` varchar(255), 78 | `company_id` bigint(19) unsigned DEFAULT NULL, 79 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 80 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 81 | PRIMARY KEY (`id`), 82 | KEY `fk_address` (`address_id`), 83 | KEY `fk_company` (`company_id`), 84 | CONSTRAINT `fk_address` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 85 | CONSTRAINT `fk_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 86 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 87 | 88 | CREATE TABLE `todos` ( 89 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 90 | `title` varchar(255) NOT NULL, 91 | `completed` boolean default false, 92 | `user_id` bigint(19) unsigned DEFAULT NULL, 93 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 94 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 95 | `created_by` bigint(19) unsigned DEFAULT NULL, 96 | `updated_by` bigint(19) unsigned DEFAULT NULL, 97 | PRIMARY KEY (`id`), 98 | KEY `fk_user_todos` (`user_id`), 99 | CONSTRAINT `fk_user_todos` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 100 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 101 | 102 | CREATE TABLE `albums` ( 103 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 104 | `title` varchar(255) NOT NULL, 105 | `user_id` bigint(19) unsigned DEFAULT NULL, 106 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 107 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 108 | `created_by` bigint(19) unsigned DEFAULT NULL, 109 | `updated_by` bigint(19) unsigned DEFAULT NULL, 110 | PRIMARY KEY (`id`), 111 | KEY `fk_user_album` (`user_id`), 112 | CONSTRAINT `fk_user_album` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 113 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 114 | 115 | CREATE TABLE `photos` ( 116 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 117 | `title` varchar(255) NOT NULL, 118 | `url` varchar(255) NOT NULL, 119 | `thumbnail_url` varchar(255) NOT NULL, 120 | `album_id` bigint(19) unsigned DEFAULT NULL, 121 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 122 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 123 | `created_by` bigint(19) unsigned DEFAULT NULL, 124 | `updated_by` bigint(19) unsigned DEFAULT NULL, 125 | PRIMARY KEY (`id`), 126 | KEY `fk_album` (`album_id`), 127 | CONSTRAINT `fk_album` FOREIGN KEY (`album_id`) REFERENCES `albums` (`id`) 128 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 129 | 130 | CREATE TABLE `posts` ( 131 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 132 | `title` varchar(255) NOT NULL, 133 | `body` text NOT NULL, 134 | `user_id` bigint(19) unsigned DEFAULT NULL, 135 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 136 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 137 | `created_by` bigint(19) unsigned DEFAULT NULL, 138 | `updated_by` bigint(19) unsigned DEFAULT NULL, 139 | PRIMARY KEY (`id`), 140 | KEY `fk_user_post` (`user_id`), 141 | CONSTRAINT `fk_user_post` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 142 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 143 | 144 | CREATE TABLE `post_tag` ( 145 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 146 | `post_id` bigint(19) unsigned NOT NULL, 147 | `tag_id` bigint(19) unsigned NOT NULL, 148 | PRIMARY KEY (`id`), 149 | KEY `fk_posttag_post_id` (`post_id`), 150 | KEY `fk_posttag_tag_id` (`tag_id`), 151 | CONSTRAINT `fk_posttag_post_id` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`), 152 | CONSTRAINT `fk_posttag_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) 153 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 154 | 155 | CREATE TABLE `comments` ( 156 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 157 | `name` varchar(255) NOT NULL, 158 | `email` varchar(255) NOT NULL, 159 | `body` text NOT NULL, 160 | `post_id` bigint(19) unsigned DEFAULT NULL, 161 | `user_id` bigint(19) unsigned DEFAULT NULL, 162 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 163 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 164 | `created_by` bigint(19) unsigned NOT NULL, 165 | `updated_by` bigint(19) unsigned NOT NULL, 166 | PRIMARY KEY (`id`), 167 | KEY `fk_comment_post` (`post_id`), 168 | KEY `fk_comment_user` (`user_id`), 169 | CONSTRAINT `fk_comment_post` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`), 170 | CONSTRAINT `fk_comment_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 171 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 172 | 173 | CREATE TABLE `roles` ( 174 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 175 | `name` varchar(255) NOT NULL, 176 | PRIMARY KEY (`id`) 177 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 178 | 179 | CREATE TABLE `user_role` ( 180 | `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, 181 | `user_id` bigint(19) unsigned NOT NULL, 182 | `role_id` bigint(19) unsigned NOT NULL, 183 | PRIMARY KEY (`id`), 184 | KEY `fk_security_user_id` (`user_id`), 185 | KEY `fk_security_role_id` (`role_id`), 186 | CONSTRAINT `fk_security_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), 187 | CONSTRAINT `fk_security_role_id` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) 188 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 189 | 190 | LOCK TABLES `roles` WRITE; 191 | INSERT INTO `roles` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER'); 192 | UNLOCK TABLES; 193 | --------------------------------------------------------------------------------