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

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

-- 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 opinio
-- -----------------------------------------------------
DROP SCHEMA IF EXISTS `opinio` ;

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

-- -----------------------------------------------------
-- Table `opinio`.`Poll`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `opinio`.`Poll` ;

CREATE TABLE IF NOT EXISTS `opinio`.`Poll` (
  `id` INT NOT NULL,
  `title` MEDIUMTEXT NOT NULL,
  `description` LONGTEXT NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `opinio`.`Question`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `opinio`.`Question` ;

CREATE TABLE IF NOT EXISTS `opinio`.`Question` (
  `id` INT NOT NULL,
  `type` MEDIUMTEXT NOT NULL,
  `text` LONGTEXT NOT NULL,
  `Poll_id` INT NOT NULL,
  PRIMARY KEY (`id`, `Poll_id`),
  INDEX `fk_Question_Poll1_idx` (`Poll_id` ASC) VISIBLE,
  CONSTRAINT `fk_Question_Poll1`
    FOREIGN KEY (`Poll_id`)
    REFERENCES `opinio`.`Poll` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `opinio`.`Answer`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `opinio`.`Answer` ;

CREATE TABLE IF NOT EXISTS `opinio`.`Answer` (
  `id` INT NOT NULL,
  `field` BLOB NOT NULL,
  `Question_id` INT NOT NULL,
  PRIMARY KEY (`id`, `Question_id`),
  INDEX `fk_Answer_Question_idx` (`Question_id` ASC) VISIBLE,
  CONSTRAINT `fk_Answer_Question`
    FOREIGN KEY (`Question_id`)
    REFERENCES `opinio`.`Question` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


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

CREATE TABLE IF NOT EXISTS `opinio`.`Role` (
  `id` INT NOT NULL,
  `name` TINYTEXT NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


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

CREATE TABLE IF NOT EXISTS `opinio`.`User` (
  `id` INT NOT NULL,
  `mail` MEDIUMTEXT NOT NULL,
  `password` MEDIUMTEXT NOT NULL,
  `name` MEDIUMTEXT NOT NULL,
  `age` INT NULL,
  `gender` MEDIUMTEXT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


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

CREATE TABLE IF NOT EXISTS `opinio`.`Grant` (
  `id` INT ZEROFILL NOT NULL,
  `assignedAt` DATE NOT NULL,
  `Role_id` INT NOT NULL,
  `User_id` INT NOT NULL,
  `Answer_id` INT NULL,
  `Answer_Question_id` INT NULL,
  `Poll_id` INT NULL,
  PRIMARY KEY (`id`, `Role_id`, `User_id`),
  INDEX `fk_Grant_Role1_idx` (`Role_id` ASC) VISIBLE,
  INDEX `fk_Grant_User1_idx` (`User_id` ASC) VISIBLE,
  INDEX `fk_Grant_Answer1_idx` (`Answer_id` ASC, `Answer_Question_id` ASC) VISIBLE,
  INDEX `fk_Grant_Poll1_idx` (`Poll_id` ASC) VISIBLE,
  CONSTRAINT `fk_Grant_Role1`
    FOREIGN KEY (`Role_id`)
    REFERENCES `opinio`.`Role` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Grant_User1`
    FOREIGN KEY (`User_id`)
    REFERENCES `opinio`.`User` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Grant_Answer1`
    FOREIGN KEY (`Answer_id` , `Answer_Question_id`)
    REFERENCES `opinio`.`Answer` (`id` , `Question_id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Grant_Poll1`
    FOREIGN KEY (`Poll_id`)
    REFERENCES `opinio`.`Poll` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `opinio`.`State`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `opinio`.`State` ;

CREATE TABLE IF NOT EXISTS `opinio`.`State` (
  `id` INT NOT NULL,
  `text` LONGTEXT NOT NULL,
  `type` MEDIUMTEXT NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


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

CREATE TABLE IF NOT EXISTS `opinio`.`Action` (
  `id` INT NOT NULL,
  `date` DATE NOT NULL,
  `Poll_id` INT NOT NULL,
  `Grant_id` INT ZEROFILL NOT NULL,
  `State_id` INT NOT NULL,
  PRIMARY KEY (`id`, `Poll_id`, `Grant_id`, `State_id`),
  INDEX `fk_Action_Poll1_idx` (`Poll_id` ASC) VISIBLE,
  INDEX `fk_Action_Grant1_idx` (`Grant_id` ASC) VISIBLE,
  INDEX `fk_Action_State1_idx` (`State_id` ASC) VISIBLE,
  CONSTRAINT `fk_Action_Poll1`
    FOREIGN KEY (`Poll_id`)
    REFERENCES `opinio`.`Poll` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Action_Grant1`
    FOREIGN KEY (`Grant_id`)
    REFERENCES `opinio`.`Grant` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Action_State1`
    FOREIGN KEY (`State_id`)
    REFERENCES `opinio`.`State` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `opinio`.`Specialty`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `opinio`.`Specialty` ;

CREATE TABLE IF NOT EXISTS `opinio`.`Specialty` (
  `id` INT NOT NULL,
  `name` MEDIUMTEXT NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `opinio`.`Qualification`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `opinio`.`Qualification` ;

CREATE TABLE IF NOT EXISTS `opinio`.`Qualification` (
  `id` INT NOT NULL,
  `level` INT NOT NULL,
  `User_id` INT NOT NULL,
  `Specialty_id` INT NOT NULL,
  PRIMARY KEY (`id`, `User_id`, `Specialty_id`),
  INDEX `fk_Qualification_User1_idx` (`User_id` ASC) VISIBLE,
  INDEX `fk_Qualification_Specialty1_idx` (`Specialty_id` ASC) VISIBLE,
  CONSTRAINT `fk_Qualification_User1`
    FOREIGN KEY (`User_id`)
    REFERENCES `opinio`.`User` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Qualification_Specialty1`
    FOREIGN KEY (`Specialty_id`)
    REFERENCES `opinio`.`Specialty` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `opinio`.`EarnedMoney`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `opinio`.`EarnedMoney` ;

CREATE TABLE IF NOT EXISTS `opinio`.`EarnedMoney` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `date` DATE NOT NULL,
  `amount` INT NOT NULL,
  `tookAway` TINYINT NOT NULL,
  `User_id` INT NOT NULL,
  `Poll_id` INT NOT NULL,
  PRIMARY KEY (`id`, `User_id`, `Poll_id`),
  INDEX `fk_EarnedMoney_User1_idx` (`User_id` ASC) VISIBLE,
  INDEX `fk_EarnedMoney_Poll1_idx` (`Poll_id` ASC) VISIBLE,
  CONSTRAINT `fk_EarnedMoney_User1`
    FOREIGN KEY (`User_id`)
    REFERENCES `opinio`.`User` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_EarnedMoney_Poll1`
    FOREIGN KEY (`Poll_id`)
    REFERENCES `opinio`.`Poll` (`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;

# drizzle schema

import {
  mysqlTable,
  mysqlSchema,
  AnyMySqlColumn,
  index,
  foreignKey,
  primaryKey,
  int,
  date,
  tinyint,
  mediumtext,
  longtext,
  tinytext,
} from "drizzle-orm/mysql-core";
import { sql } from "drizzle-orm";

export const action = mysqlTable(
  "Action",
  {
    id: int("id").autoincrement().notNull(),
    // you can use { mode: 'date' }, if you want to have Date as type for this column
    date: date("date", { mode: "string" }).notNull(),
    pollId: int("Poll_id")
      .notNull()
      .references(() => poll.id),
    grantId: int("Grant_id")
      .notNull()
      .references(() => grant.id),
    stateId: int("State_id")
      .notNull()
      .references(() => state.id),
  },
  (table) => {
    return {
      fkActionGrant1Idx: index("fk_Action_Grant1_idx").on(table.grantId),
      fkActionPoll1Idx: index("fk_Action_Poll1_idx").on(table.pollId),
      fkActionState1Idx: index("fk_Action_State1_idx").on(table.stateId),
      actionIdPollIdGrantIdStateIdPk: primaryKey({
        columns: [table.id, table.pollId, table.grantId, table.stateId],
        name: "Action_id_Poll_id_Grant_id_State_id_pk",
      }),
    };
  },
);

export const answer = mysqlTable(
  "Answer",
  {
    id: int("id").autoincrement().notNull(),
    // Warning: Can't parse blob from database
    // blobType: blob("field").notNull(),
    questionId: int("Question_id")
      .notNull()
      .references(() => question.id),
  },
  (table) => {
    return {
      fkAnswerQuestionIdx: index("fk_Answer_Question_idx").on(table.questionId),
      answerIdQuestionIdPk: primaryKey({
        columns: [table.id, table.questionId],
        name: "Answer_id_Question_id_pk",
      }),
    };
  },
);

export const earnedMoney = mysqlTable(
  "EarnedMoney",
  {
    id: int("id").autoincrement().notNull(),
    // you can use { mode: 'date' }, if you want to have Date as type for this column
    date: date("date", { mode: "string" }).notNull(),
    amount: int("amount").notNull(),
    tookAway: tinyint("tookAway").notNull(),
    userId: int("User_id")
      .notNull()
      .references(() => user.id),
    pollId: int("Poll_id")
      .notNull()
      .references(() => poll.id),
  },
  (table) => {
    return {
      fkEarnedMoneyPoll1Idx: index("fk_EarnedMoney_Poll1_idx").on(table.pollId),
      fkEarnedMoneyUser1Idx: index("fk_EarnedMoney_User1_idx").on(table.userId),
      earnedMoneyIdUserIdPollIdPk: primaryKey({
        columns: [table.id, table.userId, table.pollId],
        name: "EarnedMoney_id_User_id_Poll_id_pk",
      }),
    };
  },
);

export const grant = mysqlTable(
  "Grant",
  {
    id: int("id").autoincrement().notNull(),
    // you can use { mode: 'date' }, if you want to have Date as type for this column
    assignedAt: date("assignedAt", { mode: "string" }).notNull(),
    roleId: int("Role_id")
      .notNull()
      .references(() => role.id),
    userId: int("User_id")
      .notNull()
      .references(() => user.id),
    answerId: int("Answer_id"),
    answerQuestionId: int("Answer_Question_id"),
    pollId: int("Poll_id").references(() => poll.id),
  },
  (table) => {
    return {
      fkGrantAnswer1Idx: index("fk_Grant_Answer1_idx").on(
        table.answerId,
        table.answerQuestionId,
      ),
      fkGrantPoll1Idx: index("fk_Grant_Poll1_idx").on(table.pollId),
      fkGrantRole1Idx: index("fk_Grant_Role1_idx").on(table.roleId),
      fkGrantUser1Idx: index("fk_Grant_User1_idx").on(table.userId),
      fkGrantAnswer1: foreignKey({
        columns: [table.answerId, table.answerQuestionId],
        foreignColumns: [answer.id, answer.questionId],
        name: "fk_Grant_Answer1",
      }),
      grantIdRoleIdUserIdPk: primaryKey({
        columns: [table.id, table.roleId, table.userId],
        name: "Grant_id_Role_id_User_id_pk",
      }),
    };
  },
);

export const poll = mysqlTable(
  "Poll",
  {
    id: int("id").notNull(),
    title: mediumtext("title").notNull(),
    description: longtext("description").notNull(),
  },
  (table) => {
    return {
      pollIdPk: primaryKey({ columns: [table.id], name: "Poll_id_pk" }),
    };
  },
);

export const qualification = mysqlTable(
  "Qualification",
  {
    id: int("id").autoincrement().notNull(),
    level: int("level").notNull(),
    userId: int("User_id")
      .notNull()
      .references(() => user.id),
    specialtyId: int("Specialty_id")
      .notNull()
      .references(() => specialty.id),
  },
  (table) => {
    return {
      fkQualificationSpecialty1Idx: index("fk_Qualification_Specialty1_idx").on(
        table.specialtyId,
      ),
      fkQualificationUser1Idx: index("fk_Qualification_User1_idx").on(
        table.userId,
      ),
      qualificationIdUserIdSpecialtyIdPk: primaryKey({
        columns: [table.id, table.userId, table.specialtyId],
        name: "Qualification_id_User_id_Specialty_id_pk",
      }),
    };
  },
);

export const question = mysqlTable(
  "Question",
  {
    id: int("id").autoincrement().notNull(),
    type: mediumtext("type").notNull(),
    text: longtext("text").notNull(),
    pollId: int("Poll_id")
      .notNull()
      .references(() => poll.id),
  },
  (table) => {
    return {
      fkQuestionPoll1Idx: index("fk_Question_Poll1_idx").on(table.pollId),
      questionIdPollIdPk: primaryKey({
        columns: [table.id, table.pollId],
        name: "Question_id_Poll_id_pk",
      }),
    };
  },
);

export const role = mysqlTable(
  "Role",
  {
    id: int("id").autoincrement().notNull(),
    name: tinytext("name").notNull(),
  },
  (table) => {
    return {
      roleIdPk: primaryKey({ columns: [table.id], name: "Role_id_pk" }),
    };
  },
);

export const specialty = mysqlTable(
  "Specialty",
  {
    id: int("id").autoincrement().notNull(),
    name: mediumtext("name").notNull(),
  },
  (table) => {
    return {
      specialtyIdPk: primaryKey({
        columns: [table.id],
        name: "Specialty_id_pk",
      }),
    };
  },
);

export const state = mysqlTable(
  "State",
  {
    id: int("id").autoincrement().notNull(),
    text: longtext("text").notNull(),
    type: mediumtext("type").notNull(),
  },
  (table) => {
    return {
      stateIdPk: primaryKey({ columns: [table.id], name: "State_id_pk" }),
    };
  },
);

export const user = mysqlTable(
  "User",
  {
    id: int("id").autoincrement().notNull(),
    mail: mediumtext("mail").notNull(),
    password: mediumtext("password").notNull(),
    name: mediumtext("name").notNull(),
    age: int("age"),
    gender: mediumtext("gender"),
  },
  (table) => {
    return {
      userIdPk: primaryKey({ columns: [table.id], name: "User_id_pk" }),
    };
  },
);

# express-сервер

import express from "express";
import "dotenv/config";
import * as console from "console";
import router from "./routes";
const app = express();
const PORT = 3000;

app.use("/static", express.static("static"));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use("/", router);

app.listen(3000, () => {
  console.log(`Server is up on http://localhost:${PORT}`);
});

# User Router

import { Router } from "express";
import userController from "../controllers/user.controller";
import handleZodResponse from "../middleware/handleZodResponse";

const router = Router();
router.post("/create", handleZodResponse(userController.create));
router.patch("/:userId", handleZodResponse(userController.update));

router.get("/", userController.getAll);
router.get("/:userId", handleZodResponse(userController.get));

router.delete("/:userId", handleZodResponse(userController.delete));

export default router;

# User Controller

import { Request, Response } from "express";
import UserService from "../services/user.service";
import { zodParseRequest } from "../middleware/handleZodResponse";

import {
    updateUserSchema,
    createUserSchema,
    withUserIdSchema,
} from "../zodSchemas/user.schema";
import * as console from "console";

class UserController {
    async create(req: Request, res: Response) {
        await zodParseRequest(createUserSchema, req);
        const userId = await UserService.create(req.body);
        return { userId };
    }
    async update(req: Request, res: Response) {
        await zodParseRequest(updateUserSchema, req);
        return await UserService.update(req.body, req.params.userId);
    }

    async delete(req: Request, res: Response) {
        await zodParseRequest(withUserIdSchema, req);
        return await UserService.delete(req.params.userId);
    }
    async get(req: Request, res: Response) {
        await zodParseRequest(withUserIdSchema, req);
        return await UserService.get(req.params.userId);
    }

    async getAll(req: Request, res: Response) {
        const users = await UserService.getAll();
        res.status(200).json(users);
    }
}

export default new UserController();

# User service

import db from "../db";
import { z } from "zod";
import { updateUserSchema, createUserSchema } from "../zodSchemas/user.schema";
import { user } from "../db/schema";
import { eq, getTableColumns, sql } from "drizzle-orm";
import * as console from "console";

const { password, ...nonPasswordColumns } = getTableColumns(user);

class UserService {
  async create(body: z.infer<typeof createUserSchema>["body"]) {
    const { roleId } = body;
    const [data] = await db.insert(user).values(body);
    return data.insertId;
  }
  async update(
    body: z.infer<(typeof updateUserSchema)["body"]>,
    userId: string,
  ) {
    await db
      .update(user)
      .set({
        age: body.age,
        gender: body.gender,
        name: body.name,
        mail: body.mail,
      })
      .where(eq(user.id, userId));

    return await this.get(userId);
  }

  async delete(userId: string) {
    await db.delete(user).where(eq(user.id, userId));

    return { message: "Deleted successfully" };
  }

  async get(userId: string) {
    return db.select(nonPasswordColumns).from(user).where({ id: userId });
  }

  async getAll() {
    return db.select(nonPasswordColumns).from(user);
  }
}

export default new UserService();

# zod schemas (для валідації запитів)

import { z } from "zod";

export const createUserSchema = z.object({
  body: z.object({
    password: z.string(),
    name: z.string(),
    mail: z.string().email(),
    age: z.number().optional(),
    gender: z.string().optional(),
    roleId: z.number(),
  }),
});

export const updateUserSchema = z.object({
  body: z.object({
    name: z.string().optional(),
    mail: z.string().email().optional(),
    age: z.number().optional(),
    gender: z.enum(["male", "female"]).optional(),
    role: z.enum(["client", "expert"]).optional(),
  }),
  params: z.object({
    userId: z.string(),
  }),
});

export const withUserIdSchema = z.object({
  params: z.object({
    userId: z.string(),
  }),
});

# Middleware для обробки помилок

import { Request, Response } from "express";
import { BaseError } from "../errors";
import { ZodError, z } from "zod";

const handleZodResponse =
  (fn: (req: Request, res: Response) => Return) =>
  async (req: Request, res: Response) => {
    try {
      const result = await fn(req, res);

      if (!result) {
        return res.status(404).json({ message: "Not found" });
      }

      return res.json(result);
    } catch (err) {
      if (err instanceof ZodError) {
        res.status(400).json({
          status: 400,
          message: "Request input validation error",
          issues: err.issues,
        });
        return;
      }

      const error = err as Error | BaseError;
      console.error(error);
      return res.status(400).json({ message: error.message });
    }
  };

export async function zodParseRequest<T extends AnyZodObject>(
  schema: T,
  req: Request,
): Promise<z.infer<T>> {
  return await schema.parseAsync(req);
}

export default handleZodResponse;
Останнє оновлення: 11/19/2023, 2:37:24 PM