# Реалізація інформаційного та програмного забезпечення

# SQL-скрипт для створення на початкового наповнення бази даних

-- MySQL Script generated by MySQL Workbench
-- Fri Oct 20 09:55:08 2023
-- Model: New Model    Version: 1.0
-- MySQL Workbench Forward Engineering

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

-- -----------------------------------------------------
-- Schema db_coursework
-- -----------------------------------------------------
DROP SCHEMA IF EXISTS `db_coursework` ;

-- -----------------------------------------------------
-- Schema db_coursework
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `db_coursework` DEFAULT CHARACTER SET utf8 ;

USE `db_coursework` ;

-- -----------------------------------------------------
-- Table `db_coursework`.`Role`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Role` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Role` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `description` VARCHAR(180) NULL DEFAULT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Permission`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Permission` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Permission` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `action` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Project`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Project` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Project` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `description` VARCHAR(180) NULL DEFAULT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Team`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Team` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Team` (
  `id` INT NOT NULL,
  `name` VARCHAR(100) NULL,
  `motto` VARCHAR(255) NULL,
  `Project_id` INT NOT NULL,
  INDEX `fk_Team_Project1_idx` (`Project_id` ASC) VISIBLE,
  PRIMARY KEY (`id`),
  CONSTRAINT `fk_Team_Project1`
    FOREIGN KEY (`Project_id`)
    REFERENCES `db_coursework`.`Project` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`User`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`User` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`User` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `nickname` VARCHAR(45) NOT NULL,
  `email` VARCHAR(45) NOT NULL,
  `password` VARCHAR(45) NOT NULL,
  `photo` VARCHAR(255) NULL DEFAULT NULL,
  `is_banned` TINYINT NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Collaborator`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Collaborator` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Collaborator` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `Role_id` INT NOT NULL,
  `User_id` INT NOT NULL,
  `Team_id` INT NOT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_Member_Role1_idx` (`Role_id` ASC) VISIBLE,
  INDEX `fk_Member_User1_idx` (`User_id` ASC) VISIBLE,
  INDEX `fk_Collaborator_Team1_idx` (`Team_id` ASC) VISIBLE,
  CONSTRAINT `fk_Member_Role1`
    FOREIGN KEY (`Role_id`)
    REFERENCES `db_coursework`.`Role` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Member_User1`
    FOREIGN KEY (`User_id`)
    REFERENCES `db_coursework`.`User` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Collaborator_Team1`
    FOREIGN KEY (`Team_id`)
    REFERENCES `db_coursework`.`Team` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Sprint`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Sprint` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Sprint` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `goal` VARCHAR(100) NOT NULL,
  `startdate` DATETIME NULL DEFAULT NULL,
  `enddate` DATETIME NULL DEFAULT NULL,
  `Project_id` INT NOT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_Sprint_Project1_idx` (`Project_id` ASC) VISIBLE,
  CONSTRAINT `fk_Sprint_Project1`
    FOREIGN KEY (`Project_id`)
    REFERENCES `db_coursework`.`Project` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Tag`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Tag` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Tag` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `description` VARCHAR(180) NULL DEFAULT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Task`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Task` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Task` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `description` VARCHAR(180) NULL DEFAULT NULL,
  `deadline` DATETIME NULL DEFAULT NULL,
  `creation_date` DATETIME NOT NULL,
  `Sprint_id` INT NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_Task_Sprint1_idx` (`Sprint_id` ASC) VISIBLE,
  CONSTRAINT `fk_Task_Sprint1`
    FOREIGN KEY (`Sprint_id`)
    REFERENCES `db_coursework`.`Sprint` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Assignment`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Assignment` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Assignment` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `datatime` DATETIME NULL DEFAULT NULL,
  `Collaborator_id` INT NOT NULL,
  `Task_id` INT NOT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_Assignment_Member1_idx` (`Collaborator_id` ASC) VISIBLE,
  INDEX `fk_Assignment_Task1_idx` (`Task_id` ASC) VISIBLE,
  CONSTRAINT `fk_Assignment_Member1`
    FOREIGN KEY (`Collaborator_id`)
    REFERENCES `db_coursework`.`Collaborator` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Assignment_Task1`
    FOREIGN KEY (`Task_id`)
    REFERENCES `db_coursework`.`Task` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Task_comment`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Task_comment` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Task_comment` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `subject` VARCHAR(45) NOT NULL,
  `text` VARCHAR(45) NOT NULL,
  `datetime` DATETIME NULL DEFAULT NULL,
  `Author_id` INT NOT NULL,
  `Task_id` INT NOT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_Task_comment_Member1_idx` (`Author_id` ASC) VISIBLE,
  INDEX `fk_Task_comment_Task1_idx` (`Task_id` ASC) VISIBLE,
  CONSTRAINT `fk_Task_comment_Member1`
    FOREIGN KEY (`Author_id`)
    REFERENCES `db_coursework`.`Collaborator` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Task_comment_Task1`
    FOREIGN KEY (`Task_id`)
    REFERENCES `db_coursework`.`Task` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Grant`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Grant` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Grant` (
  `Role_id` INT NOT NULL,
  `Permission_id` INT NOT NULL,
  INDEX `fk_Grant_Role_idx` (`Role_id` ASC) VISIBLE,
  PRIMARY KEY (`Role_id`, `Permission_id`),
  INDEX `fk_Grant_Permission2_idx` (`Permission_id` ASC) VISIBLE,
  CONSTRAINT `fk_Grant_Role0`
    FOREIGN KEY (`Role_id`)
    REFERENCES `db_coursework`.`Role` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Grant_Permission2`
    FOREIGN KEY (`Permission_id`)
    REFERENCES `db_coursework`.`Permission` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Label`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Label` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Label` (
  `Task_id` INT NOT NULL,
  `Tag_id` INT NOT NULL,
  PRIMARY KEY (`Task_id`, `Tag_id`),
  INDEX `fk_Label_Tag1_idx` (`Tag_id` ASC) VISIBLE,
  CONSTRAINT `fk_Label_Task1`
    FOREIGN KEY (`Task_id`)
    REFERENCES `db_coursework`.`Task` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Label_Tag1`
    FOREIGN KEY (`Tag_id`)
    REFERENCES `db_coursework`.`Tag` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `db_coursework`.`Action`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `db_coursework`.`Action` ;

CREATE TABLE IF NOT EXISTS `db_coursework`.`Action` (
  `date` DATETIME NULL DEFAULT NULL,
  `Sprint_id` INT NOT NULL,
  `Task_id` INT NOT NULL,
  `Assignment_id` INT NOT NULL,
  `Collaborator_id` INT NOT NULL,
  PRIMARY KEY (`Sprint_id`, `Task_id`, `Assignment_id`, `Collaborator_id`),
  INDEX `fk_Action_Task1_idx` (`Task_id` ASC) VISIBLE,
  INDEX `fk_Action_Assignment1_idx` (`Assignment_id` ASC) VISIBLE,
  INDEX `fk_Action_Collaborator1_idx` (`Collaborator_id` ASC) VISIBLE,
  CONSTRAINT `fk_Action_Sprint1`
    FOREIGN KEY (`Sprint_id`)
    REFERENCES `db_coursework`.`Sprint` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Action_Task1`
    FOREIGN KEY (`Task_id`)
    REFERENCES `db_coursework`.`Task` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Action_Assignment1`
    FOREIGN KEY (`Assignment_id`)
    REFERENCES `db_coursework`.`Assignment` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Action_Collaborator1`
    FOREIGN KEY (`Collaborator_id`)
    REFERENCES `db_coursework`.`Collaborator` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;


-- Додавання тестових даних




-- Початок транзакції
START TRANSACTION;

-- Додавання даних в таблицю `db_coursework`.`Permission`
INSERT INTO `db_coursework`.`Permission` (`action`)
VALUES
    -- collaborator
    ('EditUser'),
    ('CreateTask'),
    ('EditTask'),
    ('DeleteTask'),
    ('FilterTask'),
    ('CommentTask'),
    -- teamlead
    ('CreateProject'),
    ('DeleteProject'),
    ('CreateSprint'),
    ('FinishSprint'),
    ('AddMember'),
    ('DeleteMember'),
    -- admin
    ('UserSupport'),
    ('BanUser'),
    ('UnBanUser');


-- Додавання даних в таблицю `db_coursework`.`Role`
INSERT INTO `db_coursework`.`Role` (`name`, `description`)
VALUES
    ('Administrator', 'Administrator role'),
    ('Team-lead', 'Team-lead role'),
    ('Collaborator', 'Developer role');

-- Додавання даних в таблицю `db_coursework`.`Grant`
INSERT INTO `db_coursework`.`Grant` (`Role_id`, `Permission_id`)
VALUES
    (1, 1),
    (1, 2),
    (1, 3),
    (1, 4),
    (1, 5),
    (1, 6),
    (1, 7),
    (1, 8),
    (1, 9),
    (1, 10),
    (1, 11),
    (1, 12),
    (1, 13),
    (1, 14),
    (1, 15),

    (2, 1),
    (2, 2),
    (2, 3),
    (2, 4),
    (2, 5),
    (2, 6),
    (2, 7),
    (2, 8),
    (2, 9),
    (2, 10),
    (2, 11),
    (2, 12),

    (3, 1),
    (3, 2),
    (3, 3),
    (3, 4),
    (3, 5),
    (3, 6);


-- Додавання даних в таблицю `db_coursework`.`Project`
INSERT INTO `db_coursework`.`Project` (`name`, `description`)
VALUES
    ('Project 1', 'Description for Project 1'),
    ('Project 2', 'Description for Project 2');

-- Додавання даних в таблицю `db_coursework`.`Team`
INSERT INTO `db_coursework`.`Team` (`id`, `name`, `motto`, `Project_id`)
VALUES
    (1, 'Team 1', 'Motto for Team 1', 1),
    (2, 'Team 2', 'Motto for Team 2', 2);

-- Додавання даних в таблицю `db_coursework`.`User`
INSERT INTO `db_coursework`.`User` (`nickname`, `email`, `password`, `photo`, `is_banned`)
VALUES
    ('User1', 'user1@example.com', 'password1', 'link.com/photo', 0),
    ('User2', 'user2@example.com', 'password2', 'link.com/photo', 0);

-- Додавання даних в таблицю `db_coursework`.`Collaborator`
INSERT INTO `db_coursework`.`Collaborator` (`Role_id`, `User_id`, `Team_id`)
VALUES
    (1, 1, 1),  -- Admin User 1 in Team 1
    (2, 2, 2);  -- Manager User 2 in Team 2

-- Додавання тестових даних в таблицю `db_coursework.Sprint`
INSERT INTO `db_coursework`.`Sprint` (`name`, `goal`, `startdate`, `enddate`, `Project_id`)
VALUES
    ('Sprint 1', 'Complete Task 1', '2023-10-18 10:00:00', '2023-10-22 18:00:00', 1),
    ('Sprint 2', 'Finish Project 2', '2023-10-25 09:00:00', '2023-10-29 17:00:00', 2),
    ('Sprint 3', 'Implement Feature 3', '2023-11-01 08:00:00', '2023-11-05 16:00:00', 1);


-- Додавання даних в таблицю `db_coursework`.`Task`
INSERT INTO `db_coursework`.`Task` (`name`, `description`, `deadline`, `creation_date`, `Sprint_id`)
VALUES
    ('Task 1', 'Description for Task 1', '2023-10-31 12:00:00', NOW(), 1),
    ('Task 2', 'Description for Task 2', '2023-11-15 14:30:00', NOW(), 2),
    ('Task 3', 'Description for Task 3', '2023-11-20 10:00:00', NOW(), 3);

-- Додавання тестових даних в таблицю `db_coursework.Assignment`
INSERT INTO `db_coursework`.`Assignment` (`datatime`, `Collaborator_id`, `Task_id`)
VALUES
    ('2023-10-18 11:30:00', 1, 1),
    ('2023-10-19 14:15:00', 2, 2),
    ('2023-10-20 09:45:00', 1, 3);


COMMIT;

# RESTfull сервіс для управління даними

Цей RESTfull сервіс розроблений із застосуванням Spring Framework, який забезпечує потужну підтримку для створення веб-додатків та сервісів. Основними компонентами цього сервісу є:

  • Spring Boot - спрощує конфігурацію та розгортання додатку, забезпечуючи вбудовану підтримку серверів застосунків.
  • Spring Data JPA - використовується для взаємодії з базами даних через Java Persistence API (JPA). Він дозволяє легко створювати репозиторії для роботи з даними, автоматизуючи більшість CRUD операцій, що полегшує роботу з базою даних.
  • Swagger/OpenAPI - використовується для документації цього RESTful API, надаючи зручний інтерфейс для тестування API та ознайомлення з його специфікацією. Це значно полегшує процес інтеграції та використання API іншими розробниками та сервісами.
  • Data Transfer Objects (DTO) - згенеровані з OpenAPI специфікації, використовуються для безпечної передачі даних між різними шарами додатку. DTO допомагають уникнути зайвого викриття внутрішніх деталей моделей і підтримують чистоту API.

# Діаграма класів

На другому рівні схеми, DTO для Role, Grant, Permission, а також клас ErrorResponse згенеровані за допомогою Swagger.

# Project Management API Specification

openapi: "3.0.0"
info:
  title: "Project Management API"
  version: "1.0.0"
  description: "API for managing project roles, grants, and permissions."

servers:
  - url: "http://localhost:8080/api"

paths:
  /roles:
    get:
      summary: "Get all roles"
      tags:
        - "Roles"
      responses:
        '200':
          description: "A list of roles"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RoleDTO'
        '404':
          description: "Not Found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    post:
      summary: "Create a role"
      tags:
        - "Roles"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleDTO'
      responses:
        '201':
          description: "Role created"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleDTO'
        '400':
          description: "Bad Request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: "Conflict"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /roles/{roleId}:
    get:
      summary: "Get a role by ID"
      tags:
        - "Roles"
      parameters:
        - name: "roleId"
          in: "path"
          required: true
          schema:
            type: "integer"
      responses:
        '200':
          description: "A role object"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleDTO'
        '404':
          description: "Role not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    put:
      summary: "Update a role"
      tags:
        - "Roles"
      parameters:
        - name: "roleId"
          in: "path"
          required: true
          schema:
            type: "integer"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleDTO'
      responses:
        '200':
          description: "Role updated"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleDTO'
        '400':
          description: "Bad Request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: "Role not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    delete:
      summary: "Delete a role"
      tags:
        - "Roles"
      parameters:
        - name: "roleId"
          in: "path"
          required: true
          schema:
            type: "integer"
      responses:
        '204':
          description: "Role deleted"
        '404':
          description: "Role not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /grants:
    get:
      summary: "Get all grants"
      tags:
        - "Grants"
      responses:
        '200':
          description: "A list of grants."
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/GrantDTO'
        '404':
          description: "Not Found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    post:
      summary: "Create a grant"
      tags:
        - "Grants"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GrantDTO'
      responses:
        '201':
          description: "Grant created"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GrantDTO'
        '400':
          description: "Bad Request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: "Conflict"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /grants/{grantId}:
    get:
      summary: "Get a grant by ID"
      tags:
        - "Grants"
      parameters:
        - name: "grantId"
          in: "path"
          required: true
          schema:
            type: "integer"
      responses:
        '200':
          description: "A grant object"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GrantDTO'
        '404':
          description: "Grant not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    put:
      summary: "Update a grant"
      tags:
        - "Grants"
      parameters:
        - name: "grantId"
          in: "path"
          required: true
          schema:
            type: "integer"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GrantDTO'
      responses:
        '200':
          description: "Grant updated"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GrantDTO'
        '400':
          description: "Bad Request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: "Grant not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    delete:
      summary: "Delete a grant"
      tags:
        - "Grants"
      parameters:
        - name: "grantId"
          in: "path"
          required: true
          schema:
            type: "integer"
      responses:
        '204':
          description: "Grant deleted"
        '404':
          description: "Grant not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /grants/by-role/{roleId}:
    get:
      summary: "Get grants by role ID"
      tags:
        - "Grants"
      parameters:
        - name: "roleId"
          in: "path"
          required: true
          schema:
            type: "integer"
      responses:
        '200':
          description: "A list of grants for the specified role"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/GrantDTO'
        '400':
          description: "Bad Request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: "No grants found for the role"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /grants/by-permission/{permissionId}:
    get:
      summary: "Get grants by permission ID"
      tags:
        - "Grants"
      parameters:
        - name: "permissionId"
          in: "path"
          required: true
          schema:
            type: "integer"
      responses:
        '200':
          description: "A list of grants for the specified permission"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/GrantDTO'
        '400':
          description: "Bad Request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: "No grants found for the permission"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /permissions:
    get:
      summary: "Get all permissions"
      tags:
        - "Permissions"
      responses:
        '200':
          description: "A list of permissions."
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PermissionDTO'
        '404':
          description: "Not Found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    post:
      summary: "Create a permission"
      tags:
        - "Permissions"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PermissionDTO'
      responses:
        '201':
          description: "Permission created"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionDTO'
        '400':
          description: "Bad Request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: "Conflict"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /permissions/{permissionId}:
      get:
        summary: "Get a permission by ID"
        tags:
          - "Permissions"
        parameters:
          - name: "permissionId"
            in: "path"
            required: true
            schema:
              type: "integer"
        responses:
          '200':
            description: "A permission object"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/PermissionDTO'
          '404':
            description: "Permission not found"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse'

      put:
        summary: "Update a permission"
        tags:
          - "Permissions"
        parameters:
          - name: "permissionId"
            in: "path"
            required: true
            schema:
              type: "integer"
        requestBody:
          required: true
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionDTO'
        responses:
          '200':
            description: "Permission updated"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/PermissionDTO'
          '400':
            description: "Bad Request"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse'
          '404':
            description: "Permission not found"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse'

      delete:
        summary: "Delete a permission"
        tags:
          - "Permissions"
        parameters:
          - name: "permissionId"
            in: "path"
            required: true
            schema:
              type: "integer"
        responses:
          '204':
            description: "Permission deleted"
          '404':
            description: "Permission not found"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse'

components:
  schemas:
    RoleDTO:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
    GrantDTO:
      type: object
      properties:
        roleId:
          type: integer
          format: int64
        permissionId:
          type: integer
          format: int64
    PermissionDTO:
      type: object
      properties:
        action:
          type: string
    ErrorResponse:
      type: object
      properties:
        message:
          type: string

# Entity

# Role

import lombok.*;
import javax.persistence.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "description")
    private String description;
}

# Grant

import lombok.*;
import javax.persistence.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "grants")
public class Grant {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "role_id", nullable = false)
    private Role role;

    @ManyToOne
    @JoinColumn(name = "permission_id", nullable = false)
    private Permission permission;
}

# Permission

import lombok.*;
import javax.persistence.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "permission")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "action", unique = true)
    private String action;
}

# Repository

# RoleRepository

import com.example.duallo.entity.Role;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;


@Repository
public interface RoleRepository extends CrudRepository<Role, Long> {
}

# GrantRepository

import com.example.duallo.entity.Grant;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
import java.util.Optional;

public interface GrantRepository extends CrudRepository<Grant, Long> {
    List<Grant> findByRoleId(Long roleId);
    List<Grant> findByPermissionId(Long permissionId);
    Optional<Grant> findByRoleIdAndPermissionId(Long roleId, Long permissionId);
}

# PermissionRepository

import com.example.duallo.entity.Permission;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;

public interface PermissionRepository extends CrudRepository<Permission, Long> {
    Optional<Permission> findByAction(String action);
}

# Service

# RoleService

import dto.RoleDTO;
import com.example.duallo.entity.Role;
import com.example.duallo.repository.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Service
public class RoleService {

    private final RoleRepository roleRepository;

    @Autowired
    public RoleService(RoleRepository roleRepository) {
        this.roleRepository = roleRepository;
    }

    public Role createRole(RoleDTO roleDTO) {
        Role role = Role.builder()
                .name(roleDTO.getName())
                .description(roleDTO.getDescription()).build();
        return roleRepository.save(role);
    }

    public List<Role> getAllRoles() {
        return StreamSupport.stream(roleRepository.findAll().spliterator(), false)
                .collect(Collectors.toList());
    }

    public Role getRoleById(Long id) {
        return roleRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Role not found"));
    }

    @Transactional
    public Role updateRole(Long id, RoleDTO roleDetails) {
        Optional<Role> roleOptional = roleRepository.findById(id);
        if (roleOptional.isPresent()) {
            Role existingRole = roleOptional.get();
            existingRole.setName(roleDetails.getName());
            existingRole.setDescription(roleDetails.getDescription());
            return roleRepository.save(existingRole);
        } else {
            throw new IllegalArgumentException("Role not found with id: " + id);
        }
    }

    public void deleteRole(Long id) {
        if (roleRepository.existsById(id)) {
            roleRepository.deleteById(id);
        } else {
            throw new IllegalArgumentException("Role not found with id: " + id);
        }
    }
}

# GrantService

import com.example.duallo.entity.Grant;
import com.example.duallo.entity.Permission;
import com.example.duallo.entity.Role;
import com.example.duallo.repository.GrantRepository;
import com.example.duallo.repository.PermissionRepository;
import com.example.duallo.repository.RoleRepository;
import dto.GrantDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;


@Service
public class GrantService {

    private final GrantRepository grantRepository;
    private final RoleRepository roleRepository;
    private final PermissionRepository permissionRepository;

    @Autowired
    public GrantService(GrantRepository grantRepository, RoleRepository roleRepository, PermissionRepository permissionRepository) {
        this.grantRepository = grantRepository;
        this.roleRepository = roleRepository;
        this.permissionRepository = permissionRepository;
    }

    public Grant createGrant(GrantDTO grantDTO) {
        Role role = roleRepository.findById(grantDTO.getRoleId()).orElseThrow(() -> new IllegalArgumentException("Role not found"));
        Permission permission = permissionRepository.findById(grantDTO.getPermissionId()).orElseThrow(() -> new IllegalArgumentException("Permission not found"));
        Optional<Grant> existingGrant = grantRepository.findByRoleIdAndPermissionId(grantDTO.getRoleId(), grantDTO.getPermissionId());
        if (existingGrant.isPresent()) {
            throw new IllegalArgumentException("Grant already exists for the given role and permission");
        }
        Grant grant = Grant.builder()
                .role(role)
                .permission(permission)
                .build();
        return grantRepository.save(grant);
    }

    public List<Grant> getAllGrants() {
        return StreamSupport.stream(grantRepository.findAll().spliterator(), false)
                .collect(Collectors.toList());
    }

    public Grant getGrantById(Long id) {
        return grantRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Grant not found"));
    }

    @Transactional
    public Grant updateGrant(Long id, GrantDTO grantDTO) {
        Grant grant = grantRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Grant not found with id: " + id));
        Role role = roleRepository.findById(grantDTO.getRoleId()).orElseThrow(() -> new IllegalArgumentException("Role not found with id: " + grantDTO.getRoleId()));
        Permission permission = permissionRepository.findById(grantDTO.getPermissionId()).orElseThrow(() -> new IllegalArgumentException("Permission not found with id: " + grantDTO.getPermissionId()));
        grant.setRole(role);
        grant.setPermission(permission);
        return grantRepository.save(grant);
    }

    public void deleteGrant(Long id) {
        if (grantRepository.existsById(id)) {
            grantRepository.deleteById(id);
        } else {
            throw new IllegalArgumentException("Grant not found with id: " + id);
        }
    }

    public List<Grant> getGrantsByRoleId(Long roleId) {
        if (!roleRepository.existsById(roleId)) {
            throw new IllegalArgumentException("Role not found with id: " + roleId);
        }
        return grantRepository.findByRoleId(roleId);
    }

    public List<Grant> getGrantsByPermissionId(Long permissionId) {
        if (!permissionRepository.existsById(permissionId)) {
            throw new IllegalArgumentException("Permission not found with id: " + permissionId);
        }
        return grantRepository.findByPermissionId(permissionId);
    }
}

# PermissionService

import dto.PermissionDTO;
import com.example.duallo.entity.Permission;
import com.example.duallo.repository.PermissionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;


@Service
public class PermissionService {
    private final PermissionRepository permissionRepository;

    @Autowired
    public PermissionService(PermissionRepository permissionRepository) {
        this.permissionRepository = permissionRepository;
    }

    public Permission createPermission(PermissionDTO permissionDTO) {
        Optional<Permission> existingPermission = permissionRepository.findByAction(permissionDTO.getAction());
        if (existingPermission.isPresent()) {
            throw new IllegalArgumentException("Permission with action '" + permissionDTO.getAction() + "' already exists");
        }
        Permission permission = Permission.builder()
                .action(permissionDTO.getAction())
                .build();
        return permissionRepository.save(permission);
    }
    public List<Permission> getAllPermission() {
        return StreamSupport.stream(permissionRepository.findAll().spliterator(), false)
                .collect(Collectors.toList());
    }
    public Permission getPermissionById(Long id) {
        return permissionRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Permission not found"));
    }
    @Transactional
    public Permission updatePermission(Long id, PermissionDTO permissionDTO) {
        Optional<Permission> permissionOptional = permissionRepository.findById(id);
        if (permissionOptional.isPresent()) {
            Permission permission = permissionOptional.get();
            permission.setAction(permissionDTO.getAction());
            return permissionRepository.save(permission);
        } else {
            throw new IllegalArgumentException("Permission not found with id: " + id);
        }
    }
    public void deletePermission(Long id) {
        if (permissionRepository.existsById(id)) {
            permissionRepository.deleteById(id);
        } else {
            throw new IllegalArgumentException("Permission not found with id: " + id);
        }
    }
}

# Controller

# RoleController

import com.example.duallo.entity.Role;
import com.example.duallo.service.RoleService;
import dto.ErrorResponse;
import dto.RoleDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api")
public class RoleController {

    private final RoleService roleService;

    @Autowired
    public RoleController(RoleService roleService) {
        this.roleService = roleService;
    }

    @PostMapping("/roles")
    public ResponseEntity<?> createRole(@RequestBody RoleDTO roleDTO) {
        Role role = roleService.createRole(roleDTO);
        if (StringUtils.isBlank(role.getName())) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage("Role name is required and cannot be blank");
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
        return new ResponseEntity<>(role, HttpStatus.CREATED);
    }

    @GetMapping("/roles")
    public ResponseEntity<List<Role>> getAllRoles() {
        List<Role> roles = roleService.getAllRoles();
        return ResponseEntity.ok(roles);
    }

    @GetMapping("/roles/{id}")
    public ResponseEntity<?> getRoleById(@PathVariable Long id) {
        try {
            Role role = roleService.getRoleById(id);
            return ResponseEntity.ok(role);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }

    @PutMapping("/roles/{id}")
    public ResponseEntity<?> updateRole(@PathVariable Long id, @RequestBody RoleDTO roleDetails) {
        try {
            Role updatedRole = roleService.updateRole(id, roleDetails);
            return ResponseEntity.ok(updatedRole);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);        
        }
    }

    @DeleteMapping("/roles/{id}")
    public ResponseEntity<?> deleteRole(@PathVariable Long id) {
        try {
            roleService.deleteRole(id);
            return ResponseEntity.noContent().build();
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);        
        }
    }
}

# GrantController

import dto.ErrorResponse;
import dto.GrantDTO;
import com.example.duallo.entity.Grant;
import com.example.duallo.service.GrantService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/grants")
public class GrantController {
    private final GrantService grantService;

    @Autowired
    public GrantController(GrantService grantService) {
        this.grantService = grantService;
    }
    @PostMapping
    public ResponseEntity<?> createGrant(@RequestBody GrantDTO grantDTO) {
        try {
            Grant grant = grantService.createGrant(grantDTO);
            return new ResponseEntity<>(grant, HttpStatus.CREATED);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }
    @GetMapping
    public ResponseEntity<List<Grant>> getAllGrants() {
        List<Grant> grants = grantService.getAllGrants();
        return ResponseEntity.ok(grants);
    }
    @GetMapping("/{id}")
    public ResponseEntity<?> getGrantById(@PathVariable Long id) {
        try {
            Grant grant = grantService.getGrantById(id);
            return ResponseEntity.ok(grant);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }
    @PutMapping("/{id}")
    public ResponseEntity<?> updateGrant(@PathVariable Long id, @RequestBody GrantDTO grantDTO) {
        try {
            Grant updatedGrant = grantService.updateGrant(id, grantDTO);
            return ResponseEntity.ok(updatedGrant);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }
    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteGrant(@PathVariable Long id) {
        try {
            grantService.deleteGrant(id);
            return ResponseEntity.noContent().build();
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }
    @GetMapping("/by-role/{roleId}")
    public ResponseEntity<?> getGrantsByRoleId(@PathVariable Long roleId) {
        try {
            List<Grant> grants = grantService.getGrantsByRoleId(roleId);
            return ResponseEntity.ok(grants);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }
    @GetMapping("/by-permission/{permissionId}")
    public ResponseEntity<?> getGrantsByPermissionId(@PathVariable Long permissionId) {
        try {
            List<Grant> grants = grantService.getGrantsByPermissionId(permissionId);
            return ResponseEntity.ok(grants);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }
}

# PermissionController

import dto.ErrorResponse;
import dto.PermissionDTO;
import com.example.duallo.service.PermissionService;
import com.example.duallo.entity.Permission;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api")
public class PermissionController {
    private final PermissionService permissionService;

    @Autowired
    public PermissionController(PermissionService permissionService) {
        this.permissionService = permissionService;
    }
    @PostMapping("/permissions")
    public ResponseEntity<?> createPermission(@RequestBody PermissionDTO permissionDTO) {
        try {
            Permission permission = permissionService.createPermission(permissionDTO);
            if (StringUtils.isBlank(permission.getAction())) {
                ErrorResponse badRequestResponse = new ErrorResponse();
                badRequestResponse.setMessage("permission action is required and cannot be blank");
                return ResponseEntity.badRequest().body(badRequestResponse);
            }
            return new ResponseEntity<>(permission, HttpStatus.CREATED);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);
        }
    }
    @GetMapping("/permissions")
    public ResponseEntity<List<Permission>> getAllPermissions() {
        List<Permission> permissions = permissionService.getAllPermission();
        return ResponseEntity.ok(permissions);
    }
    @GetMapping("/permissions/{id}")
    public ResponseEntity<?> getPermissionById(@PathVariable Long id) {
        try {
            Permission permission = permissionService.getPermissionById(id);
            return ResponseEntity.ok(permission);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);        
        }
    }
    @PutMapping("/permissions/{id}")
    public ResponseEntity<?> updatePermission(@PathVariable Long id, @RequestBody PermissionDTO permissionDTO) {
        try {
            Permission updatedPermission = permissionService.updatePermission(id, permissionDTO);
            return ResponseEntity.ok(updatedPermission);
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);        
        }
    }
    @DeleteMapping("/permissions/{id}")
    public ResponseEntity<?> deletePermission(@PathVariable Long id) {
        try {
            permissionService.deletePermission(id);
            return ResponseEntity.noContent().build();
        } catch (Exception e) {
            ErrorResponse badRequestResponse = new ErrorResponse();
            badRequestResponse.setMessage(e.getMessage());
            return ResponseEntity.badRequest().body(badRequestResponse);        
        }
    }
}

# Main Class for Spring Boot Application Launch

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringDualloApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringDualloApplication.class, args);
    }
}
Останнє оновлення: 11/23/2023, 2:40:36 AM