Как подключить веб-приложение Node.js к базе данных SQLite

Как подключить веб-приложение Node.js к базе данных SQLite

Node.js и SQLite

SQLite, выраженный ess-kue-ELL-ite, это легкая бессерверная система баз данных, которая хранит данные в одном файле. Он предоставляет основные функции полнофункционального SQL СУБД.

В этом примере реализуется веб-приложение на Node.js с использованием sqlite3 модуль для чтения и записи данных в файл базы данных SQLite. Он использует макет и дизайн Node.js и пример Святого Грааля Express, добавление панели управления администратора, где контент можно создавать, читать, обновлять или удалять (CRUD операции).

Примечание

В этом примере демонстрируется интеграция базы данных с Node.js, но он не защищает доступ к разделу администратора паролем. Для использования в производственной среде приложению потребуется промежуточное ПО для аутентификации, такое как Заграничный пасспорт, для обеспечения управления сеансом.

Дизайн

На главной странице веб-сайта в основной части отображаются четыре раздела контента: Объявления, Предметы для продажи, Предстоящие события и Сообщение дня..

Схема расположения

Цели

Добавляя возможность подключения к базе данных, это приложение стремится достичь двух связанных целей..

Цель 1: Когда пользователь запрашивает маршрут домашней страницы / с веб-сайта приложение должно считывать текущие данные для этих разделов из базы данных. Текущие данные включаются в шаблон представления и отображаются пользователю..

Цель 2: Приложение должно предоставлять интерфейс браузера, в котором записи базы данных можно просматривать, создавать, обновлять или удалять. Интерфейс «Панели управления» доступен по маршруту / admin, и предназначен только для использования администратором.

Для достижения этих целей необходимо определить структуру данных..

Схема базы данных

База данных схема определяет, как структурирована информация.

Эта база данных должна хранить четыре таблицы данных, по одной для каждого раздела. Эти таблицы похожи на электронные таблицы, организованные в строки и столбцы..

В таблице строка содержит один набор связанных данных: одно объявление, один товар для продажи и т. Д..

Столбцы таблиц структурированы следующим образом.

Имя таблицы
Название столбца
Тип данных
Описание
Предметы я бы ЦЕЛОЕ Положительное целое число, однозначно идентифицирующее эту строку в таблице: 1, 2, так далее.
имя ТЕКСТ Строка с названием продаваемого товара, например., Яйца.
единица измерения ТЕКСТ Строка, описывающая единицы, по которым она оценивается, например., фунт. или же дюжина.
цена ЦЕЛОЕ Цена за единицу, выраженная целым числом центов, например., 500 по пять долларов за единицу.
кол-во ЦЕЛОЕ Целое количество единиц на складе (количество).
desc ТЕКСТ Описание, например., Из наших органических кур на свободном выгуле.
объявить я бы ЦЕЛОЕ Уникальный идентификатор строки.
заглавие ТЕКСТ Строка заголовка, например Открыт для бизнеса.
Дата ТЕКСТ Строка даты, например 15 января, или же В следующую среду в полдень.
тело ТЕКСТ Тело объявления.
События я бы ЦЕЛОЕ Уникальный идентификатор строки.
заглавие ТЕКСТ Название мероприятия.
Дата ТЕКСТ Дата мероприятия.
тело ТЕКСТ Описание мероприятия.
motd я бы ЦЕЛОЕ Уникальный идентификатор строки.
заглавие ТЕКСТ Заголовок сообщения.
тело ТЕКСТ Тело сообщения.

Следующим шагом является разработка того, как эти данные используются в приложении..

Логика MVC

В дизайне MVC (модель-представление-контроллер) логика приложения разделена на три категории..

  • В модель данных является центральным компонентом приложения. Он программно определяет структуру данных, доступ к ним и управление ими. Модель данных взаимодействует напрямую с базой данных..
  • В Посмотреть это самый внешний компонент приложения. Он определяет, как данные будут просматриваться пользователем в браузере, включая макет пользовательского интерфейса и интерактивные компоненты, такие как кнопки, текстовые поля и меню. Логика представления сводится к HTML, CSS и клиентскому JavaScript, отправляемому в браузер пользователя..
  • В контролер ликвидирует разрыв между моделью и видом. Он определяет поведение приложения, например то, что происходит, когда пользователь запрашивает маршрут приложения. Бизнес-логика (как данные используются и преобразуются внутри приложения) является частью контроллера..

В этом приложении логика модели данных содержится в одном файле (/models/data-model.js). Только функции, перечисленные в этом файле, имеют прямой доступ к объекту базы данных..

Логика просмотра содержится в каталогах /взгляды/, / общедоступные / таблицы стилей /, и / общедоступные / javascripts /.

Логика контроллера содержится в /app.js и /routes/*.js. Эти скрипты обрабатывают HTTP-запросы, ошибки и транзакции с помощью модели данных..

Точкой входа в приложение является контроллер. Отправной точкой взаимодействия с пользователем является представление.

Контроллер получает доступ к модели данных для удовлетворения запросов, сделанных из представления..

Базовая настройка

Настройка для этого приложения такая же, как и для Node.js и пример Express.

  1. Убедись Node.js LTS установлен, и npm до даты.

Окна

нпм я нпм -g

Linux, macOS

sudo npm i npm -g

  1. Используйте npm для глобальной установки экспресс-генератор.

Окна

npm я экспресс-генератор -g

Linux, macOS

sudo npm я экспресс-генератор -g

  1. Создайте новое приложение Express и перейдите в каталог.

выразить myapp —view = «мопс»
cd myapp

  1. Установите зависимости приложения Express (npm считывает их из файла package.json, созданного для приложения Express).

нпм я

  1. Если доступны исправления безопасности, примените их.

исправление аудита npm

  1. Установите nodemon как зависимость разработки.

npm я nodemon —save-dev

  1. В текстовом редакторе добавьте сценарий запуска разработки в файл приложения package.json.

package.json (Windows)

«скрипты»: {«начало»: «узел ./bin/www»,
«devstart»: «SET DEBUG = myapp: * & nodemon ./bin/www «
},

package.json (Linux, macOS)

«скрипты»: {«начало»: «узел ./bin/www»,
«devstart»: «DEBUG = myapp: * nodemon ./bin/www»
},

Не забудьте добавить запятую после определения «start», как показано выше..

Установить SQLite

Установите программное обеспечение базы данных SQLite 3.

Окна

Загрузите программное обеспечение и инструменты SQLite, sqlite-инструменты-Win32-x86-версия.застегивать, от Официальный сайт загрузок SQLite под Предварительно скомпилированные двоичные файлы для Windows.

В архиве три файла.

sqldiff.exe sqlite3.exe sqlite3_analyzer.exe

Создайте каталог, например «C: \ Program Files \ SQLite», и извлеките три файла в каталог..

Добавьте полный путь к этому каталогу в системный %ДОРОЖКА% переменная окружения. Для получения дополнительной информации см. как установить переменные среды в Windows.

Убедитесь, что вы можете запустить sqlite3 команда в любом каталоге. Откройте новое окно командной строки, и запустите следующую команду.

sqlite3 —version
3.31.1 2020-01-27 19:55:54 3bfa9cc97da5..

macOS

Бинарные файлы macOS можно загрузить с Сайт SQLite.

В качестве альтернативы sqlite3 пакет можно установить с MacPorts.

sudo порт установить sqlite3

Или sqlite пакет можно установить с помощью Homebrew.

варить установить sqlite

Linux

В Linux SQLite можно установить с помощью системного диспетчера пакетов..

Например, в Debian и Ubuntu sqlite3 пакет можно установить с помощью подходящий.

sudo apt установить sqlite3

Предварительно скомпилированные двоичные файлы и исходный код также доступны на Сайт SQLite.

Установите модуль sqlite3 Node

В sqlite3 Модуль Node.js обеспечивает связь между приложением Node и базой данных SQLite..

В каталоге приложения Express используйте npm установить модуль и сохранить его как зависимость.

npm я sqlite3 — сохранить

Если доступны обновления безопасности, установите их.

исправление аудита npm

Инициализировать базу данных

В сценарий инициализации базы данных содержит серию SQL команды, которые инициализируют базу данных. При выполнении скрипт создает каждую из четырех таблиц и заполняет их предустановленными значениями..

Обзор сценария инициализации

Скрипт представляет собой текстовый файл с расширением .sql. Команды SQLite в сценарии инициализации выполняют следующие задачи..

  1. Первый, СОЗДАЙТЕ названный СТОЛ, и установите параметры для каждого именованного столбца. Параметры включают тип данных, ДЕФОЛТ значение, которое будет установлено при создании строки, и любые специальные атрибуты.
  1. потом, ВСТАВИТЬ В таблица одна или несколько строк, заполненных предустановленным начальным ЗНАЧЕНИЯ.

Примечание

Во всех четырех таблицах столбец «id» имеет специальный атрибут ПЕРВИЧНЫЙ КЛЮЧ. Записи в первичный ключ столбец должен быть уникальным. Значение первичного ключа устанавливается автоматически SQLite при создании строки. По умолчанию первая назначенная клавиша — 1, затем 2 и т. Д..

Создайте сценарий инициализации

В каталоге приложения создайте новый каталог с именем база данных, и изменить на это.

база данных mkdir
база данных cd

В каталоге базы данных создайте каталог в этом.

mkdir init

Создайте новый текстовый файл в каталоге init, db.sql, содержащий следующие команды SQLite.

/database/init/db.sql

/* * Создавать & содержание сайта инициализации * /
— ПРЕДМЕТЫ —
СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ элементы (id INTEGER PRIMARY KEY, имя ТЕКСТ ПО УМОЛЧАНИЮ «», единица ТЕКСТ ПО УМОЛЧАНИЮ «», цена INTEGER DEFAULT 0, кол-во INTEGER DEFAULT 0, desc ТЕКСТ ПО УМОЛЧАНИЮ «»);
ВСТАВИТЬ пункты (название, единица измерения, цена, количество, описание) ЗНАЧЕНИЯ («Бискотти», «пакет из 10 штук», 500, 5, «Замоченный в кофе»), («Молоко», «кварта», 500, 3, «Хороший источник кальция.»), («Яйца», «дюжина», 400, 7, «Отлично подходит для завтрака и выпечки.»), («Целая курица», «фунт», 750, 2, «Идеально» для запекания. «), (» Мед «,» пинта «, 1350, 4,» Сладкий чай. «);
— ОБЪЯВЛЕНИЯ —
СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ, анонс (id INTEGER PRIMARY KEY, заголовок ТЕКСТ ПО УМОЛЧАНИЮ «», дата ТЕКСТ ПО УМОЛЧАНИЮ «», ТЕКСТ ТЕКСТА ПО УМОЛЧАНИЮ «»);
ВСТАВИТЬ INTO объявить (название, дата, тело) ЦЕННОСТИ («Открыт для бизнеса», «15 января», «Ремонт нашего нового магазина завершен, и мы открыты для бизнеса»);
— СОБЫТИЯ —
СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ события (id INTEGER PRIMARY KEY, заголовок ТЕКСТ ПО УМОЛЧАНИЮ «», дата ТЕКСТ ПО УМОЛЧАНИЮ «», ТЕКСТ ТЕКСТА ПО УМОЛЧАНИЮ «»);
ВСТАВИТЬ события (название, дата, текст) ЦЕННОСТИ («Фестиваль сидра», «20 октября, 14: 00–18: 00», «Отметьте сезон свежевыжатым сидром из наших садов»), («Мастерская по выпечке хлеба», » 13 декабря, 9 утра-полдень »,« Научитесь создавать и выращивать закваску »);
— MOTD —
СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ motd (id INTEGER PRIMARY KEY, заголовок ТЕКСТ ПО УМОЛЧАНИЮ «», ТЕКСТ ТЕКСТА ПО УМОЛЧАНИЮ «»);
ВСТАВЬТЕ В motd (заголовок, текст) ЦЕННОСТИ («Послание дня», «Ешьте лучше. Поддержите свою местную ферму»);

Запустите сценарий инициализации

Сценарий SQLite не может запускаться непосредственно из командной строки системы. Он должен запускаться из клиента командной строки SQLite.

Примечание

Эти команды не предназначены для выполнения в существующем файле базы данных. Если у вас есть существующая база данных и вы хотите вернуть ее в исходное состояние, удалите (или переместите) существующий файл базы данных перед повторным запуском скрипта..

В каталоге базы данных запустите sqlite3 чтобы запустить интерактивный клиент, открыв файл db.sqlite как (новая) база данных.

sqlite3 db.sqlite

В клиенте SQLite запустите сценарий инициализации, используя .читать команда.

.читать init / db.sql

Скрипт выполняется немедленно. Если команды выполнены успешно, данные записываются в файл db.sqlite в каталоге базы данных, и сообщение не отображается.

Чтобы выйти из клиента SQLite, выполните команду .покидать.

.покидать
Кончик

Чтобы узнать больше о SQLite и модуле sqlite3, продолжайте читать следующие два раздела. Чтобы перейти непосредственно к коду приложения, см.: Реализация: код и структура.

Обзор SQLite

SQLite похож на другие базы данных SQL, но имеет свои сильные стороны и вариации синтаксиса. Ниже приводится обзор некоторых основных команд и примеров запросов..

Кончик

Команды SQLite, начинающиеся с точки (например,., .читать) выполнять административные функции. Эти команды должны выполняться в интерактивном режиме на клиенте, и не можешь быть написанным по сценарию.

Команды запроса SQL (например,., ВЫБРАТЬ * ИЗ таблицы;) должен заканчиваться точкой с запятой при запуске в клиенте или скрипте. Разрывы строк разрешены как пробелы в команде запроса SQL..

Имена команд не чувствительный к регистру, но по соглашению они пишутся заглавными буквами (например,., ВЫБРАТЬ скорее, чем Выбрать), чтобы отличить их от параметров и значений..

Листинговые таблицы

В каталоге базы данных, который вы создали (myapp / база данных), запустите клиент SQLite, открыв db.sqlite база данных для транзакций.

sqlite3 db.sqlite

На sqlite> подсказка, запустите команду .столы чтобы вывести список таблиц, созданных сценарием инициализации.

.столы
анонсировать события предметы motd

Чтение данных из таблицы

Запрос ВЫБРАТЬ столбец ИЗ стол выбирает все значения в этом столбце этой таблицы.

ВЫБРАТЬ единицу ИЗ элементов;
мешок 10 литров дюжины фунтов пинты

Пункт КУДА ограничение может использоваться в команде SELECT для фильтрации результатов.

ВЫБРАТЬ desc FROM items WHERE name = ‘Honey’;
Делает ваш чай сладким.

Команда SELECT принимает подстановочный знак * для имени столбца, что означает «получить всю строку». Значения в одной строке разделены вертикальной чертой..

ВЫБРАТЬ * ИЗ элементов;
1 | Бискотти | пакет из 10 | 500 | 5 | Замочен в кофе. 2 | Молоко | кварта | 500 | 3 | Хороший источник кальция. 3 | Яйца | дюжина | 400 | 7 | Отлично подходят для завтрака и выпечки. 4 | Целая курица | фунт | 750 | 2 | Идеально подходит для запекания. 5 | Мед | пинта | 1350 | 4 | Придает сладость вашему чаю.

Изменение значений

Значения можно изменить с помощью ОБНОВИТЬ стол НАБОР столбец КУДА ограничение.

ВЫБЕРИТЕ количество ИЗ элементов, ГДЕ name = ‘Яйца’;
7
ОБНОВЛЕНИЕ элементов SET qty = 6 WHERE name = ‘Eggs’;
ВЫБЕРИТЕ количество ИЗ элементов, ГДЕ name = ‘Яйца’;
6

Несколько столбцов можно указать в любом порядке через запятую. Если предложение WHERE опущено в запросе UPDATE, затронуты все строки.

ОБНОВЛЕНИЕ пунктов SET qty = 1, price = 888; / * обновляем два столбца во всех строках * /
ВЫБРАТЬ * ИЗ элементов;
1 | Бискотти | пакет из 10 |888|1| Великолепно обмакнутый в кофе. 2 | Молоко | кварта |888|1| Хороший источник кальция. 3 | Яйца | дюжина |888|1| Отлично подходит для завтрака и выпечки. 4 | Целая курица | фунт |888|1| Идеально подходит для жарки. 5 | Мед | пинта |888|1| Делает чай сладким.

Цитирование

По соглашению, текст в двойных кавычках в запросе SQL обозначает идентификатор, Такие как «Предметы» или же «кол-во». Текст в одинарных кавычках обозначает строковый литерал, Такие как «мешок из 10» или же «Хороший источник кальция».

Обычно допускается использование одинарных или двойных кавычек, если они совпадают. Например, если удобнее заключать строку в двойные кавычки, потому что она содержит одинарные кавычки как часть своего текста, это нормально..

Строковые литералы всегда следует заключать в кавычки. Цитировать идентификаторы и числа необязательно..

ОБНОВЛЕНИЕ элементов SET desc = ‘foo’, qty = 2 WHERE name = ‘Milk’; /* действительный */
ОБНОВЛЕНИЕ пунктов SET «desc» = «foo», qty = ‘2’ WHERE name = «Milk»; /* действительный */
ОБНОВЛЕНИЕ ‘items’ SET desc = ‘foo’, qty = 2 WHERE name = «Milk»; /* действительный */

Создание таблицы

Новую таблицу можно создать с помощью СОЗДАТЬ ТАБЛИЦУ стол (столбец тип атрибуты…, …).

СОЗДАТЬ ТАБЛИЦУ foobar (id INTEGER PRIMARY KEY, foo TEXT NOT NULL DEFAULT ‘bar’);

Если вы попытаетесь создать таблицу, которая уже существует, выдается сообщение об ошибке и никаких изменений не вносится..

Ошибка: таблица foobar уже существует

В СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ команда ведет себя аналогично CREATE TABLE, за исключением того, что если таблица уже существует, команда завершается с ошибкой (не сообщается об ошибке).

СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ foobar (id INTEGER PRIMARY KEY, bar BLOB);

Атрибут столбца ДЕФОЛТ буквальный устанавливает значение по умолчанию для столбца. Когда создается новая строка, указав ЗНАЧЕНИЯ ПО УМОЛЧАНИЮ вставляет это значение в новую строку. Если значение по умолчанию не указано, SQLite вместо этого использует значение NULL..

СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ foobar (id INTEGER PRIMARY KEY, bar TEXT DEFAULT ‘not foo’, baz TEXT DEFAULT ‘not bar’);

Создание строк

Новую строку можно создать с помощью ВСТАВИТЬ В стол (столбец; …) ЗНАЧЕНИЯ (значение; …).

INSERT INTO items (name, price, unit, qty, desc) VALUES («Чеснок», 150, «голова», 24, «Репеллент вампиров.»);

Можно создать несколько строк, указав несколько групп значений.

ВСТАВИТЬ пункты (название, цена) ЗНАЧЕНИЯ (‘Петрушка’, 100), (‘Шпинат’, 350);

Удаление строк

Строки можно удалить с помощью УДАЛИТЬ ИЗ стол КУДА ограничение.

УДАЛИТЬ ИЗ элементов, ГДЕ name = ‘Чеснок’;

Если предложение WHERE опущено, все строки удаляются..

УДАЛИТЬ ИЗ foobar;

Список столбцов таблицы

Чтобы перечислить метаданные столбцов таблицы, включая имя идентификатора, тип данных, значение по умолчанию и атрибуты, используйте команду PRAGMA table_info (стол).

PRAGMA table_info (foobar);
0 | id | INTEGER | 0 || 1 1 | foo | TEXT | 1 | ‘bar’ | 0

Удаление таблицы

Чтобы удалить таблицу, используйте ТАБЛИЦА ПАРАМЕТРОВ команда.

DROP TABLE foobar;

Выход из клиента SQLite

Для выхода из клиента SQLite используйте команду .покидать.

.покидать
Кончик

Подробное описание команд и запросов SQLite см. В официальный справочник по SQLite SQL.

Обзор модуля sqlite3 Node

В sqlite3 модуль предоставляет функции для приложения Node для связи с базой данных SQLite.

Объект базы данных

В этом приложении модуль sqlite3 требуется /database/db.js, указание, что отображаются подробные ошибки.

var sqlite3 = require (‘sqlite3’). verbose ();

Затем новый База данных объект создается с использованием базы данных, инициализированной выше, /database/db.sqlite.

var db = new sqlite3.Database (‘./ database / db.sqlite’);

Этот объект экспортируется (доступен для других скриптов, которым он нужен).

module.exports = db;

Использование объекта базы данных

Единственный сценарий, для которого требуется объект базы данных, — это модель данных., /models/data-model.js.

var db = require (‘../ database / db’);

Методы этого объекта могут работать с db.функция().

Пример использования в функции

Например, База данных.Функция run () выполняет команду SQL в базе данных и не возвращает строк.

Кончик

В общем, разница между запросом SQL и командой SQL заключается в том, что запрос возвращает строки базы данных, но команда не требует ответа базы данных..

Синтаксис функции run ():

База данных.пробег(Строка запроса, [параметры…], [Перезвоните])

В этом приложении команда run () используется createRow () и deleteRow () функции модели данных, которые действуют как функции-оболочки для одного вызова db.run ().

функция createRow (table, cb) {let sql = `INSERT INTO $ {table} DEFAULT VALUES`; db.run (sql, cb); };

В createRow () функция принимает два параметра: имя таблицы в базе данных и функция обратного вызова.

Строка запроса sql создается как строка шаблона JavaScript, в которой используются обратные кавычки (`…`) вместо одинарных или двойных кавычек. Внутри строки шаблона экземпляры $ {} оцениваются, и их значение вставляется в этой точке строки.

Функция обратного вызова cb передается db.run в качестве последнего параметра. Когда запрос в Строка запроса завершено, db выполняет обратный вызов cb.

Если другой скрипт требует data-model.js как модель данных, он может создать строку в базе данных с dataModel.createRow («имя таблицы«, Перезвоните). Функция обратного вызова может быть определена встроенной в качестве параметра в вызывающей функции..

Например, функция обратного вызова в этом вызове createRow () определяется встроенным как анонимная функция.

dataModel.createRow («элементы», function () {console.log («Все готово.»)});

Обратные вызовы

Как и большинство модулей Node, sqlite3 предназначен для асинхронный операция, используя функции обратного вызова, чтобы предотвратить блокировку ввода-вывода базы данных основной поток программы.

По умолчанию модуль узла sqlite3 выполняет транзакции базы данных в параллельно. Когда функция вроде db.run () вызывается, он немедленно возвращается, до того, как произошли какие-либо транзакции, и выполнение программы продолжается. Контроллер может продолжать делать другие вещи, будучи уверенным, что действие базы данных произойдет как можно скорее..

Любые задачи, зависящие от завершения действия с базой данных, структурированы как функция обратного вызова. В База данных объект «перезванивает» этой функции, когда транзакции базы данных завершены.

Например, когда пользователь запрашивает домашнюю страницу приложения, маршрутизатор (в index.js) запрашивает контент из базы данных, предоставляя функцию обратного вызова, которая отображает представление..

Кончик

Для получения подробной информации об API sqlite3 см. официальная вики sqlite3.

Реализация: код и структура

Структура и код файла приложения указаны ниже..

  • Файлы и каталоги, перечисленные в чернить являются частью приложения Express по умолчанию и не были изменены.
  • Файлы, перечисленные в синий являются частью приложения Express по умолчанию. Их содержимое следует заменить приведенным ниже кодом..
  • Файлы, перечисленные в зеленый должен быть создан. Также необходимо создать каталог «модели»..
  • Файлы, перечисленные в красный являются частью приложения Express по умолчанию, но не используются в этом проекте и могут быть безопасно удалены.

Файловая структура приложения

myapp / ├─ app.js Основная логика приложения. ├─ bin / (содержит исполняемые скрипты приложения) │ └─ www Обертка, запускающая app.js. ├─ база данных / (содержит файл базы данных, сценарий инициализации и объект JS) │ ├─ db.js Открывает базу данных как объект базы данных sqlite3. │ ├─ db.sqlite База данных. │ └─ init / (содержит сценарии инициализации базы данных) │ └─ db.sql Скрипт инициализации базы данных. ├─ models / (содержит логику модели) │ └─ data-model.js Модель данных, включая функции CRUD. ├─ node_modules / (содержит зависимости, установленные npm) ├─ пакет-lock.json Манифест установленных зависимостей в формате JSON. ├─ package.json JSON сохраненных зависимостей, сценариев запуска и т. Д.. ├─ public / (Файлы, загруженные веб-браузером пользователя) │ ├─ images / (содержит файлы изображений, доступные для клиента) │ ├─ javascripts / (содержит файлы JavaScript, доступные клиенту) │ │ ├─ datestring.js Преобразует время UNIX в строки даты │ │ └─ menu.js Переключатель меню реализует │ └─ таблицы стилей / (Содержит доступный клиенту CSS) │ └─ style.css Таблица стилей CSS ├─ routes / (содержит логику для отдельных маршрутов сайта) │ ├─ about.js Логика для маршрута / о │ ├─ admin.js Логика для маршрута / админа │ ├─ advice.js Логика маршрута / совета │ ├─ contact.js Логика для маршрута / контакта │ ├─ index.js Логика для маршрута / │ ├─ recipes.js Логика для маршрута / рецептов │ ├─ tips.js Логика маршрута / подсказки │ └─ users.js Не используется, можно удалить └─ views / (содержит логику просмотра) ├─ о. мопс Просмотр маршрута / о ├─ админ-анонс.pug Просмотр маршрута / администратора / объявления ├─ админ-events.pug Просмотр маршрута / администратора / событий ├─ админ-items.pug Просмотр маршрута / администратора / предметов ├─ админ-layout.pug Родительский шаблон для admin * просмотров ├─ админ-motd.pug Просмотр для маршрута / admin / motd ├─ admin.pug Просмотр для маршрута / администратора ├─ совет. мопс Просмотр маршрута / совета ├─ contact.pug Просмотр маршрута / контакта ├─ error.pug Просмотр страниц с ошибками, например, HTTP 404 ├─ index.pug Просмотр маршрута / ├─ layout.pug Просмотреть шаблон для всех неадминистративных страниц ├─ recipes.pug Просмотр маршрута / рецептов └─ советы. мопс Просмотр маршрута / советов
синий = изменено, зеленый = новый файл, красный = не используется

/app.js

/ * app.js — логика и конфигурация ядра приложения * /
var createError = require (‘http-errors’); var express = require (‘экспресс’); var path = require (‘путь’); var logger = require (‘morgan’);
// объект приложения var app = express ();
// объекты маршрутизатора var indexRouter = require (‘./ routes / index’); var aboutRouter = require (‘./ routes / about’); var contactRouter = require (‘./ routes / contact’); var tipsRouter = require (‘./ routes / tips’); var recipesRouter = require (‘./ routes / recipes’); var adviceRouter = require (‘./ routes / advice’); var adminRouter = require (‘./ routes / admin’);
// просмотр конфигурации движка app.set (‘views’, path.join (__ dirname, ‘views’)); app.set (‘двигатель просмотра’, ‘мопс’);
// конфигурация приложения app.use (logger (‘dev’)); app.use (express.json ()); app.use (express.urlencoded ({extended: false})); app.use (express.static (path.join (__ dirname, ‘public’)));
// настраиваем приложение для использования этих маршрутов app.use (‘/’, indexRouter); app.use (‘/ about’, aboutRouter); app.use (‘/ contact’, contactRouter); app.use (‘/ tips’, tipsRouter); app.use (‘/ recipes’, recipesRouter); app.use (‘/ advice’, adviceRouter); app.use (‘/ admin’, adminRouter);
// поймать 404, перейти к обработчику ошибок app.use (function (req, res, next) {next (createError (404));});
// отвечаем на запросы favicon с 204 без содержимого app.get (‘/ favicon.ico’, (req, res) => res.sendStatus (204));
// обработчик ошибок app.use (function (err, req, res, next) {// установить локальные переменные, выдавая только ошибку в разработке res.locals.message = err.message; res.locals.error = req.app.get (‘env’) === ‘development’? err: {}; // отображаем страницу с ошибкой res.status (err.status || 500); res.render (‘error’);});
// предоставляем это приложение скриптам, которые этого требуют, т.е. myapp / bin / www module.exports = app;

/database/db.js

/ * db.js — Конфигурация объекта базы данных * / / * verbose = длинные трассировки стека, только для разработки * /
var sqlite3 = require (‘sqlite3’). verbose (); var db = new sqlite3.Database (‘./ database / db.sqlite’);
module.exports = db;

/models/data-model.js

/ * data-model.js: определение структуры данных и управления ими * / / * Это единственный файл, для которого требуется объект базы данных * /
var db = require (‘../ database / db’);
/ * схема var используется для удобства получения имен столбцов в updateRow () * /
var schema = {«items»: [«id», «name», «unit», «price», «qty», «desc»], «announce»: [«id», «title», «date» , «body»], «events»: [«id», «title», «date», «body»], «motd»: [«id», «title», «body»]};
/ * Функции CRUD: readTable, createRow, updateRow, deleteRow * /
функция readTable (table, cb) {let sql = `SELECT * FROM $ {table}`; let data = {}; db.all (sql, function (err, rows) {/ * Вернуть все результаты запроса * / if (err) throw (err); / * Если есть ошибка, завершить приложение * / rows.forEach (function (row) {/ * Для каждой строки, соответствующей запросу * / data [row.id] = {}; / * инициализировать идентификатор строки как ключ верхнего уровня * / Object.keys (row) .forEach (function (k) {/ * For каждый столбец строки * / data [row.id] [k] = unescape (row [k]); / * добавить пару ключ-значение * /});}); cb (data); / * data = { id: {«colname»: value}, …}, id2: …} * /}); };
функция createRow (table, cb) {let sql = `INSERT INTO $ {table} DEFAULT VALUES`; db.run (sql, cb); };
функция updateRow (таблица, rb, cb) {var пары = «»; / * для построения ‘идентификатор = значение, …’ * / for (поле схемы [таблица] .slice (1)) {/ * для каждого столбца, кроме id * / if (пары) пары + = «,»; / * вставляем запятую, если строка не пуста * / pair + = `$ {field} = ‘$ {escape (rb [field])}’`; / * столбец = ‘значение’ * /} let sql = `UPDATE $ {table} SET $ {pair} WHERE id =?`; / *? = rb [‘id’] * / db.run (sql, rb [‘id’], cb); };
функция deleteRow (table, id, cb) {let sql = `УДАЛИТЬ ИЗ $ {table} WHERE id = $ {id};`; db.run (sql, cb); };
module.exports = {схема, readTable, createRow, updateRow, deleteRow}

/public/javascripts/datestring.js

/ * datestring.js — клиентский скрипт для преобразования времени UNIX в строки * / / * Используется в кнопке «сегодня» в / admin / announce, / admin / events * /
function stringifyDate (unixDate) {var monthLUT = {0: «Январь», 1: «Февраль», 2: «Март», 3: «Апрель», 4: «Май», 5: «Июнь», 6: «Июль. », 7:« Август », 8:« Сентябрь », 9:« Октябрь », 10:« Ноябрь », 11:« Декабрь »}; var weekdayLUT = {0: «Воскресенье», 1: «Понедельник», 2: «Вторник», 3: «Среда», 4: «Четверг», 5: «Пятница», 6: «Суббота»,};
var dateObj = новая дата (unixDate * 1000); var weekdayStr = `$ {weekdayLUT [dateObj.getDay ()]}`; var monthStr = `$ {monthLUT [dateObj.getMonth ()]}`; var monthday = dateObj.getDate (); var monthdayStr = `$ {monthday}`; var yearStr = `$ {dateObj.getFullYear ()}`;
var dateString = «»; dateString + = `$ {weekdayStr},`; dateString + = `$ {monthStr}`; dateString + = `$ {monthdayStr}`; / * Раскомментируйте следующую строку для «12-го», «23-го» и т. Д. * / // dateString + = `$ {getOrd (monthday)}, $ {yearStr}`;
return dateString; }
функция getOrd (число) {var ord = «»; переключатель (истина) {case (число == 1 || число == 21 || число == 31): ord = «st»; перемена; case (число == 2 || число == 22): ord = «nd»; перемена; case (число == 3 || число == 23): ord = «rd»; перемена; по умолчанию: ord = «th»; перемена; } return ord; }
функция todayString () {пусть сейчас = новая дата (); return stringifyDate (Math.round (now.getTime () / 1000)); }

/public/javascripts/menu.js

/ * menu.js — клиентский скрипт для переключения меню при нажатии кнопки * /
функция menuToggle (состояние) {var ele = document.getElementById (‘меню’); переключатель (состояние) {case ‘show’: ele.style.opacity = 1; ele.style.color = ‘rgb (96, 96, 96)’; ele.style.visibility = ‘видимый’; ele.style.transition = ‘видимость 0 с, непрозрачность 0,3 с’; перемена; case ‘hide’: ele.style.opacity = 0; ele.style.color = ‘черный’; ele.style.visibility = ‘скрытый’; ele.style.transition = ‘видимость 0,3 с, непрозрачность 0,3 с’; перемена; }}

/public/stylesheets/style.css

/ * style.css — стили элементов сайта и адаптивное поведение области просмотра * /
* {маржа: 0; / * по умолчанию все элементы (селектор *) не имеют поля * /}
html {ширина: 100%; / * 100% ширина родительского (корневого) элемента * / height: 100vh; / * 100% высота области просмотра * / background: rgb (0, 0, 0, 0.1); / * 10% черный * / font-size: 1.0em; / * размер нашего корневого шрифта * / font-family: Arial, Helvetica, sans-serif; / * шрифт по умолчанию * /}
body {min-height: 100%; }
section {padding: 0.5rem; }
section.admin {padding: 0 0 0 0.5rem; flex-grow: 0; }
h1 {/ * Название сайта в шапке * / font-size: 2.0rem; шрифт: нормальный; набивка: 0,5 бэр; }
h2 {/ * About, Contact * / font-size: 1.25rem; }
h3 {/ * Заголовки разделов * / font-size: 1.2rem; набивка: 0,5 бэр; }
h3.admin {padding-left: 1.0rem; }
h4 {/ * Название элемента раздела * / font-weight: normal; набивка: 0,5 бэр; }
p {/ * тело элемента раздела * / padding: 0.5rem; }
a: link, a: посещенные {/ * якорные ссылки и посещенные якорные ссылки * / color: black; текстовое оформление: нет; / * отключить подчеркивание * /}
a: hover {/ * при наведении курсора на якорную ссылку * / color: rgb (25, 25, 25); }
a: active {/ * при нажатии на ссылку привязки * / color: rgb (96, 96, 96); }
ввод, текстовое поле {размер шрифта: 1.0rem; }
textarea {ширина: 90%; высота: 8бэр; отступ справа: 2 бэр; }
кнопка, ввод [type = button], input [type = submit], input [type = reset] {display: flex; размер шрифта: 1 бэр; цвет фона: rgb (0, 0, 0, 0,15); заполнение: 0 1 бэр 0 1 бэр; радиус границы: 1 бэр; граница: 0,08 бэр, сплошной RGB (48, 40, 32); align-items: center; justify-content: пробел между; курсор: указатель; / * указывает, что по нему можно щелкнуть как по ссылке * / user-select: none; / * пользователь не может выбрать кнопку как текст * /} button.delete {background-color: rgb (210, 200, 200); } button.update {цвет фона: rgb (200, 210, 200); } button.add {цвет фона: rgb (210, 210, 220); }
.кнопки {дисплей: гибкий; justify-content: гибкий старт; align-items: center; край-правый: 2 бэр; }
.icon {font-size: 1.5rem; } .delete .icon {цвет: темно-красный; } .update .icon {цвет: темно-зеленый; } .add .icon {цвет: темно-синий; }
ввод [type = submit]: hover {background-color: rgb (200, 225, 250, 1); }
/ * стили компонентов * /
#container {display: grid; высота: 100vh; столбцы-шаблон-сетки: [слева] 10rem auto 10rem [справа]; сетка-шаблон-строки: [вверху] 5rem auto 5rem [внизу]; / * высота заголовка соответствует его содержимому * / grid-template-area: «head head head» «panleft mainbody panright» «foot foot foot»; }
#header {grid-area: head; / * соответствует имени в шаблоне * / background: rgb (0, 0, 0, 0.2); / * 20% черный * / display: flex; flex-direction: ряд; justify-content: пробел между; align-items: baseline; / * имя сайта и текст навигационного элемента выравниваются по базовой линии * / padding: 0.5rem; }
#panel {/ * для элемента id = «panel» * / display: flex; / * этот элемент является родительским элементом flexbox * / flex-direction: column; / * его дочерние элементы изгибаются вертикально * / padding: 0.5rem; фон: rgb (0, 0, 0, 0.1); / * 10% черный * /} # panel.left {/ * для элементов id = «panel» и class = «left» * / grid-area: panleft; / * этот элемент заполняет область сетки * /} # panel.right {grid-area: panright; }
#footer {grid-area: foot; дисплей: гибкий; / * этот элемент является родительским элементом flexbox * / flex-direction: column; / * его дочерние элементы изгибаются по вертикали * / justify-content: center; / * содержимое нижнего колонтитула по центру * / align-items: center; / * содержимое нижнего колонтитула по вертикали * / padding: 0.5rem; фон: rgb (0, 0, 0, 0.2); }
#mainbody {/ * для элемента id = «mainbody» * / display: flex; / * этот элемент является родительским элементом flexbox * / flex-direction: column; / * его дочерние элементы изгибаются вертикально * / grid-area: mainbody; оправдать-себя: слева; ширина: 99%; / * небольшая передышка у правого края * / min-width: 22.5rem; / * ширина основной части не может идти < 22,5 бэр * /}
#partners, #sections {/ * для элемента id = «partners» или id = «section» * / display: flex; / * этот элемент является родительским элементом flexbox * / flex-direction: row; / * его дочерние элементы сгибаются по горизонтали * / flex-wrap: wrap; / * его дочерние элементы могут переноситься на следующую строку * / align-content: flex-start; / * дочерние элементы начинаются в верхнем левом углу * /}
# partners.wide {/ * для элементов id = «partners» и class = «wide» * / display: none; / * по умолчанию не отображать этот элемент * /}
#menu {position: absolute; / * позиция меню не зависит от других элементов * / right: 0; / * ноль пикселей от правой границы * / background: rgb (239, 239, 239); граница: 0,15 бэр, сплошной RGB (0, 0, 0, 0,4); видимость: скрыта; / * свойство видимости поддерживает переходы * / opacity: 0; / * непрозрачность + переход видимости = эффект исчезновения меню * / z-index: 1; / * убедитесь, что меню отображается поверх всего остального содержимого * / min-width: 13rem; / * меню никогда не должно сужаться * /}
#menuitems {/ * меню реализовано как контейнер flexbox * / display: flex; flex-direction: столбец; набивка: 0,5 бэр; }
#menuitems h3 {border-top: 0.15rem твердый rgb (0, 0, 0, 0.1); / * светлая горизонтальная линейка * /}
#menuitems .sectrule {цвет границы: rgb (0, 0, 0, 0,25); / * более темная горизонтальная линия * /}
#menuitems .menuhead {border-top: none; }
#menuitems h3: hover {background-color: rgb (0, 0, 0, 0,1); / * серый цвет пунктов меню при наведении курсора * /}
.menubutton {выравнивание текста: вправо; курсор: указатель; / * указывает, что по нему можно щелкнуть как по ссылке * / user-select: none; / * пользователь не может выбрать кнопку как текст * /}
#menuitems .alignright {выравнивание текста: право; / * текст пункта меню с выравниванием по правому краю (не используется) * /}
# заголовок .menubutton {дисплей: нет; / * в виде по умолчанию (альбомный), скрыть кнопку меню * / border: 0.15rem solid rgb (0, 0, 0, 0); / * (невидимая) регулировочная шайба * /}
#header .placeholder {/ * эта невидимая кнопка отображается при отображении меню * / color: rgb (0, 0, 0, 0); / * кнопка скрыта, поэтому высота заголовка совпадает. * / user-select: нет; / * пользователь не может выделить текст невидимой кнопки * / border: 0.15rem solid rgb (0, 0, 0, 0); / * (невидимая) регулировочная шайба * /}
.sectionlink, .partnerlink {радиус границы: 0,25 rem; / * придаем этому элементу слегка закругленный край * / font-weight: normal; размер шрифта: 1.1rem; ширина: 100%; нижнее поле: 1,0 бэр; фон: rgb (0, 0, 0, 0.1); }
.sectionlink: hover, .partnerlink: hover {цвет фона: rgb (0, 0, 0, 0,065); / * увеличение яркости при наведении курсора мыши * /}
.партнерская ссылка {высота: 8рем; / * элементы-партнеры имеют дополнительно фиксированную высоту * / width: 8rem; набивка: 0,0 бэр; нижнее поле: 1.0rem; }
.partnerlink.wide {маржа: 0,5 бэр 1 бэр 0,5 бэр 0; / * поля для интервала, если они переносятся * /}
.eventitem, .announceitem, .motditem {margin-bottom: 0,5rem; / * небольшой запас для удобочитаемости * /}
.title {/ * например, «Открыто для бизнеса» * / font-style: italic; шрифт: нормальный; размер шрифта: 1.1rem; }
.дата, .ingredient {стиль шрифта: курсив; размер шрифта: 0,9 бэр; заполнение: 0 0 0,01 бэр 0,5 бэр; цвет: rgb (0, 0, 0, 0,5); }
.navitem {/ * О нас, Контакты * / font-weight: normal; набивка: 0,5 бэр; padding-right: 1.0rem; }
.пробел, .headspace, .panspace, .footspace, .bodyspace {flex-grow: 1; / * эти элементы расширяются по оси гибкости, чтобы заполнить пространство * /}
.отступ {отступ слева: 0,5 бэр; }
/ * стили таблицы («товары для продажи») * /
таблица {граница-коллапс: коллапс; / * ячейки таблицы, смежные с пикселями * / width: 100%; нижнее поле: 1 бэр; }
й {выравнивание текста: слева; }
tr {маржа: 4rem 0 0 0; нижняя граница: 0,15 бэр, сплошной RGB (0, 0, 0, 0,15); / * горизонтальная линейка * /}
tr.norule {граница-дно: нет; }
td, th {padding: 0.5rem; вертикальное выравнивание: сверху; }
td.price {белое пространство: nowrap; / * пробел в цене не переносит строку * /}
td.qty, th.qty {выравнивание текста: центр; }
th {font-size: 1.1rem; }
td.label, th.label {font-size: 1.1rem; ширина: 3бэр; выравнивание текста: вправо;
}
span.perunit {непрозрачность: 0,5; }
/ * адаптивные стили для портретного режима: скрыть панели, партнеры в теле * /
@media screen и (max-width: 45rem) {/ * если ширина области просмотра < 45rem * / # panel.left {конец столбца сетки: слева; / * область сетки панели уменьшается до нуля * /} # panel.right {grid-column-start: right; / * область сетки панели уменьшается до нуля * /} # partners.tall {display: none; / * скрыть партнеров в панели (перезаписывает display: flex) * /} # partners.wide {display: flex; / * показать партнеров в теле (перезаписывает display: none) * /} #panel, / * они исчезают из макета * / #header .placeholder, .navitem {display: none; } #mainbody {начало столбца сетки: слева; / * mainbody теперь начинается с левого края * / grid-column-end: right; / * mainbody теперь заканчивается на правом краю * /} #header .menubutton {/ * отображать кнопку меню заголовка * / display: inline; / * заменяет отображение: нет * /}}
/ * В альбомном режиме (приблизительная ширина области просмотра 45-55бэр) скрыть правую панель * /
@media screen и (max-width: 55rem) {# partners.tall {display: none; / * скрыть партнеров в панели (перезаписывает display: flex) * /} # partners.wide {display: flex; / * показать партнеров в теле (перезаписывает display: none) * /} # panel.right {grid-column-start: right; дисплей: нет; } #mainbody {конец столбца сетки: справа; }}

/routes/about.js

/ * about.js — маршрут / about * /
var express = require (‘экспресс’); var router = express.Router ();
router.get (‘/’, функция (req, res, next) {res.render (‘about’, {pagetitle: ‘О нас’});});
module.exports = маршрутизатор;

/routes/admin.js

/ * admin.js — маршруты / admin / * * /
var express = require (‘экспресс’); var router = express.Router (); var dataModel = require (‘../ models / data-model.js’);
/ * МАРШРУТ: / admin * /
router.get (‘/’, функция (req, res, next) {res.render (‘admin’, {pagetitle: ‘Control Panel’});});
/ * МАРШРУТ: / admin / announce * /
router.get (‘/ announce’, function (req, res, next) {dataModel.readTable («announce», function (data) {res.render (‘admin-announce’, {pagetitle: ‘Edit Announcements’, messages) : данные }); }); });
/ * МАРШРУТ: / admin / update / announce * /
router.post (‘/ update / announce’, function (req, res, next) {cb = function () {/ * тот же обратный вызов, используемый для обновления и удаления * / res.redirect (‘/ admin / announce’);} ; if (req.body.action == «delete») {/ * if action = delete * / dataModel.deleteRow («announce», req.body.id, cb);} else {/ * else action = update * / dataModel.updateRow («анонс», req.body, cb);}});
/ * МАРШРУТ: / admin / создать / объявить * /
router.post (‘/ create / announce’, function (req, res, next) {dataModel.createRow («announce», function () {res.redirect (‘/ admin / announce # footer’);});} );
/ * МАРШРУТ: / admin / items * /
router.get (‘/ items’, function (req, res, next) {dataModel.readTable («items», function (data) {Object.keys (data) .forEach (function (id) {data [id]. price = (parseFloat (data [id] .price) / 100) .toFixed (2);}); res.render (‘admin-items’, {pagetitle: ‘Edit Items’, items: data});}) ;});
/ * МАРШРУТ: / admin / update / item * /
router.post (‘/ update / item’, function (req, res, next) {cb = function () {res.redirect (‘/ admin / items’);}; если (req.body.action == » delete «) {dataModel.deleteRow (» items «, req.body.id, cb);} else {/ * req.body.price, например, 5,00 * / / * Преобразовать, например, в 500 для хранения базы данных * / / * если пользовательский ввод более точен, чем пенни, например, 5,005 * / / * тогда используйте Math.floor, чтобы отбросить эти данные * / req.body.price = Math.floor (parseFloat (req.body.price) * 100); dataModel.updateRow («элементы», req.body, cb);}});
/ * МАРШРУТ: / admin / create / item * /
router.post (‘/ create / item’, function (req, res, next) {dataModel.createRow («items», function () {res.redirect (‘/ admin / items # footer’);});} );
/ * МАРШРУТ: / admin / events * /
router.get (‘/ events’, function (req, res, next) {dataModel.readTable («events», function (data) {res.render (‘admin-events’, {pagetitle: ‘Edit Events’, events) : данные }); }); });
/ * МАРШРУТ: / admin / update / event * /
router.post (‘/ update / event’, function (req, res, next) {let cb = function () {res.redirect (‘/ admin / events’);}; если (req.body.action == «удалить») {dataModel.deleteRow («события», req.body.id, cb);} else {dataModel.updateRow («события», req.body, cb);}});
/ * МАРШРУТ: / admin / create / event * /
router.post (‘/ create / event’, function (req, res, next) {dataModel.createRow («events», function () {res.redirect (‘/ admin / events # footer’);});}});} );
/ * МАРШРУТ: / admin / motd * /
router.get (‘/ motd’, function (req, res, next) {dataModel.readTable («motd», function (data) {res.render (‘admin-motd’, {pagetitle: ‘Edit MOTD’, motd) : данные }); }); });
/ * МАРШРУТ: / admin / update / motd * /
router.post (‘/ update / motd’, function (req, res, next) {cb = function () {res.redirect (‘/ admin / motd’);}; если (req.body.action == » удалить «) {dataModel.deleteRow (» motd «, req.body.id, cb);} else {dataModel.updateRow (» motd «, req.body, cb);}});
/ * МАРШРУТ: / admin / create / motd * /
router.post (‘/ create / motd’, function (req, res, next) {dataModel.createRow («motd», function () {res.redirect (‘/ admin / motd’);});});
module.exports = маршрутизатор;

/routes/advice.js

var express = require (‘экспресс’); var router = express.Router ();
router.get (‘/’, функция (req, res, next) {res.render (‘advice’, {pagetitle: ‘Homesteading Advice’});});
module.exports = маршрутизатор;

/routes/contact.js

var express = require (‘экспресс’); var router = express.Router ();
router.get (‘/’, функция (req, res, next) {res.render (‘contact’, {pagetitle: ‘Свяжитесь с нами’});});
module.exports = маршрутизатор;

/routes/index.js

/ * index.js — логика для маршрута / (домашняя страница сайта) * /
var express = require (‘экспресс’); var router = express.Router (); var dataModel = require (‘../ models / data-model.js’);
/ * Следующие четыре функции получают данные и передают их как часть req obj * /
функция getAnnouncements (req, res, next) {req.announce = {}; dataModel.readTable («анонс», функция (данные) {req.announce = data; next ();}); }
функция getItems (req, res, next) {req.items = {}; dataModel.readTable («items», function (data) {Object.keys (data) .forEach (function (id) {let price = data [id] .price; if (price% 100) {/ * if price is not вся сумма в долларах * / price = (parseInt (price) / 100) .toFixed (2); / * включать центы * /} else {price = (parseInt (price) / 100); / * в противном случае опускать десятичные * /} данные [id] .price = price;}); req.items = data; next ();}); }
функция getEvents (req, res, next) {dataModel.readTable («события», функция (данные) {req.events = data; next ();}); }
функция getMotd (req, res, next) {dataModel.readTable («motd», function (data) {req.motd = data; next ();}); }
/ * Получение данных, отображение домашней страницы * /
function renderPage (req, res) {res.render (‘index’, {pagetitle: «Наша ферма», объявления: req.announce, items: req.items, events: req.events, motd: req.motd}) ; }
router.get (‘/’, getAnnouncements, getItems, getEvents, getMotd, renderPage);
module.exports = маршрутизатор;

/routes/recipes.js

var express = require (‘экспресс’); var router = express.Router ();
router.get (‘/’, функция (req, res, next) {res.render (‘recipes’, {pagetitle: ‘Recipes’});});
module.exports = маршрутизатор;

/routes/tips.js

var express = require (‘экспресс’); var router = express.Router ();
router.get (‘/’, функция (req, res, next) {res.render (‘советы’, {pagetitle: ‘Полезные советы’});});
module.exports = маршрутизатор;

/views/about.pug

расширяет макет
блокировать mainbody p Алиса &amp; Боб работает на своей ферме с 1992 года..

/views/admin-announce.pug

расширяет админ-макет
block mainbody .buttons h1 Объявления p форма (method = ‘POST’ action = ‘/ admin / create / announce’) button.add (type = «submit») .icon &плюс; p Новое каждое объявление в форме часов объявлений (method = ‘POST’ action = ‘/ admin / update / announce’) table tr th.label # {announce [‘id’]} th # {announce [‘title’]} каждый значение, поле в объявлении, если поле! = ‘id’ — var inputId = «_» + announce [‘id’] + «_» + field; — var useToday = «document.getElementById (‘_» + объявить [‘ id ‘] — useToday + = «_» + field + «‘) .value = todayString ()»; tr td.label: label (for = field) # {field}: if field == ‘body’ td: textarea (name = field id = inputId) = announce [field] else td.buttons if field == ‘date’ input (type = «text», size = 18, name = field, value = announce [field] id = inputId) p button (type = «button» onclick = useToday) div today else input (type = «text», size = 28, имя = поле, значение = анонс [поле] id = inputId) tr.norule td: input (type = «hidden» name = «id» value = объявление [‘id’]) td.buttons button.update ( type = «submit» name = «update») .icon &проверить; p Обновить .space button.delete (name = «action» value = «delete» onclick = «return confirm (‘Вы уверены?’)»). icon X p Удалить p

/views/admin-events.pug

расширяет админ-макет
block mainbody .buttons h1 Events p form (method = ‘POST’ action = ‘/ admin / create / event’) button.add (type = «submit») .icon &плюс; p Новое каждое событие в форме часов событий (method = ‘post’ action = ‘/ admin / update / event’) table tr th.label # {event [‘id’]} th # {event [‘title’]} каждое значение, поле в событии if field! = ‘id’ — var inputId = «_» + event [‘id’] + «_» + field; — var useToday = «document.getElementById (‘_» + событие [‘ id ‘] — useToday + = «_» + поле + «‘) .value = todayString ()»; tr td.label: label (for = field) # {field}: if field == ‘body’ td: textarea (name = field id = inputId) = event [field] else td.buttons if field == ‘date’ input (type = «text», size = 18, name = field, value = event [field] id = inputId) p button (type = «button» onclick = useToday) div сегодня еще input (type = «text», size = 28, name = field, value = event [field] id = inputId) tr.norule td: input (type = «hidden» name = «id» value = event [‘id’]) td.buttons button.update ( type = «submit» name = «update») .icon &проверить; p Обновить .space button.delete (name = «action» value = «delete» onclick = «return confirm (‘Вы уверены?’)»). icon X p Удалить p

/views/admin-items.pug

расширяет админ-макет
блокировать mainbody .buttons h1 Items p form (method = ‘POST’ action = ‘/ admin / create / item’) button.add (type = «submit») .icon &плюс; p Новый каждый элемент в форме hr элементов (method = ‘POST’ action = ‘/ admin / update / item’) table tr th.label # {item [‘id’]} th # {item [‘name’]} каждый значение, поле в элементе if field! = ‘id’ tr td.label: label (for = field) # {field}: if field == ‘desc’ td: textarea (name = field) = item [field] else td : input (type = «text», size = 25, name = field, value = item [field]) tr.norule td: input (type = «hidden» name = «id» value = item [‘id’]) td.buttons button.update (type = «submit» name = «update») .icon &проверить; p Обновить .space button.delete (name = «action» value = «delete» onclick = «return confirm (‘Вы уверены?’)»). icon X p Удалить p

/views/admin-layout.pug

doctype html html head title = pagetitle meta (charset = «utf-8») meta (name = «viewport» content = «width = device-width, initial-scale = 1») script (src = «/ javascripts / menu. js «) скрипт (src =» / javascripts / datestring.js «) ссылка (rel =» stylesheet «, href =» / stylesheets / style.css «) body # раздел меню # элементы меню .menubutton h1.menubutton (onclick =» menuToggle (‘скрыть’) «) &# 9776; p a (href = «/ админ / анонс») h3.sectrule &# 9998; Объявить (href = «/ admin / items») h3 &# 9998; Элементы a (href = «/ admin / events») h3 &# 9998; События a (href = «/ admin / motd») h3 &# 9998; Сообщение a (href = «/») h3.sectrule &rarr; Перейдите на сайт #container (onmouseup = «menuToggle (‘hide’)») #header a (href = «/ admin») h1 (style = «padding-left: 0») Панель управления .headspace h1.menubutton (onclick = «menuToggle (‘показать’)») &# 9776; h1.placeholder &# 9776; a (href = «/») h2.navitem &rarr; Перейдите на сайт # panel.left section # section a.sectionlink (href = «/ admin / announce») p &# 9998; Объявить a.sectionlink (href = «/ admin / items») p &# 9998; Элементы a.sectionlink (href = «/ admin / events») p &# 9998; События a.sectionlink (href = «/ admin / motd») p &# 9998; Сообщение #mainbody block mainbody # panel.right #footer p Авторские права &копия; 2020 Алиса &amp; Ферма Боба

/views/admin-motd.pug

расширяет админ-макет
block mainbody .buttons h1 Сообщение дня p форма (method = ‘POST’ action = ‘/ admin / create / motd’) button.add (type = «submit») .icon &плюс; p Новое каждое сообщение в форме motd hr (method = ‘POST’ action = ‘/ admin / update / motd’) table tr th.label # {message [‘id’]} th # {message [‘title’]} каждое значение, поле в сообщении, если поле! = ‘id’ tr td.label: label (for = field) # {field}: if field == ‘body’ td: textarea (name = field) = message [field] else td : input (type = «text», size = 30, name = field, value = message [field]) tr.norule td: input (type = «hidden» name = «id» value = message [‘id’]) td.buttons button.update (type = «submit» name = «update») .icon &проверить; p Обновить .space button.delete (name = «action» value = «delete» onclick = «return confirm (‘Вы уверены?’)»). icon X p Удалить p

/views/admin.pug

расширяет админ-макет
block mainbody h3 Добро пожаловать в панель управления p Выберите раздел для редактирования.

/views/contact.pug

расширяет макет
блокировать mainbody h3 Алиса &amp; Bob p 1344 Chattanooga Way p Homestead, VT 05401 p (802) 555-5555

/views/index.pug

расширяет макет
блокировать mainbody h3 Объявления .indent .announceitem каждое объявление в объявлениях, если не указано объявление .title == «» h4.title # {announce.title} p.date # {announce.date} p # {announce.body} h3 Предметы для продажи. таблица отступов tr th Item th Описание th Цена th.qty Количество каждого элемента в элементах, если только item.name == «» tr td # {item.name} td # {item.desc} td.price $ # {item.price} span.perunit / # {item.unit} td.qty # {item.qty} h3 Ближайшие события каждое событие в events, если только event.title == «» .eventitem.indent h4.title # {event.title} p.date # {event.date} p # {event.body} каждое сообщение в motd, если только message.title == «» h3 # {message.title} .motditem.indent p # {message.body} h3 # partners.wide Наши друзья .indent section # partners.wide a.partnerlink.wide (hre f = «») p Green Valley Greens a.partnerlink.wide (href = «/») p Кленовый сироп Берта a.partnerlink.wide (href = «») p Turkey Hill Farm a.partnerlink.wide (href = «» ) p Только органические семена .bodyspace

/views/layout.pug

doctype html html head title = pagetitle meta (charset = «utf-8») meta (name = «viewport» content = «width = device-width, initial-scale = 1») script (src = «/ javascripts / menu. js «) ссылка (rel =» stylesheet «, href =» / stylesheets / style.css «) body #menu section # menuitems .menubutton h1.menubutton (onclick =» menuToggle (‘hide’) «) &# 9776; a (href = «/») h3.menuhead Наша ферма Стенд a (href = «/ tips») h3.sectrule Советы для хорошей жизни a (href = «/ recipes») h3 Рецепты a (href = «/ advice») h3 Рекомендации по размещению a (href = «/ about») h3.sectrule О нас a (href = «/ contact») h3 Свяжитесь с нами #container (onmouseup = «menuToggle (‘hide’)») #header a (href = » / «) h1.logo Наша ферма .headspace h1.menubutton (onclick =» menuToggle (‘show’) «) &# 9776; h1.placeholder &# 9776; h2.navitem a (href = «/ about») .clickable-area О нас h2.navitem a (href = «/ contact») .clickable-area Связаться с нами # panel.left section # section a.sectionlink (href = » / tips «) p Советы для хорошей жизни a.sectionlink (href =» / recipes «) p Рецепты a.sectionlink (href =» / advice «) p Советы по усадьбе #mainbody section block mainbody # panel.right h3 Раздел наших друзей # partners.tall a.partnerlink (href = «/») p Green Valley Greens a.partnerlink (href = «/») p Ферма Turkey Hill a.partnerlink (href = «/») p Кленовый сироп Берта a.partnerlink (href = «/») p Только органические семена #footer p Авторские права &копия; 2020 Алиса &amp; Ферма Боба

/views/recipes.pug

расширяет макет
block mainbody h3 Alice’s Recipes .indent pb Без замеса голландского хлеба на следующий день .indent ингредиент 1/4 чайной ложки активных сухих дрожжей ингредиент 3 чашки универсальной муки ингредиент 1 1/2 чайной ложки соли ингредиент Кукурузная мука или пшеничные отруби для посыпки p В большой миске растворите дрожжи в воде. p Добавьте муку и соль, помешивая, пока не получится однородная смесь. p Закройте чашу. Дать отдохнуть не менее 8 часов, лучше от 12 до 18, при комнатной температуре около 70 градусов. p Когда поверхность теста покрывается пузырями, оно готово к складыванию. Слегка посыпьте мукой рабочую поверхность. Присыпать тесто мукой и один или два раза переложить на него. Неплотно накройте и дайте постоять около 15 минут. p Используя столько муки, чтобы тесто не прилипало, аккуратно сформируйте из него шар. Обильно смазать чистое кухонное полотенце мукой, пшеничными отрубями или кукурузной мукой. Выложите на полотенце шовную сторону теста. Накройте другим полотенцем и дайте подняться на 1-2 часа. p Нагрейте духовку до 475&град ;. Накрыть крышкой и запекать 30 минут..

/views/tips.pug

расширяет макет
block mainbody h3 Советы Алисы p Всегда вставайте до восхода солнца. p Никогда не используйте поддельный кленовый сироп. p Если медведь черный, будь громким, нападай. p Если медведь коричневый, прикидывайся мертвым, ложись.

Запуск приложения

В каталоге приложения запустите приложение в режиме разработки.

npm запустить devstart

Приложение обслуживается в локальной сети через порт 3000. Чтобы просмотреть сайт в локальной сети, откройте веб-браузер, чтобы айпи адрес: 3000, куда айпи адрес это айпи адрес компьютера, на котором запущено приложение Node. Если вы просматриваете компьютер, на котором запущено приложение, вы можете использовать адрес локальный: 3000.

Кончик

Если вы не знаете, какой у компьютера IP-адрес, см. Как узнать мой IP-адрес.

Домашняя страница приложения

Чтобы просмотреть панель управления, перейдите в локальный: 3000 / администратор, или же айпи адрес: 3000 / админ. Выберите раздел, чтобы перейти на страницу его редактирования.

Панель управления приложением

Чтобы изменить информацию, отредактируйте текст в любом поле, затем щелкните или коснитесь Обновлять кнопка прямо под ним. Если вы измените несколько полей, все они будут обновлены..

Обновление базы данных

Новая информация сразу видна, если вы перезагрузите домашнюю страницу..

Домашняя страница обновлена

Чтобы добавить новый элемент, щелкните или коснитесь Новый кнопка.

Вставить строку

В новом элементе нет данных, но запись «живая». Он бы отобразился на домашней странице, если бы заголовок не был пустым (если не указано объявление .title в index.pug).

Добавить данные

Добавьте данные и щелкните или коснитесь Обновлять. Новое объявление видно на главной странице.

Новые данные на главной странице

Чтобы удалить элемент, щелкните значок Удалить кнопку, затем щелкните Ok чтобы подтвердить. (Удаленные элементы исчезают навсегда. Это приложение не сохраняет удаленные элементы и не реализует функцию отмены.)

Подтвердите удаление

Дополнительные примечания

  • Это приложение не выполняет фильтрацию или проверку входных данных. Он работает просто дезинфекция путем экранирования всего ввода и отмены экранирования данных перед передачей их механизму просмотра.
  • Если вы инициализируете файл базы данных во время работы приложения, nodemon не обнаружит автоматически новый дескриптор файла. В nodemon введите RS и нажмите Enter, чтобы перезапустить приложение принудительно. Или нажмите Ctrl + C чтобы убить nodemon (и приложение), затем запустите npm запустить devstart опять таки. Любой из них заставит приложение обнаруживать обновленный дескриптор файла базы данных.
  • Это приложение использует три адаптивных представления. На рабочем столе показаны обе панели. В альбомном режиме отображается левая панель, но правая панель скрыта. В портретном режиме обе панели скрыты, а кнопка меню активна..
  • Когда меню открыто в портретной ориентации, щелчок или касание за пределами меню закрывает его..