diff --git a/client/Dockerfile b/client/Dockerfile index 47e0746a..d622caf0 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -1,19 +1,26 @@ -FROM node:18-alpine +FROM node:lts AS base +WORKDIR /app -# Set working directory -WORKDIR /opt/app +# By copying only the package.json and package-lock.json here, we ensure that the following `-deps` steps are independent of the source code. +# Therefore, the `-deps` steps will be skipped if only the source code changes. +COPY package.json package-lock.json ./ -# Copy package files -COPY package*.json ./ +FROM base AS prod-deps +RUN npm install --omit=dev -# Install dependencies -RUN npm ci +FROM base AS build-deps +RUN npm install -# Copy source code +FROM build-deps AS build COPY . . +RUN npm run build + +FROM base AS runtime +COPY --from=prod-deps /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist + +ENV HOST=0.0.0.0 +ENV PORT=4321 -# Expose port EXPOSE 4321 - -# Start the development server -CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] \ No newline at end of file +CMD node ./dist/server/entry.mjs \ No newline at end of file diff --git a/client/astro.config.mjs b/client/astro.config.mjs index 508cbece..a49f256b 100644 --- a/client/astro.config.mjs +++ b/client/astro.config.mjs @@ -3,9 +3,16 @@ import { defineConfig } from 'astro/config'; import tailwindcss from '@tailwindcss/vite'; +import node from '@astrojs/node'; + // https://astro.build/config export default defineConfig({ + output: 'server', vite: { plugins: [tailwindcss()] - } + }, + + adapter: node({ + mode: 'standalone' + }) }); \ No newline at end of file diff --git a/client/docker-compose.yml b/client/docker-compose.yml new file mode 100644 index 00000000..2784cb24 --- /dev/null +++ b/client/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + client: + build: + context: . + dockerfile: Dockerfile + container_name: astro-client + restart: unless-stopped + ports: + - "4321:4321" + env_file: + - .env + environment: + NODE_ENV: ${NODE_ENV} + STRAPI_URL: ${STRAPI_URL} + volumes: + - ./src:/opt/app/src + - ./public:/opt/app/public + - ./astro.config.mjs:/opt/app/astro.config.mjs + - ./tsconfig.json:/opt/app/tsconfig.json + networks: + - strapi-network + +networks: + strapi-network: + external: true \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 12fc959d..cd6bedb4 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,7 @@ "name": "client", "version": "0.0.1", "dependencies": { + "@astrojs/node": "^9.2.2", "@tailwindcss/vite": "^4.1.10", "@types/qs": "^6.14.0", "astro": "^5.7.12", @@ -70,6 +71,20 @@ "vfile": "^6.0.3" } }, + "node_modules/@astrojs/node": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.2.2.tgz", + "integrity": "sha512-PtLPuuojmcl9O3CEvXqL/D+wB4x5DlbrGOvP0MeTAh/VfKFprYAzgw1+45xsnTO+QvPWb26l1cT+ZQvvohmvMw==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.6.1", + "send": "^1.2.0", + "server-destroy": "^1.0.1" + }, + "peerDependencies": { + "astro": "^5.3.0" + } + }, "node_modules/@astrojs/prism": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.2.0.tgz", @@ -1433,6 +1448,15 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1532,12 +1556,27 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -1639,6 +1678,12 @@ "@esbuild/win32-x64": "0.25.4" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -1660,6 +1705,15 @@ "@types/estree": "^1.0.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -1728,6 +1782,15 @@ "unicode-trie": "^2.0.0" } }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2060,6 +2123,31 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -2070,6 +2158,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -3274,6 +3368,27 @@ ], "license": "MIT" }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -3435,6 +3550,18 @@ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "license": "MIT" }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/oniguruma-parser": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", @@ -3645,6 +3772,15 @@ "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -3942,6 +4078,40 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -4117,6 +4287,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -4223,6 +4402,15 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", diff --git a/client/package.json b/client/package.json index 93ca0d6a..ddb9c089 100644 --- a/client/package.json +++ b/client/package.json @@ -9,6 +9,7 @@ "astro": "astro" }, "dependencies": { + "@astrojs/node": "^9.2.2", "@tailwindcss/vite": "^4.1.10", "@types/qs": "^6.14.0", "astro": "^5.7.12", diff --git a/client/src/collections/strapiPoleElementsLoader.mjs b/client/src/collections/strapiPoleElementsLoader.mjs deleted file mode 100644 index 44fac7e5..00000000 --- a/client/src/collections/strapiPoleElementsLoader.mjs +++ /dev/null @@ -1,87 +0,0 @@ -import { defineCollection, z } from "astro:content"; -import qs from "qs"; - -// Define a custom content collection that loads data from Strapi -const strapiPoleElementsLoader = defineCollection({ - // Async loader function that fetches data from Strapi API - loader: async () => { - // Get Strapi URL from environment variables or fallback to localhost - const BASE_URL = import.meta.env.STRAPI_URL || "http://localhost:1337"; - const path = "/api/elements"; - const url = new URL(path, BASE_URL); - - // Build query parameters using qs to populate cover image data - url.search = qs.stringify({ - populate: { - mainImage: { - fields: ["url", "alternativeText"], - }, - }, - }); - - try { - // Fetch articles from Strapi - const poleElementsData = await fetch(url.href); - - if (!poleElementsData.ok) { - throw new Error(`Failed to fetch data from Strapi: ${poleElementsData.status} ${poleElementsData.statusText}`); - } - - const response = await poleElementsData.json(); - const { data } = response; - - // Check if data is null or undefined - if (!data) { - throw new Error("No data received from Strapi API - the response was null or undefined"); - } - - // Ensure data is an array - const dataArray = Array.isArray(data) ? data : [data]; - - // Transform the API response into the desired data structure - return dataArray - .filter(item => item !== null && item !== undefined) // Filter out null/undefined items - .map((item) => ({ - id: item.id?.toString() || "", - name: item.name || "", - title: item.name || "", - description: item.description || "", - createdAt: item.createdAt || "", - updatedAt: item.updatedAt || "", - publishedAt: item.publishedAt || "", - mainImage: item.mainImage ? { - id: Number(item.mainImage.id) || 0, - documentId: item.mainImage.documentId || "", - url: item.mainImage.url || "", - alternativeText: item.mainImage.alternativeText || "", - } : { - id: 0, - documentId: "", - url: "", - alternativeText: "", - } - })); - } catch (error) { - console.error("Error loading pole elements from Strapi:", error); - return []; - } - }, - // Define the schema for type validation using Zod - schema: z.object({ - id: z.string(), - name: z.string(), - title: z.string(), - description: z.string(), - createdAt: z.string(), - updatedAt: z.string(), - publishedAt: z.string(), - mainImage: z.object({ - id: z.number(), - documentId: z.string(), - url: z.string(), - alternativeText: z.string(), - }), - }), -}); - -export default strapiPoleElementsLoader; \ No newline at end of file diff --git a/client/src/components/PoleElementsList.astro b/client/src/components/PoleElementsList.astro index ed37698a..10a77a76 100644 --- a/client/src/components/PoleElementsList.astro +++ b/client/src/components/PoleElementsList.astro @@ -1,7 +1,7 @@ --- // Import necessary components and utilities import MarkdownComponent from "./MardownContent.astro"; -import { getStrapiMedia } from "../utils/strapi"; +import { getStrapiMedia } from "../lib/strapi"; import { getStrapiBaseUrl } from "../config/strapi"; import type { HTMLAttributes } from "astro/types"; @@ -18,19 +18,20 @@ const BASE_URL = getStrapiBaseUrl();