From b64d112ea7361e81d868a713496b764bb85f27f2 Mon Sep 17 00:00:00 2001 From: Benjamin Palko Date: Tue, 29 Apr 2025 14:43:13 -0400 Subject: [PATCH 1/3] contact section stubbing, still need SMTP provider --- src/routes/contact/+page.server.ts | 87 ++++++++++++++++++++++++++++++ src/routes/contact/+page.svelte | 67 +++++++++++++---------- 2 files changed, 127 insertions(+), 27 deletions(-) create mode 100644 src/routes/contact/+page.server.ts diff --git a/src/routes/contact/+page.server.ts b/src/routes/contact/+page.server.ts new file mode 100644 index 0000000..b469008 --- /dev/null +++ b/src/routes/contact/+page.server.ts @@ -0,0 +1,87 @@ +import { + MAIL_TRANSPORT_HOST, + MAIL_TRANSPORT_PASS, + MAIL_TRANSPORT_PORT, + MAIL_TRANSPORT_USER, +} from "$env/static/private"; +import { fail } from "@sveltejs/kit"; +import { createTransport } from "nodemailer"; +import type { Actions } from "./$types"; + +export const actions = { + default: async ({ request }) => { + const form = await request.formData(); + + if (!form.has("name")) { + return fail(400, { name: null, missing: true }); + } + + if (!form.has("email")) { + return fail(400, { email: null, missing: true }); + } + + if (!form.has("subject")) { + return fail(400, { subject: null, missing: true }); + } + + if (!form.has("message")) { + return fail(400, { message: null, missing: true }); + } + + const name = form.get("name"); + if (typeof name !== "string") { + return fail(400, { name, incorrect: true }); + } + + const email = form.get("email"); + if (typeof email !== "string") { + return fail(400, { email, incorrect: true }); + } + + const subject = form.get("subject"); + if (typeof subject !== "string") { + return fail(400, { subject, incorrect: true }); + } + + const message = form.get("message"); + if (typeof message !== "string") { + return fail(400, { message, incorrect: true }); + } + + const transport = createTransport({ + host: MAIL_TRANSPORT_HOST, + port: isNaN(Number(MAIL_TRANSPORT_PORT)) + ? 587 + : Number(MAIL_TRANSPORT_PORT), + auth: { + user: MAIL_TRANSPORT_USER, + pass: MAIL_TRANSPORT_PASS, + }, + }); + + try { + const info1 = await transport.sendMail({ + from: "no-reply@benjaminpalko.ca", + to: email, + subject: `Thank you for reaching out`, + text: `Hello ${name}!\n\nThank you for reaching out! I will be sure to get back to you ASAP.\n\nCheers,\nBenjamin`, + }); + + const info2 = await transport.sendMail({ + from: email, + to: "contact@benjaminpalko.ca", + subject: subject, + text: message, + }); + + return { + response: { + accepted: [...info1.accepted, ...info2.accepted], + rejected: [...info1.rejected, ...info2.rejected], + }, + }; + } catch (error) { + console.error(error); + } + }, +} satisfies Actions; diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte index 6be8ebd..dd12b27 100644 --- a/src/routes/contact/+page.svelte +++ b/src/routes/contact/+page.svelte @@ -1,41 +1,54 @@ - -
-
- 😁 -

Hello there friend

-
+
+
+
+ 😁 +

Hello there friend

+
- -
-

Want to chat?

-
Reach out! 🤝
+

Reach out! 🤝

+ + {#if form?.response.rejected.length ?? 0 > 0} + + {:else if form?.response.accepted.length ?? 0 > 0} + + {/if} + +
-
- -
+ + + + +
+ From 86805522085a21d6c89b6a0a67ccc50ca514d454 Mon Sep 17 00:00:00 2001 From: Benjamin Palko Date: Wed, 30 Apr 2025 16:51:46 -0400 Subject: [PATCH 2/3] implemented contact site formatting nodemailer docker --- bun.lock | 58 ++++++++++++--------- devops/.gitignore | 1 + devops/docker-compose.dev.yml | 10 ++++ package.json | 6 ++- src/lib/components/WIP.svelte | 2 +- src/routes/+layout.svelte | 16 +++--- src/routes/contact/+page.svelte | 91 ++++++++++++++++++--------------- 7 files changed, 110 insertions(+), 74 deletions(-) create mode 100644 devops/.gitignore create mode 100644 devops/docker-compose.dev.yml diff --git a/bun.lock b/bun.lock index f4e2a00..3a5f470 100644 --- a/bun.lock +++ b/bun.lock @@ -4,32 +4,34 @@ "": { "name": "benjaminpalko.github.io", "dependencies": { - "lucide-svelte": "latest", + "lucide-svelte": "^0.503.0", + "nodemailer": "^6.10.1", }, "devDependencies": { - "@eslint/compat": "latest", - "@eslint/js": "latest", - "@sveltejs/adapter-node": "latest", - "@sveltejs/kit": "latest", - "@sveltejs/vite-plugin-svelte": "latest", - "@tailwindcss/vite": "latest", - "autoprefixer": "latest", - "daisyui": "latest", - "eslint": "latest", - "eslint-config-prettier": "latest", - "eslint-plugin-svelte": "latest", - "globals": "latest", - "mdsvex": "latest", - "prettier": "latest", - "prettier-plugin-svelte": "latest", - "prettier-plugin-tailwindcss": "latest", - "svelte": "latest", - "svelte-check": "latest", - "tailwindcss": "latest", - "typescript": "latest", - "typescript-eslint": "latest", - "vite": "latest", - "vitest": "latest", + "@eslint/compat": "^1.2.8", + "@eslint/js": "^9.25.1", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.20.7", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@tailwindcss/vite": "^4.1.4", + "@types/nodemailer": "^6.4.17", + "autoprefixer": "^10.4.21", + "daisyui": "^5.0.28", + "eslint": "^9.25.1", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-svelte": "^3.5.1", + "globals": "^16.0.0", + "mdsvex": "^0.12.5", + "prettier": "^3.5.3", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.28.2", + "svelte-check": "^4.1.6", + "tailwindcss": "^4.1.4", + "typescript": "^5.8.3", + "typescript-eslint": "^8.31.0", + "vite": "^6.3.3", + "vitest": "^3.1.2", }, }, }, @@ -228,6 +230,10 @@ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + "@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="], + + "@types/nodemailer": ["@types/nodemailer@6.4.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww=="], + "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], "@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], @@ -508,6 +514,8 @@ "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + "nodemailer": ["nodemailer@6.10.1", "", {}, "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA=="], + "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], @@ -632,6 +640,8 @@ "typescript-eslint": ["typescript-eslint@8.31.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.31.0", "@typescript-eslint/parser": "8.31.0", "@typescript-eslint/utils": "8.31.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ=="], + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unist-util-is": ["unist-util-is@4.1.0", "", {}, "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg=="], "unist-util-stringify-position": ["unist-util-stringify-position@2.0.3", "", { "dependencies": { "@types/unist": "^2.0.2" } }, "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g=="], diff --git a/devops/.gitignore b/devops/.gitignore new file mode 100644 index 0000000..8fce603 --- /dev/null +++ b/devops/.gitignore @@ -0,0 +1 @@ +data/ diff --git a/devops/docker-compose.dev.yml b/devops/docker-compose.dev.yml new file mode 100644 index 0000000..18c751d --- /dev/null +++ b/devops/docker-compose.dev.yml @@ -0,0 +1,10 @@ +services: + postgres: + container_name: postgres + image: postgres + environment: + POSTGRES_DB: ${DATABASE_NAME} + POSTGRES_USER: ${DATABASE_USERNAME} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + volumes: + - ./data:/var/lib/postgresql/data diff --git a/package.json b/package.json index 86e8c0f..fa91b4a 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "type": "module", "scripts": { "dev": "vite dev", + "compose:up": "docker compose -p strapi -f devops/docker-compose.dev.yml up -d ", + "compose:down": "docker compose -p strapi -f devops/docker-compose.dev.yml down", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", @@ -21,6 +23,7 @@ "@sveltejs/kit": "^2.20.7", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tailwindcss/vite": "^4.1.4", + "@types/nodemailer": "^6.4.17", "autoprefixer": "^10.4.21", "daisyui": "^5.0.28", "eslint": "^9.25.1", @@ -40,6 +43,7 @@ "vitest": "^3.1.2" }, "dependencies": { - "lucide-svelte": "^0.503.0" + "lucide-svelte": "^0.503.0", + "nodemailer": "^6.10.1" } } diff --git a/src/lib/components/WIP.svelte b/src/lib/components/WIP.svelte index e4ef588..89048f3 100644 --- a/src/lib/components/WIP.svelte +++ b/src/lib/components/WIP.svelte @@ -40,7 +40,7 @@
{/snippet} -
+
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 22521d4..4a72278 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -24,20 +24,22 @@
-
-
+
+
-
+ {#if current && current.slug !== '/'}

{current.name}

{/if} -
+
{@render children()} -
-
+ +
-
+
diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte index dd12b27..a1bad8a 100644 --- a/src/routes/contact/+page.svelte +++ b/src/routes/contact/+page.svelte @@ -1,54 +1,63 @@ -
-
-
- 😁 -

Hello there friend

+ +
+
+ {#if form?.response?.rejected.length ?? 0 > 0} + + {:else if form?.response?.accepted.length ?? 0 > 0} + + {:else if form?.incorrect} + + {/if}
-

Reach out! 🤝

+

Reach out! 🤝

- {#if form?.response.rejected.length ?? 0 > 0} - - {:else if form?.response.accepted.length ?? 0 > 0} - - {/if} + + + + -
- - - +
+
- - - -
From 52df8db3ade548a35d478b35a7eda12f1c978c23 Mon Sep 17 00:00:00 2001 From: Benjamin Palko Date: Wed, 30 Apr 2025 16:54:34 -0400 Subject: [PATCH 3/3] fix workflows --- .github/workflows/build.yml | 48 ------------------------------------ .github/workflows/deploy.yml | 5 ++++ .github/workflows/pr.yml | 35 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 48 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 91e3a6a..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,48 +0,0 @@ -# Simple workflow for deploying static content to GitHub Pages -name: Deploy static content to Pages - -on: - # Runs on pushes targeting the default branch - pull_request: - branches: ['main'] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: 'pages' - cancel-in-progress: false - -jobs: - # Single deploy job since we're just deploying - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v5 - - uses: oven-sh/setup-bun@v2 - - name: Install - run: bun install - - name: Build - run: bun run build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - # Upload entire repository - path: './dist' - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 452f32e..b26e9eb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,6 +5,11 @@ on: push: branches: - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.sha }} + cancel-in-progress: true + jobs: install: name: Install diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..45d1dd3 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,35 @@ +name: PR Gate + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.sha }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + checks: + name: Checks + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Bun Setup + uses: oven-sh/setup-bun@v2 + with: + bun-version-file: '.tool-versions' + - name: Install + run: bun install + - name: Lint + run: bun lint + - name: Build + run: bun run build + - name: Svelte Check + run: bun check + - name: Test + run: bun run test:unit