Compare commits

...
35 Commits
Author SHA1 Message Date
github-actions[bot] 21b9b8b44d chore: release v1.4.13 2026-03-04 12:37:48 +00:00
6ef7fcc0c2 fix: Feat/changelog icon (#137)
* Update .release-it.json

* Update .release-it.json

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:37:19 +01:00
fc4ce178cb fix: Update .release-it.json (#136)
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:33:58 +01:00
github-actions[bot] 98b72988c4 chore: release v1.4.12 2026-03-04 12:30:32 +00:00
317473e528 fix: Update CONTRIBUTING.md (#135)
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:30:04 +01:00
github-actions[bot] e13c5ddf51 chore: release v1.4.11 2026-03-04 12:24:36 +00:00
90be3ab731 fix: password input on sensible fields (#134)
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:24:10 +01:00
github-actions[bot] 85c0445a64 chore: release v1.4.10 2026-03-04 12:02:54 +00:00
6a227ccd7c fix: Feat/flow (#133)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

* Update pr-checker.yml

* Update pr-checker.yml

* fix: citation version

* Update .release-it.json

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:02:24 +01:00
github-actions[bot] 3d9a21f854 chore: release v1.4.9 2026-03-04 11:58:24 +00:00
d4229a985d fix: Feat/flow (#132)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

* Update pr-checker.yml

* Update pr-checker.yml

* fix: citation version

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 12:57:56 +01:00
github-actions[bot] c6e0921dea chore: release v1.4.8 2026-03-04 10:57:55 +00:00
346a8a4375 fix: Feat/flow (#131)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

* Update pr-checker.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:57:11 +01:00
github-actions[bot] 4799faa827 chore: release v1.4.7 2026-03-04 10:34:00 +00:00
2c6f5c38ed fix: Feat/flow (#130)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:33:25 +01:00
github-actions[bot] 4a884ab5cc chore: release v1.4.6 2026-03-04 10:27:05 +00:00
e59beaf749 fix: feat/flow (#129)
* fix

* fix

* fix

* fix

* Update docker.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:26:28 +01:00
github-actions[bot] 0d4e1229ab chore: release v1.4.5 2026-03-04 10:23:35 +00:00
8810fd91c9 fix: Feat/flow (#128)
* fix

* fix

* fix

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:22:51 +01:00
github-actions[bot] 7cf43c5e7c chore: release v1.4.4 2026-03-04 09:29:30 +00:00
77183af08d fix: Feat/flow (#127)
* fix

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 10:23:52 +01:00
3a2c5be5dd fix: bug
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 10:21:02 +01:00
Théo LAGACHE 11c764c2f3 fix 2026-03-04 10:17:37 +01:00
d128234ae6 fix: Feat/workflow (#125)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

* Update auto-release.yml

* fix

* Update auto-release.yml

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:46:47 +01:00
Théo LAGACHE ee31ee225d fix 2026-03-04 09:44:30 +01:00
e2195616d3 fix: Feat/workflow (#124)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

* Update auto-release.yml

* fix

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:34:32 +01:00
Théo LAGACHE 2ca818b996 Update auto-release.yml 2026-03-04 09:33:51 +01:00
131f2b4538 fix Feat/workflow (#123)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

* Update auto-release.yml

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:30:26 +01:00
6731c3fea9 fix: Feat/new workflow (#122)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:27:00 +01:00
d3da5e8c13 fix: Feat/new workflow (#121)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:20:10 +01:00
3b0d4225ce fix: Feat/new workflow (#118)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 17:00:01 +01:00
66f0930164 fix: Feat/new workflow (#117)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:50:36 +01:00
3979b83a0a fix: Feat/new workflow (#116)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:47:59 +01:00
b65e432564 fix: Feat/new workflow (#115)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:45:31 +01:00
340e7c3e00 fix: new workflow CI (#114)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:39:59 +01:00
22 changed files with 2109 additions and 906 deletions
+3 -3
View File
@@ -72,7 +72,7 @@ If you encounter a bug or have a suggestion for improvement, follow these steps:
3. **Push your branch**
```bash
git push origin feature/<feature-name>
git push origin feat/<feature-name>
```
4. **Open a Pull Request (PR)**
@@ -82,7 +82,7 @@ If you encounter a bug or have a suggestion for improvement, follow these steps:
## Code Style Guidelines
- Follow the [specific coding style guide] (e.g., Prettier, ESLint, PEP8).
- Follow the [specific coding style guide] (e.g., Prettier, ESLint, PEP8,Biome).
- Use meaningful variable names and include comments where necessary.
- Tests before submitting your changes.
@@ -107,4 +107,4 @@ If you encounter a bug or have a suggestion for improvement, follow these steps:
Thank you for contributing! 🙌
---
---
+46
View File
@@ -0,0 +1,46 @@
name: Auto Release bot
on:
pull_request:
types: [closed]
branches:
- main
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
ref: ${{ github.base_ref }}
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: git config --global user.name 'github-actions[bot]'
- run: git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Run release-it
run: |
git pull origin main
pnpm exec release-it --ci
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
@@ -1,16 +1,8 @@
name: GitHub Release
name: Discord Notification
on:
workflow_call:
inputs:
prerelease:
required: false
type: boolean
default: false
make_latest:
required: false
type: boolean
default: false
discord_title:
required: true
type: string
@@ -27,74 +19,24 @@ on:
required: true
jobs:
create-release:
notify-discord:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out the repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build Changelog
id: build_changelog
uses: mikepenz/release-changelog-builder-action@v5
with:
mode: "COMMIT"
configurationJson: |
{
"template": "#{{CHANGELOG}}",
"categories": [
{
"title": "## Feature",
"labels": ["feat", "feature"]
},
{
"title": "## Fix",
"labels": ["fix", "bug"]
},
{
"title": "## Other",
"labels": []
}
],
"label_extractor": [
{
"pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)",
"on_property": "title",
"target": "$1"
}
]
}
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: false
body: ${{ steps.build_changelog.outputs.changelog }}
prerelease: ${{ inputs.prerelease }}
make_latest: ${{ inputs.make_latest }}
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Send Discord Notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
RELEASE_INFO=$(gh release view "${{ github.ref_name }}" -R ${{ github.repository }} --json name,url,body,author)
RELEASE_TITLE=$(echo "$RELEASE_INFO" | jq -r .name)
if [ -z "$RELEASE_TITLE" ] || [ "$RELEASE_TITLE" = "null" ]; then RELEASE_TITLE="${{ github.ref_name }}"; fi
RELEASE_URL=$(echo "$RELEASE_INFO" | jq -r .url)
RELEASE_BODY=$(echo "$RELEASE_INFO" | jq -r .body)
AUTHOR_NAME="Portabase"
AUTHOR_ICON="https://github.com/Portabase.png"
AUTHOR_ICON="https://github.com/Portabase.png"
PAYLOAD=$(jq -n \
--arg title "$RELEASE_TITLE" \
@@ -105,7 +47,7 @@ jobs:
--arg discord_title "${{ inputs.discord_title }}" \
--arg discord_footer "${{ inputs.discord_footer }}" \
--argjson discord_color ${{ inputs.discord_color }} \
'{
'{
content: $discord_title,
embeds: [{
title: $title,
@@ -121,8 +63,8 @@ jobs:
}
}]
}'
)
)
curl -H "Content-Type: application/json" \
-d "$PAYLOAD" \
"$DISCORD_WEBHOOK"
+40 -48
View File
@@ -6,7 +6,7 @@ on:
image_name:
required: false
type: string
default: 'portabase/portabase'
default: "portabase/portabase"
add_latest:
required: false
type: boolean
@@ -14,11 +14,11 @@ on:
target:
required: false
type: string
default: 'prod'
default: "prod"
dockerfile:
required: false
type: string
default: './docker/dockerfile/Dockerfile'
default: "./docker/dockerfile/Dockerfile"
secrets:
DOCKER_USERNAME:
required: true
@@ -26,34 +26,32 @@ on:
required: true
jobs:
build:
name: Build and push Docker images
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
name: Build architectures
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
strategy:
fail-fast: false
matrix:
platform: [ linux/amd64, linux/arm64 ]
platform: [linux/amd64, linux/arm64]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set image tag
id: set-tags
- name: Prepare Image Tags
id: prep
run: |
REF_NAME=${GITHUB_REF#refs/tags/}
if [ "${{ matrix.platform }}" = "linux/amd64" ]; then
IMAGE="${{ inputs.image_name }}:$REF_NAME-amd64"
else
IMAGE="${{ inputs.image_name }}:$REF_NAME-arm64"
fi
echo "image=$IMAGE" >> $GITHUB_OUTPUT
ARCH=${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
echo "image=${{ inputs.image_name }}:${GITHUB_REF#refs/tags/}-$ARCH" >> $GITHUB_OUTPUT
echo "safe_platform=${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }}" >> $GITHUB_OUTPUT
- name: Build and push image
uses: docker/build-push-action@v6
@@ -62,61 +60,55 @@ jobs:
file: ${{ inputs.dockerfile }}
platforms: ${{ matrix.platform }}
push: true
tags: ${{ steps.set-tags.outputs.image }}
tags: ${{ steps.prep.outputs.image }}
target: ${{ inputs.target }}
cache-from: type=gha,scope=build-${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=build-${{ matrix.platform }}
- name: Prepare artifact name
id: artifact
run: |
platform=${{ matrix.platform }}
echo "safe_platform=${platform//\//-}" >> $GITHUB_OUTPUT
echo "${{ steps.set-tags.outputs.image }}" > image.txt
- name: Save image name for manifest
run: echo "${{ steps.prep.outputs.image }}" > image.txt
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
- uses: actions/upload-artifact@v4
with:
name: image-${{ steps.artifact.outputs.safe_platform }}
name: image-${{ steps.prep.outputs.safe_platform }}
path: image.txt
if-no-files-found: warn
compression-level: 6
overwrite: false
include-hidden-files: false
retention-days: 1
create-manifest:
name: Create multi-arch Docker manifest (Portabase)
name: Create multi-arch manifest
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
- uses: actions/download-artifact@v4
with:
name: image-linux-amd64
path: /tmp/digests/amd64
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
- uses: actions/download-artifact@v4
with:
name: image-linux-arm64
path: /tmp/digests/arm64
- name: Login to Docker
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.image_name }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable=${{ inputs.add_latest }}
- name: Create and push manifest list
working-directory: /tmp/digests
run: |
DOCKER_IMAGES="$(cat amd64/image.txt) $(cat arm64/image.txt)"
REF_NAME=${GITHUB_REF#refs/tags/}
MANIFEST_IMAGE="${{ inputs.image_name }}:$REF_NAME"
docker buildx imagetools create $DOCKER_IMAGES -t $MANIFEST_IMAGE
docker buildx imagetools inspect $MANIFEST_IMAGE
if [ "${{ inputs.add_latest }}" = "true" ]; then
docker buildx imagetools create $DOCKER_IMAGES -t ${{ inputs.image_name }}:latest
docker buildx imagetools inspect ${{ inputs.image_name }}:latest
fi
TAG_ARGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
docker buildx imagetools create $DOCKER_IMAGES $TAG_ARGS
+47
View File
@@ -0,0 +1,47 @@
name: PR Checker
on:
pull_request:
branches:
- main
- dev
jobs:
validate-code:
name: Code Integrity Check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check code formatting
run: pnpm run format:check
continue-on-error: true
- name: Run Linter
run: pnpm run lint
continue-on-error: true
- name: Type Check
run: pnpm run typecheck
continue-on-error: true
- name: Run Tests
run: pnpm run test
continue-on-error: true
- name: Test Application Build
run: pnpm run build
+8 -9
View File
@@ -3,29 +3,28 @@ name: Publish Docker image for release candidate
on:
push:
tags:
- '*-rc*'
- "*.*.*-rc*"
- "!*-*-rc*"
permissions:
contents: write
contents: read
packages: write
jobs:
docker_publish:
uses: ./.github/workflows/docker.yml
with:
add_latest: false
add_latest: true
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }}
github_release:
discord_notification:
needs: docker_publish
uses: ./.github/workflows/github.yml
uses: ./.github/workflows/discord.yml
with:
prerelease: true
make_latest: false
discord_title: "||@release-dashboard|| New release candidate published"
discord_color: 16776960
discord_title: "New Release Candidate: ${{ github.ref_name }}"
discord_color: 2044800
discord_footer: "Portabase"
secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+8 -10
View File
@@ -3,11 +3,11 @@ name: Publish Docker image for release
on:
push:
tags:
- '*.*.*'
- '!*-*'
- "*.*.*"
- "!*-*"
permissions:
contents: write
contents: read
packages: write
jobs:
@@ -19,15 +19,13 @@ jobs:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }}
github_release:
discord_notification:
needs: docker_publish
uses: ./.github/workflows/github.yml
uses: ./.github/workflows/discord.yml
with:
prerelease: false
make_latest: true
discord_title: "||@release-dashboard|| New release published"
discord_color: 5814783
discord_title: "New Release: ${{ github.ref_name }}"
discord_color: 3066993
discord_footer: "Portabase"
secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+56
View File
@@ -0,0 +1,56 @@
{
"git": {
"commit": true,
"commitMessage": "chore: release v${version}",
"requireCleanWorkingDir": true,
"tag": true,
"tagName": "${version}",
"push": true
},
"github": {
"release": true,
"tokenRef": "GITHUB_TOKEN"
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "conventionalcommits",
"types": [
{
"type": "feat",
"section": "✨ Features"
},
{
"type": "fix",
"section": "🐛 Bug Fixes"
},
{
"type": "perf",
"section": "⚡️ Performance Improvements"
},
{
"type": "revert",
"section": "⏪️ Reverts"
},
{
"type": "docs",
"section": "📝 Documentation",
"hidden": true
},
{
"type": "chore",
"section": "🔧 Chores",
"hidden": true
}
]
}
},
"@release-it/bumper": {
"out": {
"file": "CITATION.cff",
"path": "version",
"type": "text/yaml"
}
}
}
}
+9 -4
View File
@@ -1,6 +1,6 @@
cff-version: 1.2.0
title: Portabase
message: "If you use this software, please cite it as below."
message: If you use this software, please cite it as below.
type: software
authors:
- family-names: Gauthereau
@@ -9,7 +9,12 @@ authors:
given-names: Killian
repository-code: https://github.com/Portabase/portabase
url: https://portabase.io
abstract: "Portabase is a free, open-source, self-hosted solution for database administration, providing backup and restore capabilities, scheduling, retention policies, notifications, and support for multiple storage backends. Its headless agent architecture enables connection to multiple database instances securely and efficiently."
abstract: >-
Portabase is a free, open-source, self-hosted solution for database
administration, providing backup and restore capabilities, scheduling,
retention policies, notifications, and support for multiple storage backends.
Its headless agent architecture enables connection to multiple database
instances securely and efficiently.
keywords:
- docker
- kubernetes
@@ -26,5 +31,5 @@ keywords:
- web-ui
- agent
license: Apache-2.0
version: 1.4.3
date-released: "2026-03-02"
version: 1.4.13
date-released: '2026-03-02'
-3
View File
@@ -1,4 +1,3 @@
#FROM node:22-bullseye AS base
FROM --platform=$BUILDPLATFORM node:22-bullseye AS base
RUN apt-get update && apt-get install -y \
@@ -112,8 +111,6 @@ RUN chmod +x /usr/local/bin/tusd
COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf
#RUN mkdir -p .next /app/private/uploads \
# && chown -R nextjs:nodejs .next /app/private /app/public
RUN mkdir -p .next /data/private/uploads \
&& chown -R nextjs:nodejs .next /data/private /app/public
+6 -3
View File
@@ -1,6 +1,6 @@
{
"name": "portabase",
"version": "1.4.3",
"version": "1.4.13",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 8887",
@@ -11,7 +11,8 @@
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:drop": "drizzle-kit drop",
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts"
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts",
"release": "release-it"
},
"dependencies": {
"@better-auth/passkey": "^1.4.19",
@@ -65,7 +66,6 @@
"drizzle-orm": "^0.43.1",
"drizzle-zod": "^0.7.1",
"embla-carousel-react": "^8.6.0",
"express": "^5.2.1",
"googleapis": "^170.1.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.553.0",
@@ -106,6 +106,8 @@
"@iconify/react": "^6.0.2",
"@react-email/preview-server": "4.3.2",
"@react-email/render": "^2.0.4",
"@release-it/bumper": "^7.0.5",
"@release-it/conventional-changelog": "^10.0.5",
"@tailwindcss/postcss": "^4.2.1",
"@types/eslint-plugin-tailwindcss": "^3.17.0",
"@types/node": "^22.19.11",
@@ -123,6 +125,7 @@
"eslint-plugin-tailwindcss": "^3.18.2",
"framer-motion": "^12.34.3",
"postcss": "^8.5.6",
"release-it": "^19.2.4",
"tailwindcss": "^4.2.1",
"tsx": "^4.21.0",
"tw-animate-css": "^1.4.0",
+1498 -276
View File
File diff suppressed because it is too large Load Diff
-95
View File
@@ -1,95 +0,0 @@
#!/bin/bash
set -e
if [ -z "$1" ]; then
echo "Usage: ./release <version>"
echo "Example: ./release 1.0.0"
exit 1
fi
VERSION=$1
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
#if [ "$CURRENT_BRANCH" = "main" ]; then
# if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# echo "Error: On 'main' branch, only release tags (X.Y.Z) are allowed."
# exit 1
# fi
#else
# if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
# echo "Error: On branch '$CURRENT_BRANCH', only RC tags matching X.Y.Z-rc.W are allowed (e.g., 1.0.0-rc.1)."
# exit 1
# fi
#fi
CLEAN_VERSION=${VERSION#v}
CURRENT_DATE=$(date +%Y-%m-%d)
echo "Preparing release $VERSION..."
# package.json
if [ -f package.json ]; then
echo "Updating package.json..."
if sed --version >/dev/null 2>&1; then
sed -i "s/\"version\": \".*\"/\"version\": \"$CLEAN_VERSION\"/" package.json
else
sed -i '' "s/\"version\": \".*\"/\"version\": \"$CLEAN_VERSION\"/" package.json
fi
fi
# pyproject.toml
if [ -f pyproject.toml ]; then
echo "Updating pyproject.toml..."
if sed --version >/dev/null 2>&1; then
sed -i "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" pyproject.toml
else
sed -i '' "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" pyproject.toml
fi
fi
# Cargo.toml
if [ -f Cargo.toml ]; then
echo "Updating Cargo.toml..."
if sed --version >/dev/null 2>&1; then
sed -i "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" Cargo.toml
else
sed -i '' "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" Cargo.toml
fi
fi
# CITATION.cff
if [ -f CITATION.cff ]; then
echo "Updating CITATION.cff..."
if sed --version >/dev/null 2>&1; then
sed -i "s/^version: .*/version: $CLEAN_VERSION/" CITATION.cff
sed -i "s/^date-released: .*/date-released: \"$CURRENT_DATE\"/" CITATION.cff
else
sed -i '' "s/^version: .*/version: $CLEAN_VERSION/" CITATION.cff
sed -i '' "s/^date-released: .*/date-released: \"$CURRENT_DATE\"/" CITATION.cff
fi
fi
git add .
if ! git diff-index --quiet HEAD --; then
echo "Committing changes..."
git commit -m "chore(release): $VERSION"
else
echo "No changes to commit. Proceeding to tag..."
fi
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Tag $VERSION already exists. Aborting."
exit 1
fi
echo "Creating tag $VERSION..."
git tag -a "$VERSION" -m "Release $VERSION"
echo "Pushing changes and tags to remote..."
git push
git push origin "$VERSION"
echo "Successfully released $VERSION!"
-6
View File
@@ -1,6 +0,0 @@
import express from "express";
import router from "../src/features/api/router";
export function mountApi(app: express.Express) {
app.use("/services/v1", router);
}
-52
View File
@@ -1,52 +0,0 @@
import next from "next";
import express from "express";
import {mountApi} from "./api";
import http from "http";
const port = Number(process.env.PORT) || 8887;
const dev = process.env.NODE_ENV !== "production";
async function start() {
const nextApp = next({
dev,
hostname: "0.0.0.0",
turbopack: dev,
port
});
const handle = nextApp.getRequestHandler();
await nextApp.prepare();
const app = express();
app.use((req, res, next) => {
req.setTimeout(0);
res.setTimeout(0);
next();
});
mountApi(app);
app.use((req, res, next) => {
if (req.path.startsWith('/services/v1')) return next();
handle(req, res).catch((err) => {
console.error('Next.js App Router error:', err);
res.status(500).send('Internal Server Error');
});
});
const server = http.createServer(app);
server.setTimeout(0);
server.headersTimeout = 0;
server.requestTimeout = 0;
server.keepAliveTimeout = 0;
server.listen(port, "0.0.0.0", () => {
console.log(`NEXT APP → http://localhost:${port}`);
console.log(`API → http://localhost:${port}/services/v1`);
});
}
start();
@@ -1,30 +1,38 @@
import {UseFormReturn} from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator";
import { UseFormReturn } from "react-hook-form";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierDiscordFormProps = {
form: UseFormReturn<any, any, any>
}
form: UseFormReturn<any, any, any>;
};
export const NotifierDiscordForm = ({form}: NotifierDiscordFormProps) => {
return (
<>
<Separator className="my-1"/>
<FormField
control={form.control}
name="config.discordWebhook"
render={({field}) => (
<FormItem>
<FormLabel>Discord Webhook URL</FormLabel>
<FormControl>
<Input {...field} placeholder="https://discord.com/api/webhooks/..."/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
</>
)
}
export const NotifierDiscordForm = ({ form }: NotifierDiscordFormProps) => {
return (
<>
<Separator className="my-1" />
<FormField
control={form.control}
name="config.discordWebhook"
render={({ field }) => (
<FormItem>
<FormLabel>Discord Webhook URL</FormLabel>
<FormControl>
<PasswordInput
{...field}
placeholder="https://discord.com/api/webhooks/..."
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
};
@@ -1,43 +1,49 @@
import {UseFormReturn} from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator";
import type { UseFormReturn } from "react-hook-form";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierGotifyFormProps = {
form: UseFormReturn<any, any, any>
}
form: UseFormReturn<any, any, any>;
};
export const NotifierGotifyForm = ({form}: NotifierGotifyFormProps) => {
return (
<>
<Separator className="my-1"/>
<FormField
control={form.control}
name="config.gotifyServerUrl"
render={({field}) => (
<FormItem>
<FormLabel>Gotify Server URL</FormLabel>
<FormControl>
<Input {...field} placeholder="https://gotify.example.com"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.gotifyAppToken"
render={({field}) => (
<FormItem>
<FormLabel>Application Token</FormLabel>
<FormControl>
<Input {...field} placeholder="A1b2C3d4E5f6G7h"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
</>
)
}
export const NotifierGotifyForm = ({ form }: NotifierGotifyFormProps) => {
return (
<>
<Separator className="my-1" />
<FormField
control={form.control}
name="config.gotifyServerUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Gotify Server URL</FormLabel>
<FormControl>
<Input {...field} placeholder="https://gotify.example.com" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.gotifyAppToken"
render={({ field }) => (
<FormItem>
<FormLabel>Application Token</FormLabel>
<FormControl>
<PasswordInput {...field} placeholder="A1b2C3d4E5f6G7h" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
};
@@ -49,7 +49,7 @@ export const NotifierNtfyForm = ({form}: NotifierNtfyFormProps) => {
<FormItem>
<FormLabel>Access Token (Optional)</FormLabel>
<FormControl>
<Input {...field} placeholder="tk_..."/>
<PasswordInput {...field} placeholder="tk_..."/>
</FormControl>
<p className="text-xs text-muted-foreground">Only required for protected topics or self-hosted instances with auth.</p>
<FormMessage/>
@@ -1,31 +1,38 @@
import {UseFormReturn} from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator";
import { UseFormReturn } from "react-hook-form";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierSmtpFormProps = {
form: UseFormReturn<any, any, any>
}
form: UseFormReturn<any, any, any>;
};
export const NotifierSlackForm = ({form}: NotifierSmtpFormProps) => {
return(
<>
<Separator className="my-1"/>
<FormField
control={form.control}
name="config.slackWebhook"
render={({field}) => (
<FormItem>
<FormLabel>Slack Webhook URL</FormLabel>
<FormControl>
<Input {...field} placeholder="https://hooks.slack.com/services/..."/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
</>
)
}
export const NotifierSlackForm = ({ form }: NotifierSmtpFormProps) => {
return (
<>
<Separator className="my-1" />
<FormField
control={form.control}
name="config.slackWebhook"
render={({ field }) => (
<FormItem>
<FormLabel>Slack Webhook URL</FormLabel>
<FormControl>
<PasswordInput
{...field}
placeholder="https://hooks.slack.com/services/..."
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
};
@@ -1,64 +1,84 @@
import {UseFormReturn} from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator";
import { UseFormReturn } from "react-hook-form";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierTelegramFormProps = {
form: UseFormReturn<any, any, any>
}
form: UseFormReturn<any, any, any>;
};
export const NotifierTelegramForm = ({form}: NotifierTelegramFormProps) => {
return (
<>
<Separator className="my-1"/>
<FormField
control={form.control}
name="config.telegramBotToken"
render={({field}) => (
<FormItem>
<FormLabel>Telegram Bot Token</FormLabel>
<FormControl>
<Input {...field} placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.telegramChatId"
render={({field}) => (
<FormItem>
<FormLabel>Telegram Chat ID</FormLabel>
<FormControl>
<Input {...field} placeholder="-100123456789 or 123456789"/>
</FormControl>
<p className="text-xs text-muted-foreground">
You must start the conversation with the bot first (<strong>/start</strong>). <br/>
For groups, add the bot to the group. <br/>
You can use <a href="https://t.me/userinfobot" target="_blank" rel="noopener noreferrer" className="underline">@userinfobot</a> to find your ID.
</p>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.telegramTopicId"
render={({field}) => (
<FormItem>
<FormLabel>Telegram Topic ID</FormLabel>
<FormControl>
<Input {...field} placeholder="123456"/>
</FormControl>
<p className="text-xs text-muted-foreground">
Unique identifier for the target message thread (topic) of the forum; for forum supergroups only
</p>
<FormMessage/>
</FormItem>
)}
/>
</>
)
}
export const NotifierTelegramForm = ({ form }: NotifierTelegramFormProps) => {
return (
<>
<Separator className="my-1" />
<FormField
control={form.control}
name="config.telegramBotToken"
render={({ field }) => (
<FormItem>
<FormLabel>Telegram Bot Token</FormLabel>
<FormControl>
<PasswordInput
{...field}
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.telegramChatId"
render={({ field }) => (
<FormItem>
<FormLabel>Telegram Chat ID</FormLabel>
<FormControl>
<Input {...field} placeholder="-100123456789 or 123456789" />
</FormControl>
<p className="text-xs text-muted-foreground">
You must start the conversation with the bot first (
<strong>/start</strong>). <br />
For groups, add the bot to the group. <br />
You can use{" "}
<a
href="https://t.me/userinfobot"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
@userinfobot
</a>{" "}
to find your ID.
</p>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.telegramTopicId"
render={({ field }) => (
<FormItem>
<FormLabel>Telegram Topic ID</FormLabel>
<FormControl>
<Input {...field} placeholder="123456" />
</FormControl>
<p className="text-xs text-muted-foreground">
Unique identifier for the target message thread (topic) of the
forum; for forum supergroups only
</p>
<FormMessage />
</FormItem>
)}
/>
</>
);
};
@@ -1,65 +1,72 @@
import {UseFormReturn} from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator";
import { UseFormReturn } from "react-hook-form";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierWebhookFormProps = {
form: UseFormReturn<any, any, any>
}
form: UseFormReturn<any, any, any>;
};
export const NotifierWebhookForm = ({form}: NotifierWebhookFormProps) => {
return (
<>
<Separator className="my-1"/>
<FormField
control={form.control}
name="config.webhookUrl"
render={({field}) => (
<FormItem>
<FormLabel>Webhook URL</FormLabel>
<FormControl>
<Input {...field} placeholder="https://example.com/api/webhook"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<div className="flex gap-4">
<div className="flex-1">
<FormField
control={form.control}
name="config.webhookSecretHeader"
render={({field}) => (
<FormItem>
<FormLabel>Header Name (Optional)</FormLabel>
<FormControl>
<Input {...field} placeholder="X-Webhook-Secret"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
</div>
<div className="flex-1">
<FormField
control={form.control}
name="config.webhookSecret"
render={({field}) => (
<FormItem>
<FormLabel>Secret Value (Optional)</FormLabel>
<FormControl>
<Input {...field} placeholder="Secret value..."/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
</div>
</div>
<p className="text-xs text-muted-foreground mt-2">
If provided, the secret will be sent in the specified header (defaults to <code>X-Webhook-Secret</code>).
</p>
</>
)
}
export const NotifierWebhookForm = ({ form }: NotifierWebhookFormProps) => {
return (
<>
<Separator className="my-1" />
<FormField
control={form.control}
name="config.webhookUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook URL</FormLabel>
<FormControl>
<Input {...field} placeholder="https://example.com/api/webhook" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex gap-4">
<div className="flex-1">
<FormField
control={form.control}
name="config.webhookSecretHeader"
render={({ field }) => (
<FormItem>
<FormLabel>Header Name (Optional)</FormLabel>
<FormControl>
<Input {...field} placeholder="X-Webhook-Secret" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex-1">
<FormField
control={form.control}
name="config.webhookSecret"
render={({ field }) => (
<FormItem>
<FormLabel>Secret Value (Optional)</FormLabel>
<FormControl>
<PasswordInput {...field} placeholder="Secret value..." />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
<p className="text-xs text-muted-foreground mt-2">
If provided, the secret will be sent in the specified header (defaults
to <code>X-Webhook-Secret</code>).
</p>
</>
);
};
@@ -1,115 +1,116 @@
import {UseFormReturn} from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator";
import {PasswordInput} from "@/components/ui/password-input";
import {Switch} from "@/components/ui/switch";
import { UseFormReturn } from "react-hook-form";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
import { Switch } from "@/components/ui/switch";
type StorageS3FormProps = {
form: UseFormReturn<any, any, any>
}
form: UseFormReturn<any, any, any>;
};
export const StorageS3Form = ({form}: StorageS3FormProps) => {
return (
<>
<Separator className="my-1"/>
<FormField
control={form.control}
name="config.endPointUrl"
render={({field}) => (
<FormItem>
<FormLabel>Endpoint URL</FormLabel>
<FormControl>
<Input {...field} placeholder="s3.amazonaws.com"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.region"
render={({field}) => (
<FormItem>
<FormLabel>Region</FormLabel>
<FormControl>
<Input {...field} placeholder="us-east-1"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.accessKey"
render={({field}) => (
<FormItem>
<FormLabel>Access Key</FormLabel>
<FormControl>
<Input {...field} placeholder="A1b2C3d4E5f6G7h"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.secretKey"
render={({field}) => (
<FormItem>
<FormLabel>Secret Key</FormLabel>
<FormControl>
<PasswordInput {...field} placeholder="A1b2C3d4E5f6G7h"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.bucketName"
render={({field}) => (
<FormItem>
<FormLabel>Bucket name</FormLabel>
<FormControl>
<Input {...field} placeholder="portabase-dev"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.port"
render={({ field }) => (
<FormItem>
<FormLabel>Port</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="443" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
export const StorageS3Form = ({ form }: StorageS3FormProps) => {
return (
<>
<Separator className="my-1" />
<FormField
control={form.control}
name="config.endPointUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Endpoint URL</FormLabel>
<FormControl>
<Input {...field} placeholder="s3.amazonaws.com" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.region"
render={({ field }) => (
<FormItem>
<FormLabel>Region</FormLabel>
<FormControl>
<Input {...field} placeholder="us-east-1" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.accessKey"
render={({ field }) => (
<FormItem>
<FormLabel>Access Key</FormLabel>
<FormControl>
<Input {...field} placeholder="A1b2C3d4E5f6G7h" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.secretKey"
render={({ field }) => (
<FormItem>
<FormLabel>Secret Key</FormLabel>
<FormControl>
<PasswordInput {...field} placeholder="A1b2C3d4E5f6G7h" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.bucketName"
render={({ field }) => (
<FormItem>
<FormLabel>Bucket name</FormLabel>
<FormControl>
<Input {...field} placeholder="portabase-dev" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.port"
render={({ field }) => (
<FormItem>
<FormLabel>Port</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="443" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.ssl"
render={({ field }) => (
<FormItem>
<FormLabel>Use SSL</FormLabel>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
)
}
<FormField
control={form.control}
name="config.ssl"
render={({ field }) => (
<FormItem>
<FormLabel>Use SSL</FormLabel>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
};