Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

# MiniHttpServer — Туристический сайт (Отели)

Проект представляет собой учебный туристический сайт с динамическими страницами отелей, собственной ORM, шаблонизатором, админ-панелью и самописным веб-сервером на `HttpListener`.

Проект полностью работает через PostgreSQL — все данные загружаются из реальной базы.  
Вёрстка и клиентская логика выполнены на чистом **HTML**, **CSS** и **JavaScript**.

---

## 🚀 Возможности

- Публичный сайт (список отелей, детали отеля, галерея, поиск).
- Полностью динамическая загрузка данных из БД.
- AJAX-фильтрация и AJAX-авторизация (без перезагрузки страницы).
- Административная панель (CRUD отелей, описание, услуги, категории).
- Авторизация пользователей (Admin / Employee / User).
- Собственный HTML-шаблонизатор: переменные, условия, циклы.
- Собственная ORM: CRUD, `Where(Expression)`, маппинг объектов.
- Собственный веб-сервер на `HttpListener`: роутинг, статические файлы, сессии.
- Чистый JavaScript без фреймворков.

---

## 📁 Структура проекта

```text
MiniHttpServer/

├── Endpoints/              # Маршруты (аналог MVC контроллеров)
├── Model/                  # Доменные сущности (Hotel, City, Country …)
├── DTOs/                   # Объекты для запросов/ответов
├── Services/               # Прикладная логика
├── Validators/             # Серверная валидация
├── Template/               # .thtml-шаблоны страниц
├── Public/                 # HTML, CSS, JS (клиентская часть)

├── MiniHttpServer.Framework/
│   ├── Server/             # Реализация HttpListener сервера
│   ├── Handlers/           # Роутинг, статика, обработка ошибок
│   ├── Attributes/         # HttpGet, HttpPost, Endpoint и т.п.
│   └── Settings/           # Настройки (порт, домен, БД)

├── MyORMContext/           # Собственная ORM
├── MiniHttpServer.Data/    # Репозитории (Repository/DAO)
└── Tests/                  # Юнит-тесты

🗄 Настройка PostgreSQL (локальный запуск без Docker)

1. Создать базу данных

CREATE DATABASE onetouche;

2. Создать структуру таблиц

Можно либо выполнить файл init.sql, либо воспользоваться этим SQL-скриптом:

-- This script was generated by the ERD tool in pgAdmin 4.
-- Please log an issue at https://github.com/pgadmin-org/pgadmin4/issues/new/choose if you find any bugs, including reproduction steps.
BEGIN;

CREATE TABLE IF NOT EXISTS public.cities
(
    id serial NOT NULL,
    name character varying(100) COLLATE pg_catalog."default" NOT NULL,
    countryid integer NOT NULL,
    CONSTRAINT cities_pkey PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS public.countries
(
    id serial NOT NULL,
    name character varying(100) COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT countries_pkey PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS public.hotel_categories
(
    id serial NOT NULL,
    name character varying(150) COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT hotel_categories_pkey PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS public.hotel_category_map
(
    hotelid integer NOT NULL,
    categoryid integer NOT NULL,
    CONSTRAINT hotel_category_map_pkey PRIMARY KEY (hotelid, categoryid)
);

CREATE TABLE IF NOT EXISTS public.hotel_mealplans
(
    hotelid integer NOT NULL,
    mealplanid integer NOT NULL,
    CONSTRAINT hotel_mealplans_pkey PRIMARY KEY (hotelid, mealplanid)
);

CREATE TABLE IF NOT EXISTS public.hoteldescriptions
(
    id serial NOT NULL,
    hotelid integer NOT NULL,
    yearopened integer,
    yearrenovated integer,
    totalareasquaremeters numeric(10, 2),
    buildinginfo text COLLATE pg_catalog."default",
    CONSTRAINT hoteldescriptions_pkey PRIMARY KEY (id),
    CONSTRAINT hoteldescriptions_hotelid_key UNIQUE (hotelid)
);

CREATE TABLE IF NOT EXISTS public.hotelplaceinfos
(
    id serial NOT NULL,
    hotelid integer NOT NULL,
    address character varying(255) COLLATE pg_catalog."default",
    city character varying(100) COLLATE pg_catalog."default",
    country character varying(100) COLLATE pg_catalog."default",
    distancetoairport character varying(100) COLLATE pg_catalog."default",
    distancetocenter character varying(100) COLLATE pg_catalog."default",
    distancetobeach character varying(100) COLLATE pg_catalog."default",
    CONSTRAINT hotelplaceinfos_pkey PRIMARY KEY (id),
    CONSTRAINT hotelplaceinfos_hotelid_key UNIQUE (hotelid)
);

CREATE TABLE IF NOT EXISTS public.hotels
(
    id serial NOT NULL,
    name character varying(150) COLLATE pg_catalog."default" NOT NULL,
    slug character varying(150) COLLATE pg_catalog."default" NOT NULL,
    cityid integer NOT NULL,
    hoteltype character varying(50) COLLATE pg_catalog."default" NOT NULL,
    description text COLLATE pg_catalog."default" NOT NULL,
    photourl character varying(255) COLLATE pg_catalog."default" NOT NULL,
    mealplancode character varying(20) COLLATE pg_catalog."default",
    price integer NOT NULL,
    contacts text[] COLLATE pg_catalog."default",
    CONSTRAINT hotels_pkey PRIMARY KEY (id),
    CONSTRAINT hotels_slug_key UNIQUE (slug)
);

CREATE TABLE IF NOT EXISTS public.hotelservices
(
    id serial NOT NULL,
    hotelid integer NOT NULL,
    category character varying(50) COLLATE pg_catalog."default" NOT NULL,
    name character varying(150) COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT hotelservices_pkey PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS public.mealplans
(
    id serial NOT NULL,
    code character varying(20) COLLATE pg_catalog."default" NOT NULL,
    description character varying(255) COLLATE pg_catalog."default" NOT NULL,
    smokingallowedinrestaurant boolean NOT NULL DEFAULT false,
    CONSTRAINT mealplans_pkey PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS public.roomtypes
(
    id serial NOT NULL,
    hotelid integer NOT NULL,
    name character varying(100) COLLATE pg_catalog."default" NOT NULL,
    view character varying(100) COLLATE pg_catalog."default" NOT NULL,
    bedconfiguration character varying(150) COLLATE pg_catalog."default" NOT NULL,
    maxoccupancy integer NOT NULL,
    areasquaremeters integer NOT NULL,
    CONSTRAINT roomtypes_pkey PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS public.users
(
    id serial NOT NULL,
    name character varying(100) COLLATE pg_catalog."default" NOT NULL,
    email character varying(120) COLLATE pg_catalog."default" NOT NULL,
    passwordhash character varying(255) COLLATE pg_catalog."default" NOT NULL,
    role character varying(50) COLLATE pg_catalog."default" NOT NULL DEFAULT 'User'::character varying,
    createdat timestamp with time zone NOT NULL DEFAULT now(),
    login character varying(50) COLLATE pg_catalog."default" NOT NULL,
    phone character varying(30) COLLATE pg_catalog."default" NOT NULL DEFAULT ''::character varying,
    CONSTRAINT users_pkey PRIMARY KEY (id),
    CONSTRAINT users_email_key UNIQUE (email),
    CONSTRAINT users_login_key UNIQUE (login)
);

ALTER TABLE IF EXISTS public.cities
    ADD CONSTRAINT cities_countryid_fkey FOREIGN KEY (countryid)
    REFERENCES public.countries (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE NO ACTION;
CREATE INDEX IF NOT EXISTS ix_cities_countryid
    ON public.cities(countryid);

ALTER TABLE IF EXISTS public.hotel_category_map
    ADD CONSTRAINT hotel_category_map_category_id_fkey FOREIGN KEY (categoryid)
    REFERENCES public.hotel_categories (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;

ALTER TABLE IF EXISTS public.hotel_category_map
    ADD CONSTRAINT hotel_category_map_hotel_id_fkey FOREIGN KEY (hotelid)
    REFERENCES public.hotels (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;

ALTER TABLE IF EXISTS public.hotel_mealplans
    ADD CONSTRAINT hotel_mealplans_hotelid_fkey FOREIGN KEY (hotelid)
    REFERENCES public.hotels (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;

ALTER TABLE IF EXISTS public.hotel_mealplans
    ADD CONSTRAINT hotel_mealplans_mealplanid_fkey FOREIGN KEY (mealplanid)
    REFERENCES public.mealplans (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;

ALTER TABLE IF EXISTS public.hoteldescriptions
    ADD CONSTRAINT hoteldescriptions_hotelid_fkey FOREIGN KEY (hotelid)
    REFERENCES public.hotels (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;
CREATE INDEX IF NOT EXISTS hoteldescriptions_hotelid_key
    ON public.hoteldescriptions(hotelid);

ALTER TABLE IF EXISTS public.hotelplaceinfos
    ADD CONSTRAINT hotelplaceinfos_hotelid_fkey FOREIGN KEY (hotelid)
    REFERENCES public.hotels (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;
CREATE INDEX IF NOT EXISTS hotelplaceinfos_hotelid_key
    ON public.hotelplaceinfos(hotelid);

ALTER TABLE IF EXISTS public.hotels
    ADD CONSTRAINT hotels_cityid_fkey FOREIGN KEY (cityid)
    REFERENCES public.cities (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE NO ACTION;
CREATE INDEX IF NOT EXISTS ix_hotels_cityid
    ON public.hotels(cityid);

ALTER TABLE IF EXISTS public.hotelservices
    ADD CONSTRAINT hotelservices_hotelid_fkey FOREIGN KEY (hotelid)
    REFERENCES public.hotels (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;
CREATE INDEX IF NOT EXISTS ix_hotelservices_hotelid
    ON public.hotelservices(hotelid);

ALTER TABLE IF EXISTS public.roomtypes
    ADD CONSTRAINT roomtypes_hotelid_fkey FOREIGN KEY (hotelid)
    REFERENCES public.hotels (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE CASCADE;
CREATE INDEX IF NOT EXISTS ix_roomtypes_hotelid
    ON public.roomtypes(hotelid);

END;

3. Настроить строку подключения

Файл:

MiniHttpServer.Framework/Settings/settings.json

Пример для локального PostgreSQL:

{
  "Domain": "localhost",
  "Port": "1337",
  "ConectionString": "Host=localhost;Port=5432;Database=onetouche;Username=postgres;Password=PASSWORD"
}

▶ Запуск проекта (локально, без Docker)

  1. Установить .NET 9 SDK
  2. Установить и запустить PostgreSQL
  3. Проверить правильность строки подключения
  4. В терминале выполнить:
cd MiniHttpServer
dotnet run

Сервер запустится по адресу:

http://localhost:1337/

🌐 Публичные страницы

Описание Путь
Главная страница /Public/index.html
Авторизация /Public/autorization.html
Список отелей /hotels
Детали отеля /hotels/{id}

🔑 Административная панель

После авторизации:

/admin

Функции:

  • Просмотр списка отелей
  • Добавление / редактирование / удаление
  • Редактирование описания, характеристик, услуг, категорий
  • Проверка ролей (Admin / Employee)

📡 AJAX-функциональность

  • AJAX-авторизация (POST /auth/login)
  • Фильтрация списка отелей (/api/hotels/search)
  • Подгрузка данных через API
  • Обновление интерфейса без перезагрузки страницы

🧩 Шаблонизатор (.thtml)

Поддерживает:

Переменные

${Hotel.Name}

Условия

@if(Model.HasDiscount) { ... }

Циклы

@foreach(var item in Model.Items) { ... }

🧪 Тесты

Запуск тестов:

dotnet test

Покрыто тестами:

  • движок шаблонизатора
  • веб-сервер

🐳 Запуск проекта в Docker

В проекте есть docker-compose.yml, который поднимает:

  • контейнер с PostgreSQL (postgres)
  • контейнер с приложением (mini-http-server)

1. Требования

  • Установлен Docker Desktop (Windows / macOS / Linux).
  • В терминале команды работают:
docker --version
docker compose version

2. Старт всего проекта

Из корня решения (там, где лежит docker-compose.yml):

docker compose up --build

Что происходит:

  • собирается образ приложения из MiniHttpServer/Dockerfile;
  • поднимается контейнер postgres, выполняется init.sql (создание схемы и начальные данные);
  • после готовности БД запускается контейнер app (mini-http-server).

3. Открыть сайт в браузере

После запуска контейнеров:

http://localhost:1337/index.html

или

http://localhost:1337/Public/index.html

Админка:

http://localhost:1337/admin

🔁 Пересборка и перезапуск контейнеров

Если изменили код, settings.json или Dockerfile:

docker compose down          # остановить контейнеры
docker compose up --build    # пересобрать образ и запустить заново

Можно просто:

docker compose up --build

Docker сам пересоберёт образ при изменениях.


💣 Как «дропнуть» базу и пересоздать её через init.sql

PostgreSQL в Docker хранит данные в томе postgres_data. Чтобы полностью сбросить БД и заново прогнать init.sql:

  1. Остановить и удалить контейнеры с удалением томов:
docker compose down -v
  1. Запустить всё снова:
docker compose up --build

При первом старте:

  • создастся чистая БД onetouche;
  • выполнится init.sql (создаст таблицы, связи, тестовые данные и пользователя Admin).

🔍 Просмотр логов

Логи приложения:

docker logs mini-http-server

Логи PostgreSQL:

docker logs postgres_db

(имена контейнеров можно уточнить командой docker ps или в Docker Desktop).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages