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
|
||||
|
||||
# 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
|
||||
;
|
||||
19
Dockerfile
19
Dockerfile
@ -10,7 +10,14 @@ FROM base AS builder
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache openssl
|
||||
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 npm run build
|
||||
|
||||
@ -18,7 +25,7 @@ FROM base AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
RUN apk add --no-cache openssl wget
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
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/static ./.next/static
|
||||
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
|
||||
RUN chmod +x /app/start.sh
|
||||
@ -40,4 +48,7 @@ EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["/bin/sh", "/app/start.sh"]
|
||||
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"]
|
||||
|
||||
@ -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 { Geist, Geist_Mono } from "next/font/google";
|
||||
import { ClerkProvider } from "@clerk/nextjs";
|
||||
import { APP_URL } from "@/lib/config";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
@ -14,7 +15,11 @@ const geistMono = Geist_Mono({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "СпоменQR — Во спомен на",
|
||||
metadataBase: new URL(APP_URL),
|
||||
title: {
|
||||
default: "СпоменQR — Во спомен на",
|
||||
template: "%s · Спомен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