commit 6c4ed3dcbe2880356b721ed0b84d0707a449dab3 Author: FutureX Date: Fri Jun 3 00:29:39 2022 +0300 init diff --git a/express/app.js b/express/app.js new file mode 100644 index 0000000..836e977 --- /dev/null +++ b/express/app.js @@ -0,0 +1,26 @@ +const express = require("express"); +const bodyParser = require("body-parser"); + +const routes = { + lessons: require("./routes/lessons"), +}; + +const app = express(); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); + +function makeHandlerAwareOfAsyncErrors(handler) { + return async function (req, res, next) { + try { + await handler(req, res); + } catch (error) { + next(error); + } + }; +} + +app.get("/", makeHandlerAwareOfAsyncErrors(routes.lessons.getAll)); +app.post("/lessons", makeHandlerAwareOfAsyncErrors(routes.lessons.create)); + +module.exports = app; diff --git a/express/routes/lessons.js b/express/routes/lessons.js new file mode 100644 index 0000000..64aa68b --- /dev/null +++ b/express/routes/lessons.js @@ -0,0 +1,288 @@ +const { models } = require("../../sequelize"); +const { Sequelize, Op } = require("sequelize"); +const moment = require("moment"); + +function checkParams(query) { + //проверка параметров на формат и их данные + let errors = {}; + let queryInfo = {}; //Все значения из запроса превратим в нужный формат для использования + + if (query.date) { + //проверка дат + queryInfo.dates = query.date.split(",").sort(); + if (queryInfo.dates.length > 2) + errors.date = "You need to use 2 or less dates in query"; + else if ( + !( + moment(queryInfo.dates[0], "YYYY-MM-DD", true).isValid() && + moment( + queryInfo.dates[1] || queryInfo.dates[0], + "YYYY-MM-DD", + true + ).isValid() + ) + ) { + errors.date = "Wrong date format"; + } + } + + if (query.status) { + //проверка статуса + if (!(query.status === "0" || query.status === "1")) + errors.status = "Status can only be 0 or 1"; + else queryInfo.status = parseInt(query.status); + } + + if (query.teacherIds) { + //проверка id учителей + let teacherIds = query.teacherIds.split(","); + queryInfo.teacherIds = []; + for (teacherId of teacherIds) { + let id = +teacherId; + if ( + isNaN(id) || + id % 1 > 0 || + id < 1 || + queryInfo.teacherIds.includes(id) + ) + errors.teacherIds = "teacherIds can only be unique integer more than 0"; + else queryInfo.teacherIds.push(id); + } + } + + if (query.studentsCount) { + //проверка количества учеников + let studentsCounts = query.studentsCount.split(",").sort(); + queryInfo.studentsCount = []; + if (studentsCounts.length > 2) + errors.studentsCount = + "You need to use 2 or less studentsCounts in query"; + else + for (studentsCount of studentsCounts) { + let count = +studentsCount; + if (isNaN(count) || count % 1 > 0 || count < 0) + errors.studentsCount = + "studentsCounts can only be integer greater than or equal to 0"; + else queryInfo.studentsCount.push(count); + } + } + + if (query.page) { + //проверка номера страницы + let page = +query.page; + if (isNaN(page) || page % 1 > 0 || page < 1) + errors.page = "Page can only be integer more than 0"; + else queryInfo.page = page; + } + + if (query.lessonsPerPage) { + //проверка количества учеников + let lessonsPerPage = +query.lessonsPerPage; + if (isNaN(lessonsPerPage) || lessonsPerPage % 1 > 0 || lessonsPerPage < 1) + errors.lessonsPerPage = "lessonsPerPage can only be integer more than 0"; + else queryInfo.lessonsPerPage = lessonsPerPage; + } + + return { queryInfo, errors }; +} + +function getWhereParametres(queryInfo) { + //Преобразуем параметры в условия запроса + let where = {}; + let and = []; + + if (queryInfo.dates) { + //Добавляем условие на проверку даты занятия + if (queryInfo.dates.length == 2) + where.date = { [Op.between]: [queryInfo.dates[0], queryInfo.dates[1]] }; + else where.date = { [Op.eq]: queryInfo.dates[0] }; + } + + if (queryInfo.status !== undefined) + //Добавляем условие на проверку статуса занятия + where.status = { [Op.eq]: queryInfo.status }; + + if (queryInfo.studentsCount) { + //Добавляем условие на проверку количества учеников записанных на занятие + if (queryInfo.studentsCount.length == 2) + and.push({ + id: Sequelize.literal(` +(SELECT CAST(COUNT(lesson_students.student_id)AS INT) AS studentsCount +FROM public.lessons AS lessons_student +LEFT OUTER JOIN public.lesson_students AS lesson_students ON lessons_student.id = lesson_students.lesson_id +WHERE lessons_student.id = lessons.id +GROUP BY lessons_student.id) BETWEEN ${queryInfo.studentsCount[0]} AND ${queryInfo.studentsCount[1]}`), + }); + else + and.push({ + id: Sequelize.literal(` +(SELECT CAST(COUNT(lesson_students.student_id)AS INT) AS studentsCount +FROM public.lessons AS lessons_student +LEFT OUTER JOIN public.lesson_students AS lesson_students ON lessons_student.id = lesson_students.lesson_id +WHERE lessons_student.id = lessons.id +GROUP BY lessons_student.id) = ${queryInfo.studentsCount[0]}`), + }); + } + + if (queryInfo.teacherIds) + //Добавляем условие на проверку наличия учителей на занятии + where.id = { + [Op.any]: Sequelize.literal(` +(SELECT lessons_in.id +FROM public.lessons AS lessons_in +LEFT OUTER JOIN public.lesson_teachers AS lesson_teachers ON lessons_in.id = lesson_teachers.lesson_id +WHERE lesson_teachers.teacher_id = ANY (SELECT unnest(ARRAY[${queryInfo.teacherIds}])) +GROUP BY lessons_in.id) +`), + }; + + return { ...where, [Op.and]: and }; +} + +async function getAll(req, res) { + let query = checkParams(req.query); + if (Object.keys(query.errors).length > 0) { + return res.status(400).json({ isSuccess: false, errors: query.errors }); + } + + const lessons = await models.lessons.findAll({ + offset: (query.queryInfo.lessonsPerPage || 5) * ((query.queryInfo.page || 1) - 1) || 0, + limit: query.queryInfo.lessonsPerPage || 5, + attributes: { + include: [ + //добавляем счетчик учеников посетивших занятие + [ + Sequelize.literal(`(SELECT COALESCE((SELECT CAST(COUNT(lesson_students.visit) AS INT) FROM lesson_students WHERE lessons.id = lesson_students.lesson_id AND lesson_students.visit GROUP BY lessons.id),0))`), + "visitCount", + ], + ], + }, + include: [ + { + model: models.students, + as: "students", + attributes: { + //Переносим аттрибут посещенного занятия из таблицы lesson_students + include: [ + [ + Sequelize.literal( + `(SELECT lesson_students.visit FROM lesson_students WHERE lesson_students.student_id = students.id and lesson_students.lesson_id = lessons.id)` + ), + "visit", + ], + ], + }, + through: { + model: models.lesson_students, + attributes: [], + }, + }, + { + model: models.teachers, + as: "teachers", + through: { + model: models.lesson_teachers, + attributes: [], + }, + }, + ], + where: getWhereParametres(query.queryInfo), + order: [["date", "ASC"]], + }); + + res.status(200).json({ isSuccess: true, lessons }); +} + +async function checkBody(body) { //проверка параметров на формат и их данные + let errors = {}; + + if (Array.isArray(body.teacherIds)) { //проверка id учителей на нужный формат и значения + for (let id of body.teacherIds) + if (!Number.isInteger(id) || id < 1) + errors.teacherIds = "teacherId must be integer more than 0"; + if (errors.teacherIds == undefined) + await models.teachers + .findAll({ + where: { + id: { + [Op.in]: body.teacherIds, + }, + }, + }) + .then(function (teachers) { + if (teachers.length != body.teacherIds.length) + errors.teacherIds = "You must use unique teacherIds"; + }) + .catch(function (err) { + errors.teacherIds = "Some teacherIds cant be found"; + }); + } else errors.teacherIds = "Wrong teacherIds format"; + + if (!(typeof body.title === "string" || body.title instanceof String)) //проверка названия на нужный формат и значения + errors.title = "Wrong title format"; + + if (Array.isArray(body.days)) { //проверка дней на нужный формат и значения + let days = []; + for (let day of body.days) + if (!Number.isInteger(day) || day < 0 || day > 6 || days.includes(day)) + errors.days = + "day must be unique integer more than or equal 0 and less than or equal 6"; + else days.push(day); + } else errors.days = "Wrong days format"; + + if (!moment(body.firstDate, "YYYY-MM-DD", true).isValid()) //проверка первой даты на нужный формат и значения + errors.firstDate = "Wrong firstDate format"; + + if (body.lessonsCount != undefined && body.lastDate != undefined) //проверка на отсутствие обоих параметров + errors.parametres = "You cant use lessonsCount and lastDate at the same time"; + if (body.lessonsCount == undefined && body.lastDate == undefined) //проверка на присутствие обоих параметров + errors.parametres = "You have missed lessonsCount or lastDate"; + + if (body.lastDate && !moment(body.lastDate, "YYYY-MM-DD", true).isValid()) //проверка последней даты на нужный формат и значения + errors.lastDate = "Wrong lastDate format"; + + if (body.lessonsCount && (!Number.isInteger(body.lessonsCount) || body.lessonsCount < 1 || body.lessonsCount > 300)) + errors.lessonsCount = "lessonsCount must be integer more than 0 and less than or equal 300"; //проверка количества занятий на нужный формат и значения + + return errors; +} + +async function create(req, res) { + let errors = await checkBody(req.body); + if (Object.keys(errors).length > 0) { + res.status(400).json({ isSuccess: false, errors }); + } else { + let date = moment(req.body.firstDate); + let col = 0; + + let data = []; //создаем массив занятий который необходимо записать в базу данных + while (col < (req.body.lessonsCount ? req.body.lessonsCount : 300) && date <= (req.body.lastDate? moment(req.body.lastDate) : moment(req.body.firstDate).add(1, "year"))) { + if (req.body.days.includes(date.day())) { + data.push({ + title: req.body.title, + date: date.format("YYYY-MM-DD"), + }); + col++; + } + date.add(1, "day"); + } + let lessons = await models.lessons.bulkCreate(data); + + let assotiations = []; + for await (let lesson of lessons) + for (let teacherId of req.body.teacherIds) { + assotiations.push({ + lesson_id: lesson.dataValues.id, + teacher_id: teacherId, + }); + } + await models.lesson_teachers.bulkCreate(assotiations); + + res.status(201).json({isSuccess: true, lessonIds: lessons.map((lesson) => lesson.dataValues.id)}); + } +} + +module.exports = { + getAll, + create, +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..4d1ff52 --- /dev/null +++ b/index.js @@ -0,0 +1,27 @@ +const app = require('./express/app'); +const sequelize = require('./sequelize'); +const PORT = 8080; + +async function assertDatabaseConnectionOk() { + console.log(`Checking database connection...`); + try { + await sequelize.authenticate(); + console.log(`Database connection OK!`); + } catch (error) { + console.log(`Unable to connect to the database:`); + console.log(error.message); + process.exit(1); + } +} + +async function init() { + await assertDatabaseConnectionOk(); + + console.log(`Starting server... Port: ${PORT}`); + + app.listen(PORT, () => { + console.log(`Server started successful`); + }); +} + +init(); \ No newline at end of file diff --git a/license b/license new file mode 100644 index 0000000..9efc456 --- /dev/null +++ b/license @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Pedro Augusto de Paula Barbosa + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/package.json b/package.json new file mode 100644 index 0000000..7152bb3 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "moy-klass-test-server", + "version": "1.0.0", + "description": "Moy klass test server", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "engines": { + "node": ">=10" + }, + "dependencies": { + "body-parser": "^1.19.0", + "dotenv": "^16.0.1", + "express": "^4.17.1", + "moment": "^2.29.3", + "pg": "^8.7.3", + "sequelize": "^6.20.1" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..dcad5ad --- /dev/null +++ b/readme.md @@ -0,0 +1,37 @@ +## Тестовое задание на разработку отчета по занятиям для moyklass.com + +Полные условия задания можно посмотреть в файле [Тестовое задание бекенд.pdf](https://github.com/FutureXpo/moyklass.com/blob/main/%D0%A2%D0%B5%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B5%20%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%B5%D0%BA%D0%B5%D0%BD%D0%B4.pdf) + +## Установка и запуск + +* Установить модули при помощи `npm install` или `yarn install` +* Запустить с помощью `npm start` +* Запросы отправолять на `localhost:8080` + * `localhost:8080/` (GET) + * `localhost:8080/lessons` (POST) + +## Список запросов + +#### / (GET) Все параметры не обязательные + +* date. Либо одна дата в формате YYYY-MM-DD, либо две в таком же формате через запятую (например, «2019-01-01,2019-09-01». Если указана одна дата, выбираются занятия на эту дату. Если указаны 2 даты, то выбираются занятия за период, включая указанные даты. +* status. Статус занятия. принимается либо 0 (не проведено), либо 1 (проведено) +* teacherIds. id учителей через запятую. Выбираются все занятия, которые ведет хотя бы один из указанных учителей. +* studentsCount. количество записанных на занятия учеников. либо одно число (тогда выбирается занятие с точным числом записанных), либо 2 числа через запятую, тогда они рассматриваются как диапазон и выбираются занятия с количеством записанных, попадающих в диапазон включительно. +* page. Номер возвращаемой страницы. первая страница - 1. +* lessonsPerPage. Количество занятий на странице. По-умолчанию - 5 занятий. + +> [Пример запроса](http://localhost:8080?date=2019-01-01,2019-09-01&status=1&teacherIds=1,2&studentsCount=1,4&page=1&lessonsPerPage=10) + +#### /lessons (POST) + +```js +{ + teacherIds: [1,2], // id учителей, ведущих занятия + title: ‘Blue Ocean’, // Тема занятия. Одинаковая на все создаваемые занятия + days: [0,1,3,6], // Дни недели, по которым нужно создать занятия, где 0 - это воскресенье + firstDate: ‘2019-09-10’, // Первая дата, от которой нужно создавать занятия + lessonsCount: 9, // Количество занятий для создания + lastDate: ‘2019-12-31’, // Последняя дата, до которой нужно создавать занятия. +} +``` diff --git a/sequelize/extra-setup.js b/sequelize/extra-setup.js new file mode 100644 index 0000000..90815cb --- /dev/null +++ b/sequelize/extra-setup.js @@ -0,0 +1,30 @@ +function applyExtraSetup(sequelize) { + const { lessons, students, teachers, lesson_students, lesson_teachers } = sequelize.models; + + lessons.belongsToMany(students, { + as: "students", + through: lesson_students, + foreignKey: "lesson_id", + otherKey: "student_id", + }); + lessons.belongsToMany(teachers, { + as: "teachers", + through: lesson_teachers, + foreignKey: "lesson_id", + otherKey: "teacher_id", + }); + students.belongsToMany(lessons, { + as: "lessons", + through: lesson_students, + foreignKey: "student_id", + otherKey: "lesson_id", + }); + teachers.belongsToMany(lessons, { + as: "lessons", + through: lesson_teachers, + foreignKey: "teacher_id", + otherKey: "lesson_id", + }); +} + +module.exports = { applyExtraSetup }; diff --git a/sequelize/index.js b/sequelize/index.js new file mode 100644 index 0000000..629897f --- /dev/null +++ b/sequelize/index.js @@ -0,0 +1,32 @@ +const { Sequelize } = require("sequelize"); +const { applyExtraSetup } = require("./extra-setup"); +require("dotenv").config(); + +const sequelize = new Sequelize(process.env.DATABASE_URL, { + logging: false, + dialectOptions: { + ssl: { + require: true, + rejectUnauthorized: false, + }, + define: { + timestamps: false, + }, + }, +}); + +const modelDefiners = [ + require("./models/lessons.model"), + require("./models/students.model"), + require("./models/teachers.model"), + require("./models/lesson_students.model"), + require("./models/lesson_teachers.model"), +]; + +for (const modelDefiner of modelDefiners) { + modelDefiner(sequelize); +} + +applyExtraSetup(sequelize); + +module.exports = sequelize; diff --git a/sequelize/models/lesson_students.model.js b/sequelize/models/lesson_students.model.js new file mode 100644 index 0000000..a8a8460 --- /dev/null +++ b/sequelize/models/lesson_students.model.js @@ -0,0 +1,21 @@ +const { DataTypes } = require("sequelize"); + +module.exports = (sequelize) => { + let lesson_students = sequelize.define( + "lesson_students", + { + visit: { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: false, + }, + }, + { + sequelize, + tableName: "lesson_students", + schema: "public", + timestamps: false, + } + ); + lesson_students.removeAttribute("id"); +}; diff --git a/sequelize/models/lesson_teachers.model.js b/sequelize/models/lesson_teachers.model.js new file mode 100644 index 0000000..a0a4460 --- /dev/null +++ b/sequelize/models/lesson_teachers.model.js @@ -0,0 +1,15 @@ +const { DataTypes } = require("sequelize"); + +module.exports = (sequelize) => { + let lesson_teachers = sequelize.define( + "lesson_teachers", + {}, + { + sequelize, + tableName: "lesson_teachers", + schema: "public", + timestamps: false, + } + ); + lesson_teachers.removeAttribute("id"); +}; diff --git a/sequelize/models/lessons.model.js b/sequelize/models/lessons.model.js new file mode 100644 index 0000000..2826e6f --- /dev/null +++ b/sequelize/models/lessons.model.js @@ -0,0 +1,41 @@ +const { DataTypes } = require("sequelize"); + +module.exports = (sequelize) => { + sequelize.define( + "lessons", + { + id: { + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + }, + date: { + type: DataTypes.DATEONLY, + allowNull: false, + }, + title: { + type: DataTypes.STRING(100), + allowNull: true, + }, + status: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0, + }, + }, + { + sequelize, + tableName: "lessons", + schema: "public", + timestamps: false, + indexes: [ + { + name: "lessons_pkey", + unique: true, + fields: [{ name: "id" }], + }, + ], + } + ); +}; diff --git a/sequelize/models/students.model.js b/sequelize/models/students.model.js new file mode 100644 index 0000000..dd0e670 --- /dev/null +++ b/sequelize/models/students.model.js @@ -0,0 +1,32 @@ +const { DataTypes } = require("sequelize"); + +module.exports = (sequelize) => { + sequelize.define( + "students", + { + id: { + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + }, + name: { + type: DataTypes.STRING(10), + allowNull: true, + }, + }, + { + sequelize, + tableName: "students", + schema: "public", + timestamps: false, + indexes: [ + { + name: "students_pkey", + unique: true, + fields: [{ name: "id" }], + }, + ], + } + ); +}; diff --git a/sequelize/models/teachers.model.js b/sequelize/models/teachers.model.js new file mode 100644 index 0000000..33b1ccf --- /dev/null +++ b/sequelize/models/teachers.model.js @@ -0,0 +1,32 @@ +const { DataTypes } = require("sequelize"); + +module.exports = (sequelize) => { + sequelize.define( + "teachers", + { + id: { + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + }, + name: { + type: DataTypes.STRING(10), + allowNull: true, + }, + }, + { + sequelize, + tableName: "teachers", + schema: "public", + timestamps: false, + indexes: [ + { + name: "teachers_pkey", + unique: true, + fields: [{ name: "id" }], + }, + ], + } + ); +}; diff --git a/test.sql b/test.sql new file mode 100644 index 0000000..5e21fba --- /dev/null +++ b/test.sql @@ -0,0 +1,345 @@ +-- +-- PostgreSQL database dump +-- + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SET check_function_bodies = false; +SET client_min_messages = warning; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: - +-- + +--COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: lesson_students; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE lesson_students ( + lesson_id integer, + student_id integer, + visit boolean DEFAULT false +); + + +-- +-- Name: lesson_teachers; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE lesson_teachers ( + lesson_id integer, + teacher_id integer +); + + +-- +-- Name: lessons; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE lessons ( + id integer NOT NULL, + date date NOT NULL, + title character varying(100), + status integer DEFAULT 0 +); + + +-- +-- Name: lessons_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE lessons_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: lessons_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE lessons_id_seq OWNED BY lessons.id; + + +-- +-- Name: students; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE students ( + id integer NOT NULL, + name character varying(10) +); + + +-- +-- Name: students_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE students_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: students_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE students_id_seq OWNED BY students.id; + + +-- +-- Name: teachers; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE teachers ( + id integer NOT NULL, + name character varying(10) +); + + +-- +-- Name: teachers_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE teachers_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: teachers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE teachers_id_seq OWNED BY teachers.id; + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY lessons ALTER COLUMN id SET DEFAULT nextval('lessons_id_seq'::regclass); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY students ALTER COLUMN id SET DEFAULT nextval('students_id_seq'::regclass); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY teachers ALTER COLUMN id SET DEFAULT nextval('teachers_id_seq'::regclass); + + +-- +-- Data for Name: lesson_students; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY lesson_students (lesson_id, student_id, visit) FROM stdin; +1 1 t +1 2 t +1 3 f +2 2 t +2 3 t +4 1 t +4 2 t +4 3 t +4 4 t +5 4 f +5 2 f +6 1 f +6 3 f +7 2 t +7 1 t +8 1 f +8 4 t +8 2 t +9 2 f +10 1 f +10 3 t +\. + + +-- +-- Data for Name: lesson_teachers; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY lesson_teachers (lesson_id, teacher_id) FROM stdin; +1 1 +1 3 +2 1 +2 4 +3 3 +4 4 +6 3 +7 1 +8 4 +8 3 +8 2 +9 3 +10 3 +\. + + +-- +-- Data for Name: lessons; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY lessons (id, date, title, status) FROM stdin; +2 2019-09-02 Red Color 0 +5 2019-05-10 Purple Color 0 +7 2019-06-17 White Color 0 +10 2019-06-24 Brown Color 0 +9 2019-06-20 Yellow Color 1 +1 2019-09-01 Green Color 1 +3 2019-09-03 Orange Color 1 +4 2019-09-04 Blue Color 1 +6 2019-05-15 Red Color 1 +8 2019-06-17 Black Color 1 +\. + + +-- +-- Name: lessons_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('lessons_id_seq', 10, true); + + +-- +-- Data for Name: students; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY students (id, name) FROM stdin; +1 Ivan +2 Sergey +3 Maxim +4 Slava +\. + + +-- +-- Name: students_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('students_id_seq', 4, true); + + +-- +-- Data for Name: teachers; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY teachers (id, name) FROM stdin; +1 Sveta +2 Marina +3 Anglessonselina +4 Masha +\. + + +-- +-- Name: teachers_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('teachers_id_seq', 4, true); + + +-- +-- Name: lessons_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- + +ALTER TABLE ONLY lessons + ADD CONSTRAINT lessons_pkey PRIMARY KEY (id); + + +-- +-- Name: students_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- + +ALTER TABLE ONLY students + ADD CONSTRAINT students_pkey PRIMARY KEY (id); + + +-- +-- Name: teachers_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- + +ALTER TABLE ONLY teachers + ADD CONSTRAINT teachers_pkey PRIMARY KEY (id); + + +-- +-- Name: lesson_students_lesson_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY lesson_students + ADD CONSTRAINT lesson_students_lesson_id_fkey FOREIGN KEY (lesson_id) REFERENCES lessons(id); + + +-- +-- Name: lesson_students_student_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY lesson_students + ADD CONSTRAINT lesson_students_student_id_fkey FOREIGN KEY (student_id) REFERENCES students(id); + + +-- +-- Name: lesson_teachers_lesson_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY lesson_teachers + ADD CONSTRAINT lesson_teachers_lesson_id_fkey FOREIGN KEY (lesson_id) REFERENCES lessons(id); + + +-- +-- Name: lesson_teachers_teacher_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY lesson_teachers + ADD CONSTRAINT lesson_teachers_teacher_id_fkey FOREIGN KEY (teacher_id) REFERENCES teachers(id); + + +-- +-- Name: public; Type: ACL; Schema: -; Owner: - +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM dimon; +GRANT ALL ON SCHEMA public TO dimon; +GRANT ALL ON SCHEMA public TO PUBLIC; + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/Тестовое задание бекенд.pdf b/Тестовое задание бекенд.pdf new file mode 100644 index 0000000..530017c Binary files /dev/null and b/Тестовое задание бекенд.pdf differ