Compare commits

...

22 Commits

Author SHA1 Message Date
Levente Orban
984c296725 fix: userId not generated in the first visit 2025-10-20 10:18:20 +02:00
Levente Orban
9acfa08ea8 chore(deps-dev): bump vite from 7.1.2 to 7.1.10 in the npm_and_yarn group across 1 directory 2025-10-20 08:49:24 +02:00
Levente Orban
45cb95f6a8 chore(deps): bump devalue from 5.1.1 to 5.4.1 in the npm_and_yarn group across 1 directory 2025-10-20 08:49:13 +02:00
dependabot[bot]
8426bd5704 chore(deps-dev): bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 7.1.2 to 7.1.10
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.10/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.10
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-19 07:00:04 +00:00
dependabot[bot]
b9833db3bb chore(deps): bump devalue in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [devalue](https://github.com/sveltejs/devalue).


Updates `devalue` from 5.1.1 to 5.4.1
- [Release notes](https://github.com/sveltejs/devalue/releases)
- [Changelog](https://github.com/sveltejs/devalue/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/devalue/compare/v5.1.1...v5.4.1)

---
updated-dependencies:
- dependency-name: devalue
  dependency-version: 5.4.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-19 07:00:03 +00:00
Levente Orban
b3572293ba chore: add support me section to README.md 2025-10-15 15:54:42 +02:00
Levente Orban
c1752efe4b chore: add support me section to README.md 2025-10-15 10:44:41 +02:00
Levente Orban
491d0020bd fix: remove dev omit 2025-10-10 21:58:23 +02:00
Levente Orban
5c1182dc66 fix: remove dev omit 2025-10-10 21:57:17 +02:00
Levente Orban
638b5ff1ca feat: add an option to remove the landing page 2025-10-10 21:37:50 +02:00
Levente Orban
069ca11917 feat: add an option to remove the landing page 2025-10-10 21:32:59 +02:00
Levente Orban
4675fa4623 feat: add an option to remove the landing page 2025-10-10 21:29:18 +02:00
Levente Orban
af88d6462b feat: add an option to remove the landing page 2025-10-10 18:32:28 +02:00
Levente Orban
ef6005e648 feat: add an option to remove the landing page 2025-10-10 18:27:58 +02:00
Levente Orban
11875b4a1e feat: add an option to remove the landing page 2025-10-10 18:11:18 +02:00
Levente Orban
22038f7779 feat: add an option to remove the landing page 2025-10-10 10:42:46 +02:00
Levente Orban
de2cb07a15 feat: add an option to remove the landing page 2025-10-10 10:37:52 +02:00
Levente Orban
ffc29b9c24 feat: add an option to remove the landing page 2025-10-10 10:29:32 +02:00
Levente Orban
4860b9439c feat: add an option to remove the landing page 2025-10-10 10:26:56 +02:00
Levente Orban
d10af13134 feat: add an option to remove the landing page 2025-10-10 10:17:06 +02:00
Levente Orban
c98260efec feat: add an option to remove the landing page 2025-10-10 10:06:34 +02:00
Levente Orban
a40b83c2b3 feat: i18n translation check
feat: i18n translation check
2025-09-29 10:58:35 +02:00
12 changed files with 158 additions and 45 deletions

View File

@@ -13,3 +13,5 @@ DATABASE_URL="postgres://cactoide:cactoide_password@localhost:5432/cactoide_data
APP_VERSION=latest APP_VERSION=latest
PORT=3000 PORT=3000
HOSTNAME=0.0.0.0 HOSTNAME=0.0.0.0
PUBLIC_LANDING_INFO=true

View File

@@ -60,6 +60,8 @@ jobs:
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
build-args: |
PUBLIC_LANDING_INFO=${{ vars.PUBLIC_LANDING_INFO }}
push: true push: true
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
cache-from: type=gha cache-from: type=gha

View File

@@ -28,6 +28,8 @@ jobs:
- name: Build application - name: Build application
run: npm run build run: npm run build
env:
PUBLIC_LANDING_INFO: ${{ vars.PUBLIC_LANDING_INFO }}
- name: Test build output - name: Test build output
run: | run: |

View File

@@ -3,25 +3,27 @@ WORKDIR /app
COPY package*.json . COPY package*.json .
RUN npm ci RUN npm ci
ARG PUBLIC_LANDING_INFO
ENV PUBLIC_LANDING_INFO=$PUBLIC_LANDING_INFO
COPY . . COPY . .
RUN npm run build RUN npm run build
RUN npm prune --production RUN npm prune --production
FROM node:20-alpine FROM node:20-alpine
WORKDIR /app WORKDIR /app
COPY --from=builder /app/build build/ COPY --from=builder /app/build build/
COPY --from=builder /app/node_modules node_modules/ COPY --from=builder /app/node_modules node_modules/
COPY package.json . COPY package.json .
EXPOSE 3000
ENV PORT 3000 ENV PORT 3000
ENV HOSTNAME "0.0.0.0" ENV HOSTNAME "0.0.0.0"
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/healthz || exit 1 CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/healthz || exit 1
EXPOSE 3000
CMD [ "node", "build" ] CMD [ "node", "build" ]

View File

@@ -55,7 +55,18 @@ Your app will be available at `http://localhost:5173`. You can use the Makefile
Use the `database/seed.sql` if you want to populate your database with dummy data. Use the `database/seed.sql` if you want to populate your database with dummy data.
### i18n ### Options
#### 1. Landing page option
Supports a conditional landing page display based on the `PUBLIC_LANDING_INFO` environment variable. If you don't want to show your users the cactoide landing page, just use the `PUBLIC_LANDING_INFO=false` variable. This will automatically remove the landing home page and redirect users to the `/discover` page.
This is useful for:
- Creating a minimal discovery-focused experience
- Customizing the user journey based on deployment environment
#### 2. i18n
There is no proper i18n implemented, we have an `/i18n` folder with specific languages. To use an existing translation, just rename the language code JSON file to `messages.json` and you are ready to go. If you would like to add a new translation (which is really appreciated), just create a new `<language_code>.json` file and add the translations from the `messages.json`. There is no proper i18n implemented, we have an `/i18n` folder with specific languages. To use an existing translation, just rename the language code JSON file to `messages.json` and you are ready to go. If you would like to add a new translation (which is really appreciated), just create a new `<language_code>.json` file and add the translations from the `messages.json`.
@@ -71,6 +82,19 @@ make i18n
make i18n FILE=src/lib/i18n/it.json make i18n FILE=src/lib/i18n/it.json
``` ```
### Support
Cactoide is an open-source project licensed under `AGPL-3.0`. Its growth and development are possible thanks to the amazing support of the community. This project is the result of many late nights, weekends, and after-hours work.
It isnt backed by a big company. Development depends on the support and generosity of people like you. With your help, I can focus more on making Cactoide even better and building tools that make coding more enjoyable.
You can support in a few ways:
- Send a one-time donation via [paypal.me/zenoazurben](paypal.me/zenoazurben)
- Reach me directly: leventeorb[@]gmail.com
If you enjoy using Cactoide, or if your business depends on it, please consider sponsoring its development. Your support keeps the project alive, improves it for everyone, and helps create educational content like blog posts and videos for the whole Cactoide community.
### License ### License
This project is licensed under the `AGPL-3.0 License` - see the [LICENSE](./LICENSE) file for details. This project is licensed under the `AGPL-3.0 License` - see the [LICENSE](./LICENSE) file for details.

92
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "event-cactus", "name": "event-cactus",
"version": "0.0.1", "version": "0.1.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "event-cactus", "name": "event-cactus",
"version": "0.0.1", "version": "0.1.1",
"dependencies": { "dependencies": {
"@sveltejs/adapter-node": "^5.3.1", "@sveltejs/adapter-node": "^5.3.1",
"drizzle-orm": "^0.44.5", "drizzle-orm": "^0.44.5",
@@ -35,7 +35,7 @@
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"typescript-eslint": "^8.20.0", "typescript-eslint": "^8.20.0",
"vite": "^7.0.4" "vite": "^7.1.10"
} }
}, },
"node_modules/@drizzle-team/brocli": { "node_modules/@drizzle-team/brocli": {
@@ -1949,6 +1949,66 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.4.5",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.0.4",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.4.5",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.0.4",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.0",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
@@ -2602,9 +2662,9 @@
} }
}, },
"node_modules/devalue": { "node_modules/devalue": {
"version": "5.1.1", "version": "5.4.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.1.tgz",
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", "integrity": "sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/drizzle-kit": { "node_modules/drizzle-kit": {
@@ -4737,13 +4797,13 @@
} }
}, },
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.14", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fdir": "^6.4.4", "fdir": "^6.5.0",
"picomatch": "^4.0.2" "picomatch": "^4.0.3"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.0.0"
@@ -4864,17 +4924,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.1.2", "version": "7.1.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.6", "fdir": "^6.5.0",
"picomatch": "^4.0.3", "picomatch": "^4.0.3",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"rollup": "^4.43.0", "rollup": "^4.43.0",
"tinyglobby": "^0.2.14" "tinyglobby": "^0.2.15"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"

View File

@@ -1,7 +1,7 @@
{ {
"name": "cactoide", "name": "cactoide",
"private": true, "private": true,
"version": "0.0.3", "version": "0.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@@ -35,7 +35,7 @@
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"typescript-eslint": "^8.20.0", "typescript-eslint": "^8.20.0",
"vite": "^7.0.4" "vite": "^7.1.10"
}, },
"dependencies": { "dependencies": {
"@sveltejs/adapter-node": "^5.3.1", "@sveltejs/adapter-node": "^5.3.1",

22
src/hooks.server.ts Normal file
View File

@@ -0,0 +1,22 @@
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { generateUserId } from '$lib/generateUserId.js';
export const handle: Handle = async ({ event, resolve }) => {
const cactoideUserId = event.cookies.get('cactoideUserId');
const userId = generateUserId();
const DAYS = 400; // practical upper bound in many browsers for cookies
const MAX_AGE = 60 * 60 * 24 * DAYS;
const PATH = '/';
if (!cactoideUserId) {
console.debug(`There is no cactoideUserId cookie, generating new one...`);
event.cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
} else {
console.debug(`cactoideUserId: ${cactoideUserId}`);
console.debug(`cactoideUserId cookie found, using existing one...`);
}
return resolve(event);
};

View File

@@ -2,6 +2,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { t } from '$lib/i18n/i18n.js'; import { t } from '$lib/i18n/i18n.js';
import { PUBLIC_LANDING_INFO } from '$env/static/public';
// Check if current page is active // Check if current page is active
const isActive = (path: string): boolean => { const isActive = (path: string): boolean => {
@@ -24,12 +25,14 @@
<!-- Navigation --> <!-- Navigation -->
<div class="md:flex md:items-center md:space-x-8"> <div class="md:flex md:items-center md:space-x-8">
{#if PUBLIC_LANDING_INFO !== 'false'}
<button <button
on:click={() => goto('/')} on:click={() => goto('/')}
class={isActive('/') ? 'text-violet-400' : 'cursor-pointer'} class={isActive('/') ? 'text-violet-400' : 'cursor-pointer'}
> >
{t('navigation.home')} {t('navigation.home')}
</button> </button>
{/if}
<button <button
on:click={() => goto('/discover')} on:click={() => goto('/discover')}

View File

@@ -1,20 +1,5 @@
import { generateUserId } from '$lib/generateUserId.js';
export function load({ cookies }) { export function load({ cookies }) {
const cactoideUserId = cookies.get('cactoideUserId'); const cactoideUserId = cookies.get('cactoideUserId');
const userId = generateUserId();
const DAYS = 400; // practical upper bound in many browsers for cookies
const MAX_AGE = 60 * 60 * 24 * DAYS;
const PATH = '/';
if (!cactoideUserId) {
console.debug(`There is no cactoideUserId cookie, generating new one...`);
cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
} else {
console.debug(`cactoideUserId: ${cactoideUserId}`);
console.debug(`cactoideUserId cookie found, using existing one...`);
}
return { return {
cactoideUserId cactoideUserId

View File

@@ -3,7 +3,7 @@
import Navbar from '$lib/components/Navbar.svelte'; import Navbar from '$lib/components/Navbar.svelte';
import { t } from '$lib/i18n/i18n.js'; import { t } from '$lib/i18n/i18n.js';
let { data } = $props(); let { data, children } = $props();
</script> </script>
<svelte:head> <svelte:head>
@@ -21,7 +21,7 @@
<!-- Main content --> <!-- Main content -->
<main class="relative z-10"> <main class="relative z-10">
<slot /> {@render children?.()}
</main> </main>
<!-- Footer --> <!-- Footer -->

View File

@@ -0,0 +1,11 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { PUBLIC_LANDING_INFO } from '$env/static/public';
export const load: PageServerLoad = async () => {
if (PUBLIC_LANDING_INFO === 'false') {
throw redirect(302, '/discover');
}
return {};
};