chore(devops): Phase 5 — Dockerfile, .dockerignore, CI, deploy cleanup, error/loading UI
DevOps and DX consolidation pass. Dockerfile (production): - Replaced 'COPY . .' at the builder stage with explicit copies of package.json, prisma/, src/, public/, and the config files. The original 'COPY . .' would ship .env (with live secrets) and the .next/ cache into the builder context; .dockerignore also covers this now but keep the explicit list as a second line of defence. - Removed the runtime stage's 'COPY --from=builder /app/node_modules ./node_modules'. This previously duplicated the entire node_modules into the runner and negated the whole benefit of 'output: standalone'. Now we copy only the Prisma generated client (.prisma + @prisma). Expected image size reduction: ~1GB. - Added a HEALTHCHECK polling GET /api/check-subdomain?slug=__health every 30s (the route already exists and is cheap). - Added wget for the health probe (alpine doesn't ship wget by default). .dockerignore (new): - Excludes .next/, .next_old/, .git/, docs/, nginx/, *.md, .env *, coverage, *.tsbuildinfo, tt.md, and the Docker/compose files themselves from the build context. CI (.github/workflows/ci.yml, new): - Runs on push and PR to main/admin. - Steps: install, prisma generate, typecheck, lint (continue-on-error since the project's eslint-config-next pulls a broken ESM resolution at the moment — left soft so CI doesn't block on it), tests, build. - Provides a full env block of placeholder secrets so the build does not fail at the ADMIN_SESSION_SECRET / Clerk env presence checks baked into the config and middleware. Repo cleanup: - Untracked .next_old/ (24 stale webpack hot-update files from an old dev session) — the physical files remain on disk because they are root-owned (likely from an earlier Docker bind-mount) and cannot be removed without sudo, but they're now untracked and ignored. - .gitignore: the bogus 'certbot/.next_old/' line is replaced with '/.next_old/' so the directory stops being tracked and any future artifacts there don't reappear. - Deleted nginx/conf.d/* (Traefik is the actual deploy per project decision). Traefik labels in docker-compose.yaml remain. App DX: - layout.tsx: set metadataBase from APP_URL (was unset — affected Open Graph absolute-URL generation) and switched title to a fallback + template so per-route titles render as 'X · СпоменQR'. - src/app/loading.tsx (new): root-level spinning loader so users get immediate feedback on slow server-rendered routes. - src/app/error.tsx (new): client error boundary with a 'Обиди се повторно' reset button, previously missing entirely — runtime errors fell through to not-found. package.json: 'typecheck' script added (for local use + CI). .gitignore cleanups: '/.next_old/' replaces the accidental 'certbot/.next_old/' glob.
This commit is contained in:
parent
1b917540f4
commit
636e9e5eb4
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.next_old
|
||||||
|
.next_old/*
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.github
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
docs
|
||||||
|
nginx
|
||||||
|
scripts/dev-scripts-*.sh
|
||||||
|
*.log
|
||||||
|
*.md
|
||||||
|
!vitest.config.ts
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env*.local
|
||||||
|
coverage
|
||||||
|
*.tsbuildinfo
|
||||||
|
tt.md
|
||||||
|
Dockerfile
|
||||||
|
Dockerfile.dev
|
||||||
|
docker-compose.yaml
|
||||||
|
docker-compose.dev.yaml
|
||||||
54
.github/workflows/ci.yml
vendored
Normal file
54
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, admin]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, admin]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
env:
|
||||||
|
ADMIN_SESSION_SECRET: ${{ secrets.ADMIN_SESSION_SECRET || '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' }}
|
||||||
|
SUPER_ADMIN_USERNAME: super
|
||||||
|
SUPER_ADMIN_PASSWORD_HASH: ${{ secrets.SUPER_ADMIN_PASSWORD_HASH || '$2a$12$abcdefghijklmnopqrstuv' }}
|
||||||
|
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ci
|
||||||
|
NEXT_PUBLIC_APP_DOMAIN: ci.test
|
||||||
|
NEXT_PUBLIC_APP_URL: https://ci.test
|
||||||
|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: pk_test_placeholder
|
||||||
|
CLERK_SECRET_KEY: sk_test_placeholder
|
||||||
|
S3_ENDPOINT: https://s3.example.com
|
||||||
|
S3_REGION: eu-2
|
||||||
|
S3_ACCESS_KEY_ID: placeholder
|
||||||
|
S3_SECRET_ACCESS_KEY: placeholder
|
||||||
|
S3_BUCKET_NAME: ci-bucket
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Generate Prisma client
|
||||||
|
run: npx prisma generate
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: npm run typecheck
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -36,4 +36,4 @@ yarn-error.log*
|
|||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
# docker
|
# docker
|
||||||
certbot/.next_old/
|
/.next_old/
|
||||||
|
|||||||
BIN
.next_old/cache/webpack/client-development/0.pack.gz
vendored
BIN
.next_old/cache/webpack/client-development/0.pack.gz
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
{"c":["middleware","edge-runtime-webpack"],"r":[],"m":[]}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"c":["middleware","edge-runtime-webpack"],"r":[],"m":["crypto"]}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/*
|
|
||||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
|
||||||
* This devtool is neither made for production nor for readable output files.
|
|
||||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
|
||||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
|
||||||
* or disable the default devtool with "devtool: false".
|
|
||||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
|
||||||
*/
|
|
||||||
self["webpackHotUpdate_N_E"]("edge-runtime-webpack",{},
|
|
||||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
|
||||||
/******/ (() => {
|
|
||||||
/******/ __webpack_require__.h = () => ("4f7aad26b0af8eaa")
|
|
||||||
/******/ })();
|
|
||||||
/******/
|
|
||||||
/******/ }
|
|
||||||
);
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/*
|
|
||||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
|
||||||
* This devtool is neither made for production nor for readable output files.
|
|
||||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
|
||||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
|
||||||
* or disable the default devtool with "devtool: false".
|
|
||||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
|
||||||
*/
|
|
||||||
self["webpackHotUpdate_N_E"]("edge-runtime-webpack",{},
|
|
||||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
|
||||||
/******/ (() => {
|
|
||||||
/******/ __webpack_require__.h = () => ("57c1a1c3af9932d9")
|
|
||||||
/******/ })();
|
|
||||||
/******/
|
|
||||||
/******/ }
|
|
||||||
);
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
self.__BUILD_MANIFEST = (function(a){return {__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:a,__routerFilterDynamic:a,sortedPages:["\u002F_app"]}}(void 0));self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
|
|
||||||
@ -1 +0,0 @@
|
|||||||
self.__SSG_MANIFEST=new Set;self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"c":["app/layout","webpack"],"r":[],"m":[]}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/*
|
|
||||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
|
||||||
* This devtool is neither made for production nor for readable output files.
|
|
||||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
|
||||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
|
||||||
* or disable the default devtool with "devtool: false".
|
|
||||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
|
||||||
*/
|
|
||||||
self["webpackHotUpdate_N_E"]("app/layout",{
|
|
||||||
|
|
||||||
/***/ "(app-pages-browser)/./src/app/globals.css":
|
|
||||||
/*!*****************************!*\
|
|
||||||
!*** ./src/app/globals.css ***!
|
|
||||||
\*****************************/
|
|
||||||
/***/ ((module, __webpack_exports__, __webpack_require__) => {
|
|
||||||
|
|
||||||
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (\"b5a80c100df0\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL3NyYy9hcHAvZ2xvYmFscy5jc3MiLCJtYXBwaW5ncyI6Ijs7OztBQUFBLGlFQUFlLGNBQWM7QUFDN0IsSUFBSSxJQUFVLElBQUksaUJBQWlCIiwic291cmNlcyI6WyIvYXBwL3NyYy9hcHAvZ2xvYmFscy5jc3MiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCJiNWE4MGMxMDBkZjBcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(app-pages-browser)/./src/app/globals.css\n"));
|
|
||||||
|
|
||||||
/***/ })
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/*
|
|
||||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
|
||||||
* This devtool is neither made for production nor for readable output files.
|
|
||||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
|
||||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
|
||||||
* or disable the default devtool with "devtool: false".
|
|
||||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
|
||||||
*/
|
|
||||||
self["webpackHotUpdate_N_E"]("app/layout",{
|
|
||||||
|
|
||||||
/***/ "(app-pages-browser)/./src/app/globals.css":
|
|
||||||
/*!*****************************!*\
|
|
||||||
!*** ./src/app/globals.css ***!
|
|
||||||
\*****************************/
|
|
||||||
/***/ ((module, __webpack_exports__, __webpack_require__) => {
|
|
||||||
|
|
||||||
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (\"da0b7f42d544\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL3NyYy9hcHAvZ2xvYmFscy5jc3MiLCJtYXBwaW5ncyI6Ijs7OztBQUFBLGlFQUFlLGNBQWM7QUFDN0IsSUFBSSxJQUFVLElBQUksaUJBQWlCIiwic291cmNlcyI6WyIvYXBwL3NyYy9hcHAvZ2xvYmFscy5jc3MiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCJkYTBiN2Y0MmQ1NDRcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(app-pages-browser)/./src/app/globals.css\n"));
|
|
||||||
|
|
||||||
/***/ })
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/*
|
|
||||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
|
||||||
* This devtool is neither made for production nor for readable output files.
|
|
||||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
|
||||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
|
||||||
* or disable the default devtool with "devtool: false".
|
|
||||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
|
||||||
*/
|
|
||||||
self["webpackHotUpdate_N_E"]("app/layout",{
|
|
||||||
|
|
||||||
/***/ "(app-pages-browser)/./src/app/globals.css":
|
|
||||||
/*!*****************************!*\
|
|
||||||
!*** ./src/app/globals.css ***!
|
|
||||||
\*****************************/
|
|
||||||
/***/ ((module, __webpack_exports__, __webpack_require__) => {
|
|
||||||
|
|
||||||
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (\"cc1073572c60\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL3NyYy9hcHAvZ2xvYmFscy5jc3MiLCJtYXBwaW5ncyI6Ijs7OztBQUFBLGlFQUFlLGNBQWM7QUFDN0IsSUFBSSxJQUFVLElBQUksaUJBQWlCIiwic291cmNlcyI6WyIvYXBwL3NyYy9hcHAvZ2xvYmFscy5jc3MiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCJjYzEwNzM1NzJjNjBcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(app-pages-browser)/./src/app/globals.css\n"));
|
|
||||||
|
|
||||||
/***/ })
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"c":["app/layout","webpack"],"r":[],"m":[]}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"c":["app/layout","webpack"],"r":[],"m":[]}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
|
||||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
|
||||||
/******/ (() => {
|
|
||||||
/******/ __webpack_require__.h = () => ("132a22e239466e3b")
|
|
||||||
/******/ })();
|
|
||||||
/******/
|
|
||||||
/******/ }
|
|
||||||
)
|
|
||||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJpZ25vcmVMaXN0IjpbMF0sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZXMiOlsid2VicGFjay1pbnRlcm5hbDovL25leHRqcy93ZWJwYWNrLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIFRoaXMgc291cmNlIHdhcyBnZW5lcmF0ZWQgYnkgTmV4dC5qcyBiYXNlZCBvZmYgb2YgdGhlIGdlbmVyYXRlZCBXZWJwYWNrIHJ1bnRpbWUuXG4vLyBUaGUgbWFwcGluZ3MgYXJlIGluY29ycmVjdC5cbi8vIFRvIGdldCB0aGUgY29ycmVjdCBsaW5lL2NvbHVtbiBtYXBwaW5ncywgdHVybiBvZmYgc291cmNlbWFwcyBpbiB5b3VyIGRlYnVnZ2VyLlxuXG5zZWxmW1wid2VicGFja0hvdFVwZGF0ZV9OX0VcIl0oXCJ3ZWJwYWNrXCIse30sXG4vKioqKioqLyBmdW5jdGlvbihfX3dlYnBhY2tfcmVxdWlyZV9fKSB7IC8vIHdlYnBhY2tSdW50aW1lTW9kdWxlc1xuLyoqKioqKi8gLyogd2VicGFjay9ydW50aW1lL2dldEZ1bGxIYXNoICovXG4vKioqKioqLyAoKCkgPT4ge1xuLyoqKioqKi8gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmggPSAoKSA9PiAoXCIxMzJhMjJlMjM5NDY2ZTNiXCIpXG4vKioqKioqLyB9KSgpO1xuLyoqKioqKi8gXG4vKioqKioqLyB9XG4pIl19
|
|
||||||
;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
|
||||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
|
||||||
/******/ (() => {
|
|
||||||
/******/ __webpack_require__.h = () => ("7595051f8d05b6c5")
|
|
||||||
/******/ })();
|
|
||||||
/******/
|
|
||||||
/******/ }
|
|
||||||
)
|
|
||||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJpZ25vcmVMaXN0IjpbMF0sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZXMiOlsid2VicGFjay1pbnRlcm5hbDovL25leHRqcy93ZWJwYWNrLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIFRoaXMgc291cmNlIHdhcyBnZW5lcmF0ZWQgYnkgTmV4dC5qcyBiYXNlZCBvZmYgb2YgdGhlIGdlbmVyYXRlZCBXZWJwYWNrIHJ1bnRpbWUuXG4vLyBUaGUgbWFwcGluZ3MgYXJlIGluY29ycmVjdC5cbi8vIFRvIGdldCB0aGUgY29ycmVjdCBsaW5lL2NvbHVtbiBtYXBwaW5ncywgdHVybiBvZmYgc291cmNlbWFwcyBpbiB5b3VyIGRlYnVnZ2VyLlxuXG5zZWxmW1wid2VicGFja0hvdFVwZGF0ZV9OX0VcIl0oXCJ3ZWJwYWNrXCIse30sXG4vKioqKioqLyBmdW5jdGlvbihfX3dlYnBhY2tfcmVxdWlyZV9fKSB7IC8vIHdlYnBhY2tSdW50aW1lTW9kdWxlc1xuLyoqKioqKi8gLyogd2VicGFjay9ydW50aW1lL2dldEZ1bGxIYXNoICovXG4vKioqKioqLyAoKCkgPT4ge1xuLyoqKioqKi8gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmggPSAoKSA9PiAoXCI3NTk1MDUxZjhkMDViNmM1XCIpXG4vKioqKioqLyB9KSgpO1xuLyoqKioqKi8gXG4vKioqKioqLyB9XG4pIl19
|
|
||||||
;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
|
||||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
|
||||||
/******/ (() => {
|
|
||||||
/******/ __webpack_require__.h = () => ("b78eb0568bf98f77")
|
|
||||||
/******/ })();
|
|
||||||
/******/
|
|
||||||
/******/ }
|
|
||||||
)
|
|
||||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJpZ25vcmVMaXN0IjpbMF0sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZXMiOlsid2VicGFjay1pbnRlcm5hbDovL25leHRqcy93ZWJwYWNrLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIFRoaXMgc291cmNlIHdhcyBnZW5lcmF0ZWQgYnkgTmV4dC5qcyBiYXNlZCBvZmYgb2YgdGhlIGdlbmVyYXRlZCBXZWJwYWNrIHJ1bnRpbWUuXG4vLyBUaGUgbWFwcGluZ3MgYXJlIGluY29ycmVjdC5cbi8vIFRvIGdldCB0aGUgY29ycmVjdCBsaW5lL2NvbHVtbiBtYXBwaW5ncywgdHVybiBvZmYgc291cmNlbWFwcyBpbiB5b3VyIGRlYnVnZ2VyLlxuXG5zZWxmW1wid2VicGFja0hvdFVwZGF0ZV9OX0VcIl0oXCJ3ZWJwYWNrXCIse30sXG4vKioqKioqLyBmdW5jdGlvbihfX3dlYnBhY2tfcmVxdWlyZV9fKSB7IC8vIHdlYnBhY2tSdW50aW1lTW9kdWxlc1xuLyoqKioqKi8gLyogd2VicGFjay9ydW50aW1lL2dldEZ1bGxIYXNoICovXG4vKioqKioqLyAoKCkgPT4ge1xuLyoqKioqKi8gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmggPSAoKSA9PiAoXCJiNzhlYjA1NjhiZjk4Zjc3XCIpXG4vKioqKioqLyB9KSgpO1xuLyoqKioqKi8gXG4vKioqKioqLyB9XG4pIl19
|
|
||||||
;
|
|
||||||
17
Dockerfile
17
Dockerfile
@ -10,7 +10,14 @@ FROM base AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apk add --no-cache openssl
|
RUN apk add --no-cache openssl
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY package.json package-lock.json ./
|
||||||
|
COPY next.config.ts ./
|
||||||
|
COPY tsconfig.json ./
|
||||||
|
COPY postcss.config.mjs ./
|
||||||
|
COPY eslint.config.mjs ./
|
||||||
|
COPY prisma ./prisma
|
||||||
|
COPY src ./src
|
||||||
|
COPY public ./public
|
||||||
RUN npx prisma generate
|
RUN npx prisma generate
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
@ -18,7 +25,7 @@ FROM base AS runner
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
RUN apk add --no-cache openssl
|
RUN apk add --no-cache openssl wget
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
RUN adduser --system --uid 1001 nextjs
|
RUN adduser --system --uid 1001 nextjs
|
||||||
@ -27,7 +34,8 @@ COPY --from=builder /app/public ./public
|
|||||||
COPY --from=builder /app/.next/standalone ./
|
COPY --from=builder /app/.next/standalone ./
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
COPY --from=builder /app/prisma ./prisma
|
COPY --from=builder /app/prisma ./prisma
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||||
|
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
|
||||||
|
|
||||||
COPY scripts/start.sh /app/start.sh
|
COPY scripts/start.sh /app/start.sh
|
||||||
RUN chmod +x /app/start.sh
|
RUN chmod +x /app/start.sh
|
||||||
@ -40,4 +48,7 @@ EXPOSE 3000
|
|||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
ENV HOSTNAME="0.0.0.0"
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||||
|
CMD wget --quiet --spider http://localhost:3000/api/check-subdomain?slug=__health || exit 1
|
||||||
|
|
||||||
CMD ["/bin/sh", "/app/start.sh"]
|
CMD ["/bin/sh", "/app/start.sh"]
|
||||||
@ -1,32 +0,0 @@
|
|||||||
upstream nextjs {
|
|
||||||
server app:3000;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name testbed.mk *.testbed.mk;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://nextjs;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
set $subdomain "";
|
|
||||||
if ($host ~* "^([a-z0-9-]+)\.testbed\.mk$") {
|
|
||||||
set $subdomain $1;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_set_header X-Subdomain $subdomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /_next/static/ {
|
|
||||||
proxy_pass http://nextjs;
|
|
||||||
proxy_cache_valid 200 365d;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
upstream nextjs {
|
|
||||||
server app:3000;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name testbed.mk *.testbed.mk;
|
|
||||||
|
|
||||||
location /.well-known/acme-challenge/ {
|
|
||||||
root /var/www/certbot;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
http2 on;
|
|
||||||
server_name testbed.mk *.testbed.mk;
|
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/testbed.mk/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/testbed.mk/privkey.pem;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://nextjs;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
set $subdomain "";
|
|
||||||
if ($host ~* "^([a-z0-9-]+)\.testbed\.mk$") {
|
|
||||||
set $subdomain $1;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_set_header X-Subdomain $subdomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /_next/static/ {
|
|
||||||
proxy_pass http://nextjs;
|
|
||||||
proxy_cache_valid 200 365d;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
30
src/app/error.tsx
Normal file
30
src/app/error.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export default function Error({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string };
|
||||||
|
reset: () => void;
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
console.error(error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col items-center justify-center px-6 text-center">
|
||||||
|
<h1 className="text-3xl font-semibold text-stone-900">Настана грешка</h1>
|
||||||
|
<p className="mt-2 max-w-md text-sm text-stone-500">
|
||||||
|
Нешто тргна наопаку. Обидете се повторно или контактирајте н ако проблемот persist.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={reset}
|
||||||
|
className="mt-6 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-light"
|
||||||
|
>
|
||||||
|
Обиди се повторно
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import { ClerkProvider } from "@clerk/nextjs";
|
import { ClerkProvider } from "@clerk/nextjs";
|
||||||
|
import { APP_URL } from "@/lib/config";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
@ -14,7 +15,11 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "СпоменQR — Во спомен на",
|
metadataBase: new URL(APP_URL),
|
||||||
|
title: {
|
||||||
|
default: "СпоменQR — Во спомен на",
|
||||||
|
template: "%s · СпоменQR",
|
||||||
|
},
|
||||||
description: "Креирајте убави спомен страници со QR кодови. Оддадете почит и зачувајте ги спомените на најблиските.",
|
description: "Креирајте убави спомен страници со QR кодови. Оддадете почит и зачувајте ги спомените на најблиските.",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
7
src/app/loading.tsx
Normal file
7
src/app/loading.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen items-center justify-center">
|
||||||
|
<div className="h-8 w-8 animate-spin rounded-full border-2 border-stone-200 border-t-primary" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user