Files
tfluceecrud/Application.cfc
T
Repinoid d4f42a8490 fix: версия приложения — 1.0.0 (в футере было захардкожено v1.0.2)
Версия была жёстко прописана в разметке index.cfm и расходилась с Flask/Node,
где она берётся из константы VERSION = "1.0.0".

Теперь как у остальных приложений: константа application.appVersion = "1.0.0"
в Application.cfc (создаётся в OnApplicationStart, копируется в request.appVersion
в OnRequestStart), а футер выводит #request.appVersion#.

Проверено: 1.0.1/1.0.2 в репозиториях больше не встречаются; парность тегов
cfoutput 5/5, <div>/</div> 14/14.
2026-10-02 07:30:40 +03:00

202 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!---
Application.cfc — конфигурация Lucee CRUD-приложения
Жизненный цикл:
1. OnApplicationStart — вызывается ОДИН раз при старте приложения
- читает TABLE_NAME из env vars (json_env в Terraform)
- создаёт таблицу CREATE TABLE IF NOT EXISTS
- сохраняет имя таблицы в application.tableName
2. OnRequestStart — вызывается ПЕРЕД каждым запросом
- копирует application.tableName → request.tableName
3. OnRequest — вызывает index.cfm с CRUD-логикой
Datasource: testds, конфигурируется из env vars с префиксом testds_
(testds_class, testds_connectionString, testds_username, testds_password, ...)
Terraform json_env должен содержать:
- TABLE_NAME — имя таблицы (по умолчанию check_point)
- testds_class — JDBC-драйвер
- testds_connectionString — строка подключения к PostgreSQL
- testds_username — имя пользователя БД
- testds_password — пароль пользователя БД
--->
<cfcomponent displayname="Application" output="true">
<!--- Идентификатор приложения для Lucee --->
<cfset this.Name = "tfluceecrud" />
<!--- Таймаут приложения: 3 часа бездействия → перезапуск (вызовет OnApplicationStart заново) --->
<cfset this.applicationTimeout = createTimeSpan(0, 0, 3, 0) />
<!--- Включаем сессии для защиты от CSRF (Lucee автоматически добавляет токены) --->
<cfset this.sessionmanagement = "Yes" />
<!--- Сессия живёт 2 часа неактивности --->
<cfset this.sessiontimeout = CreateTimeSpan(0, 0, 120, 0) />
<!--- Имя datasource по умолчанию — используется в cfquery без атрибута datasource --->
<cfset this.datasource = "testds" />
<!--- Конфигурируем datasource из env vars с префиксом testds_ --->
<cfset getDS(this.datasource, this.datasource) />
<!--- Отключаем отладку на проде, таймаут запроса 20 секунд --->
<cfsetting requesttimeout="20" showdebugoutput="false" />
<!---
OnApplicationStart — выполняется ОДИН раз при старте приложения.
Здесь: создаём таблицу если её нет, сохраняем имя в application scope.
application scope — живёт пока приложение не перезапустится (таймаут 3 часа).
Используем application а не request потому что request умирает после каждого запроса.
--->
<cffunction name="OnApplicationStart" access="public" returntype="boolean">
<!--- 1. Читаем имя таблицы из переменной окружения TABLE_NAME --->
<cfset var tableName = createObject("java", "java.lang.System").getEnv().get("TABLE_NAME") />
<!--- 2. Если переменная не задана в json_env — используем значение по умолчанию --->
<cfif NOT isDefined("tableName") OR NOT len(tableName)>
<cfset tableName = "check_point" />
</cfif>
<!--- 3. Сохраняем в application scope — доступно всем запросам этого приложения --->
<cfset application.tableName = tableName />
<!--- 3a. Имя сервиса — для колонки created_by («кто добавил строку»).
Задаётся в json_env манифеста (flask / lucee / nodejs): три приложения
пишут в ОДНУ таблицу, и по этой метке видно, какое именно добавило. --->
<cfset var serviceName = createObject("java", "java.lang.System").getEnv().get("SERVICE_NAME") />
<cfif NOT isDefined("serviceName") OR NOT len(serviceName)>
<cfset serviceName = "lucee" />
</cfif>
<cfset application.serviceName = serviceName />
<!--- 3b. Версия приложения — единая для всех страниц (index.cfm выводит её в футере).
Раньше версия была захардкожена в разметке и расходилась с Flask/Node (1.0.0). --->
<cfset application.appVersion = "1.0.0" />
<!--- 4. Создаём таблицу если она ещё не существует --->
<!--- id: автоинкрементный первичный ключ --->
<!--- value: текстовое поле для CRUD-значений --->
<!--- created_at / created_by: когда и какое приложение добавило строку --->
<cftry>
<cfquery>
CREATE TABLE IF NOT EXISTS #tableName# (
id SERIAL PRIMARY KEY,
value TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_by TEXT NOT NULL DEFAULT ''
)
</cfquery>
<!--- Досыпаем колонки в таблицу, созданную прошлой версией: данные не теряются --->
<cfquery>
ALTER TABLE #tableName# ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT now()
</cfquery>
<cfquery>
ALTER TABLE #tableName# ADD COLUMN IF NOT EXISTS created_by TEXT NOT NULL DEFAULT ''
</cfquery>
<!--- Игнорируем ошибки: нет прав, БД недоступна --->
<cfcatch type="database"></cfcatch>
</cftry>
<cfreturn true />
</cffunction>
<!---
OnRequestStart — выполняется ПЕРЕД каждым HTTP-запросом.
Здесь: копируем имя таблицы из application в request scope.
request scope — живёт только в течение одного запроса.
Каждая страница (index.cfm) читает request.tableName.
--->
<cffunction name="OnRequestStart" access="public" returntype="boolean">
<!--- Если приложение уже стартовало — берём имя из application --->
<cfif isDefined("application.tableName")>
<cfset request.tableName = application.tableName />
<!--- Иначе (первый запрос до OnApplicationStart) — fallback --->
<cfelse>
<cfset request.tableName = "check_point" />
</cfif>
<!--- Имя сервиса — из application в request (см. OnApplicationStart) --->
<cfif isDefined("application.serviceName")>
<cfset request.serviceName = application.serviceName />
<cfelse>
<cfset request.serviceName = "lucee" />
</cfif>
<!--- Версия приложения — для футера страницы --->
<cfif isDefined("application.appVersion")>
<cfset request.appVersion = application.appVersion />
<cfelse>
<cfset request.appVersion = "1.0.0" />
</cfif>
<cfreturn true />
</cffunction>
<!---
OnRequest — обработчик каждого запроса.
Включает целевой шаблон (index.cfm) с установленной кодировкой.
--->
<cffunction name="OnRequest" access="public" returntype="void">
<cfargument name="template" type="string" required="true" />
<!--- UTF-8 для форм и URL-параметров --->
<cfset setEncoding("FORM", "UTF-8") />
<cfset setEncoding("URL", "UTF-8") />
<!--- Вычисляем web-корень для ссылок --->
<cfset local.basePath = getDirectoryFromPath(getCurrentTemplatePath()) />
<cfset request.webRoot = "../" />
<cfset request.thisPage = Replace(ReplaceNoCase(expandPath(arguments.template), local.basePath, ""), "\", "/") />
<!--- Выполняем запрошенный шаблон (index.cfm) --->
<cfinclude template="#arguments.template#" />
</cffunction>
<!---
getDS — конфигурирует datasource Lucee из переменных окружения.
Соглашение об именах env vars:
{prefix}_{field} → параметр datasource
Пример для testds:
testds_class → JDBC-драйвер
testds_connectionString → jdbc:postgresql://host:5432/db
testds_username → пользователь
testds_password → пароль
Все параметры передаются через json_env в Terraform.
--->
<cffunction name="getDS" access="private" returntype="void">
<cfargument name="dsname" type="string" required="true" hint="Имя datasource (testds)" />
<cfargument name="prefix" type="string" default="#dsname#" hint="Префикс env vars" />
<!--- Java-объект для чтения переменных окружения --->
<cfset var system = createObject("java", "java.lang.System") />
<!--- Словарь параметров datasource --->
<cfset var ds = {} />
<!--- Перебираем все возможные поля datasource --->
<cfloop list="class,connectionString,username,password,bundleName,bundleVersion,connectionLimit,liveTimeout,validate" item="field">
<!--- Читаем env var: {prefix}_{field}, например testds_username --->
<cfset var value = system.getEnv("#arguments.prefix#_#field#") />
<!--- Если переменная задана и не пуста — добавляем в datasource --->
<cfif isDefined("value") AND len(value)>
<cfset structInsert(ds, field, value) />
</cfif>
</cfloop>
<!--- Регистрируем datasource в Lucee --->
<cfset THIS.datasources[dsname] = ds />
</cffunction>
</cfcomponent>