Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
18352fd
Fix Dockerfile build args
krokosik Sep 20, 2025
77c3c22
Fix docker manifest tags
krokosik Sep 20, 2025
0f279d9
Try fixing the latest boolean
krokosik Sep 20, 2025
e708190
Add pg_cron config commands
krokosik Sep 20, 2025
562db33
Unify action and submit button
krokosik Sep 20, 2025
d5d26fe
Add UI for setting recurrence rules in a expense
krokosik Sep 20, 2025
5b1b90f
Create recurrence migration
krokosik Sep 20, 2025
28356e9
Stub schedule service
krokosik Sep 21, 2025
30e3950
Migrate expense id to uuid for db side generation
krokosik Sep 21, 2025
f8a0b9e
Fix radix ui import of new components
krokosik Sep 21, 2025
11c5dd5
Add the recurrence procedure
krokosik Sep 21, 2025
5175ac3
Use the duplicate procedure with pg_cron
krokosik Sep 21, 2025
7234d19
Add the notified field for web push notification polling
krokosik Sep 30, 2025
aa5e661
Create a polling task for recurrent transaction notification
krokosik Sep 30, 2025
0891f01
Format prisma schema
krokosik Sep 30, 2025
4ec463e
Add pg_cron schema
krokosik Sep 30, 2025
5fb7234
Set cron tables as externally managed
krokosik Sep 30, 2025
d37714a
Move the extension activation to migration
krokosik Sep 30, 2025
0579ef8
Remove the problematic relation from expenseRecurrence
krokosik Sep 30, 2025
10f2030
Recurring page stub
krokosik Sep 30, 2025
e03169d
Fix instrumentation import
krokosik Sep 30, 2025
058645b
Post rebase schema fix
krokosik Oct 6, 2025
feab91f
Integrate with partial changes from banktransction PR
krokosik Oct 6, 2025
951d418
Add the cron builder component
krokosik Oct 6, 2025
0abc2bd
Localize calendar component
krokosik Oct 6, 2025
5ba2110
Replace obsolete timestamp of gocardless migration
krokosik Oct 6, 2025
78f74c5
Improve schema and migration with recurrence job relation
krokosik Oct 6, 2025
bfecc2e
HIde bank transaction icon when no account is connected
krokosik Oct 6, 2025
aa9f82f
Fix import issues due to incorrect base path
krokosik Oct 6, 2025
36a4b9a
Restyle cron builder and localize it
krokosik Oct 6, 2025
74586c3
Add last day cron key and fix drawer height
krokosik Oct 6, 2025
8144fb4
Remove redundant createdById from recurrence
krokosik Oct 6, 2025
75b19af
Basic recurrent expense submission
krokosik Oct 6, 2025
303dd99
One more migration update, change relation to 1-N for expenseRecurrence
krokosik Oct 7, 2025
f4bc0ac
Localize and display recurrence in details
krokosik Oct 7, 2025
ab455ae
Display recurring expenses in activity subpage
krokosik Oct 7, 2025
e4f3652
Use UTC hours/minutes in cron time input
krokosik Oct 7, 2025
1c6917b
Update scheduling to work around sql injection prevention
krokosik Oct 7, 2025
44adc48
Use UTC for pg_cron
krokosik Oct 7, 2025
7295481
Editing and deleting support
krokosik Oct 7, 2025
28d951b
Remove no longer used shadcn components
krokosik Oct 7, 2025
9617c0b
Lock the selections to single values as pg_cron does not support commas
krokosik Oct 7, 2025
46fe670
Remove unnecessary eslint comment
krokosik Oct 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/postgres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ jobs:
--amend ghcr.io/oss-apps/postgres-amd64:$GIT_SHA \
--amend ghcr.io/oss-apps/postgres-arm64:$GIT_SHA \

docker manifest push ghcr.io/oss-apps/postgres:$CHANNEL
docker manifest push ghcr.io/oss-apps/postgres:$DB_VERSION
docker manifest push ghcr.io/oss-apps/postgres:$GIT_SHA

- name: Create and push "latest" tag if applicable
if: ${{ github.event.inputs.is_latest == true }}
if: github.event.inputs.is_latest == 'true'
run: |
docker manifest create \
ossapps/postgres:latest \
Expand Down
3 changes: 3 additions & 0 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const config = {
// Run oxlint on JavaScript/TypeScript files
'*.{js,jsx,ts,tsx}': ['oxlint --type-aware --fix'],

// Format prisma.schema files
'*.prisma': ['pnpm prisma format'],

// Type check TypeScript files (optional - can be slow)
// '*.{ts,tsx}': () => 'tsc --noEmit',
};
Expand Down
4 changes: 2 additions & 2 deletions components.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"config": "",
"css": "src/styles/globals.css",
"baseColor": "gray",
"cssVariables": true,
Expand Down
6 changes: 3 additions & 3 deletions docker/dev/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ services:
- POSTGRES_PORT=${POSTGRES_PORT:-5432}
volumes:
- database:/var/lib/postgresql/data
ports:
- '${POSTGRES_PORT:-5432}:${POSTGRES_PORT:-5432}'
command: >
postgres
-c shared_preload_libraries=pg_cron
-c cron.database_name=${POSTGRES_DB:-splitpro}
-c cron.timezone=${TZ:-UTC}
-c cron.timezone=UTC
ports:
- '${POSTGRES_PORT:-5432}:${POSTGRES_PORT:-5432}'

minio:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
Expand Down
4 changes: 3 additions & 1 deletion docker/postgres/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
ARG POSTGRES_MAJOR=16
ARG POSTGRES_MINOR=16.10
ARG POSTGRES_MINOR=10
ARG POSTGRES_BASE=trixie

FROM postgres:${POSTGRES_MAJOR}.${POSTGRES_MINOR}-${POSTGRES_BASE}

ARG POSTGRES_MAJOR

# Install pg_cron (from PGDG packages)
RUN apt-get update && apt-get install -y \
postgresql-${POSTGRES_MAJOR}-cron \
Expand Down
10 changes: 5 additions & 5 deletions docker/prod/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ services:
interval: 10s
timeout: 5s
retries: 5
command: >
postgres
-c shared_preload_libraries=pg_cron
-c cron.database_name=${POSTGRES_DB:-splitpro}
-c cron.timezone=UTC
# ports:
# - "5432:5432"
env_file: .env
volumes:
- database:/var/lib/postgresql/data
command: >
postgres
-c shared_preload_libraries=pg_cron
-c cron.database_name=${POSTGRES_DB:-splitpro}
-c cron.timezone=${TZ:-UTC}

splitpro:
image: ossapps/splitpro:latest
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^1.1.1",
"cron-parser": "^4.9.0",
"cronstrue": "^3.3.0",
"date-fns": "^3.3.1",
"i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.2.0",
Expand Down
26 changes: 26 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions prisma.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ export default {
path: path.join('prisma', 'migrations'),
seed: 'tsx prisma/seed.ts',
},
experimental: {
externalTables: true,
},
tables: {
external: ['cron.job', 'cron.job_run_details'],
},
} satisfies PrismaConfig;
76 changes: 76 additions & 0 deletions prisma/migrations/20250920192654_recurrence/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
-- AlterTable
ALTER TABLE "public"."Expense" ADD COLUMN "recurrenceId" INTEGER;

-- CreateTable
CREATE TABLE "public"."ExpenseRecurrence" (
"id" SERIAL NOT NULL,
"jobId" BIGINT NOT NULL,
"notified" BOOLEAN NOT NULL DEFAULT true,

CONSTRAINT "ExpenseRecurrence_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "ExpenseRecurrence_jobId_key" ON "public"."ExpenseRecurrence"("jobId");

-- AddForeignKey
ALTER TABLE "public"."Expense" ADD CONSTRAINT "Expense_recurrenceId_fkey" FOREIGN KEY ("recurrenceId") REFERENCES "public"."ExpenseRecurrence"("id") ON DELETE CASCADE ON UPDATE CASCADE;

CREATE OR REPLACE FUNCTION duplicate_expense_with_participants(original_expense_id UUID)
RETURNS UUID AS $$
DECLARE
new_expense_id UUID;
BEGIN
-- STEP 1: Insert the new expense and get its new ID
INSERT INTO "Expense" (
"paidBy", "addedBy", name, category, amount, "splitType", "expenseDate", "updatedAt",
currency, "groupId", "updatedBy", "recurrenceId"
)
SELECT
"paidBy", "addedBy", name, category, amount, "splitType", now(), now(),
currency, "groupId", "updatedBy", "recurrenceId"
FROM "Expense"
WHERE id = original_expense_id
RETURNING id INTO new_expense_id;

-- STEP 2: Insert the new expense participants
INSERT INTO "ExpenseParticipant" (
"expenseId", "userId", amount
)
SELECT
new_expense_id, "userId", amount
FROM "ExpenseParticipant"
WHERE "expenseId" = original_expense_id;

-- STEP 3: Set notified to false in the ExpenseRecurrence table
UPDATE "ExpenseRecurrence"
SET notified = false
WHERE id = (SELECT "recurrenceId" FROM "Expense" WHERE id = original_expense_id);

-- STEP 4: Return the new expense ID
RETURN new_expense_id;
END;
$$ LANGUAGE plpgsql;

DO $$
BEGIN
IF current_database() NOT LIKE 'prisma_migrate_shadow_db%' THEN
CREATE EXTENSION IF NOT EXISTS pg_cron;
ELSE
CREATE SCHEMA IF NOT EXISTS cron;
CREATE TABLE IF NOT EXISTS cron.job (
jobid BIGINT PRIMARY KEY,
schedule TEXT,
command TEXT,
nodename TEXT,
nodeport INTEGER,
database TEXT,
username TEXT,
active BOOLEAN,
jobname TEXT
);
END IF;
END $$;

-- AddForeignKey
ALTER TABLE "public"."ExpenseRecurrence" ADD CONSTRAINT "ExpenseRecurrence_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "cron"."job"("jobid") ON DELETE CASCADE ON UPDATE CASCADE;
46 changes: 46 additions & 0 deletions prisma/migrations/20250921172223_add_expense_uuid/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Warnings:

- The `otherConversion` column on the `Expense` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- A unique constraint covering the columns `[uuidId]` on the table `Expense` will be added. If there are existing duplicate values, this will fail.

*/
-- DropForeignKey
ALTER TABLE "public"."Expense" DROP CONSTRAINT "Expense_otherConversion_fkey";

-- DropForeignKey
ALTER TABLE "public"."ExpenseNote" DROP CONSTRAINT "ExpenseNote_expenseId_fkey";

-- DropForeignKey
ALTER TABLE "public"."ExpenseParticipant" DROP CONSTRAINT "ExpenseParticipant_expenseId_fkey";

-- AlterTable
ALTER TABLE "public"."Expense" ADD COLUMN "uuidId" UUID DEFAULT gen_random_uuid(),
DROP COLUMN "otherConversion",
ADD COLUMN "otherConversion" UUID;

-- AlterTable
ALTER TABLE "public"."ExpenseNote" ADD COLUMN "expenseUuid" UUID;

-- AlterTable
ALTER TABLE "public"."ExpenseParticipant" ADD COLUMN "expenseUuid" UUID;

-- CreateIndex
CREATE UNIQUE INDEX "Expense_uuidId_key" ON "public"."Expense"("uuidId");

-- CreateIndex
CREATE UNIQUE INDEX "Expense_otherConversion_key" ON "public"."Expense"("otherConversion");

-- AddForeignKey
ALTER TABLE "public"."Expense" ADD CONSTRAINT "Expense_otherConversion_fkey" FOREIGN KEY ("otherConversion") REFERENCES "public"."Expense"("uuidId") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."ExpenseParticipant" ADD CONSTRAINT "ExpenseParticipant_expenseUuid_fkey" FOREIGN KEY ("expenseUuid") REFERENCES "public"."Expense"("uuidId") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."ExpenseNote" ADD CONSTRAINT "ExpenseNote_expenseUuid_fkey" FOREIGN KEY ("expenseUuid") REFERENCES "public"."Expense"("uuidId") ON DELETE CASCADE ON UPDATE CASCADE;

-- Migrate data
UPDATE "public"."Expense" SET "uuidId" = gen_random_uuid() WHERE "uuidId" IS NULL;
UPDATE "public"."ExpenseParticipant" SET "expenseUuid" = (SELECT "uuidId" FROM "public"."Expense" WHERE "id" = "expenseId") WHERE "expenseUuid" IS NULL;
UPDATE "public"."ExpenseNote" SET "expenseUuid" = (SELECT "uuidId" FROM "public"."Expense" WHERE "id" = "expenseId") WHERE "expenseUuid" IS NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Warnings:

- The primary key for the `Expense` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuidId` on the `Expense` table. All the data in the column will be lost.
- The `id` column on the `Expense` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- You are about to drop the column `expenseUuid` on the `ExpenseNote` table. All the data in the column will be lost.
- The primary key for the `ExpenseParticipant` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `expenseUuid` on the `ExpenseParticipant` table. All the data in the column will be lost.
- Changed the type of `expenseId` on the `ExpenseNote` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
- Changed the type of `expenseId` on the `ExpenseParticipant` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.

*/
-- DropForeignKey
ALTER TABLE "public"."Expense" DROP CONSTRAINT "Expense_otherConversion_fkey";

-- DropForeignKey
ALTER TABLE "public"."ExpenseNote" DROP CONSTRAINT "ExpenseNote_expenseUuid_fkey";

-- DropForeignKey
ALTER TABLE "public"."ExpenseParticipant" DROP CONSTRAINT "ExpenseParticipant_expenseUuid_fkey";

-- DropIndex
DROP INDEX "public"."Expense_uuidId_key";

-- AlterTable: Expense - Drop primary key constraint first
ALTER TABLE "public"."Expense" DROP CONSTRAINT "Expense_pkey";

-- AlterTable: Expense - Drop the old id column
ALTER TABLE "public"."Expense" DROP COLUMN "id";

-- AlterTable: Expense - Rename uuidId to id
ALTER TABLE "public"."Expense" RENAME COLUMN "uuidId" TO "id";

-- AlterTable: Expense - Add primary key constraint
ALTER TABLE "public"."Expense" ADD CONSTRAINT "Expense_pkey" PRIMARY KEY ("id");

-- AlterTable: ExpenseNote - Drop old expenseId column
ALTER TABLE "public"."ExpenseNote" DROP COLUMN "expenseId";

-- AlterTable: ExpenseNote - Rename expenseUuid to expenseId
ALTER TABLE "public"."ExpenseNote" RENAME COLUMN "expenseUuid" TO "expenseId";

-- AlterTable: ExpenseParticipant - Drop primary key constraint first
ALTER TABLE "public"."ExpenseParticipant" DROP CONSTRAINT "ExpenseParticipant_pkey";

-- AlterTable: ExpenseParticipant - Drop old expenseId column
ALTER TABLE "public"."ExpenseParticipant" DROP COLUMN "expenseId";

-- AlterTable: ExpenseParticipant - Rename expenseUuid to expenseId
ALTER TABLE "public"."ExpenseParticipant" RENAME COLUMN "expenseUuid" TO "expenseId";

-- AlterTable: ExpenseParticipant - Add primary key constraint
ALTER TABLE "public"."ExpenseParticipant" ADD CONSTRAINT "ExpenseParticipant_pkey" PRIMARY KEY ("expenseId", "userId");

-- AlterTable
ALTER TABLE "public"."ExpenseNote" ALTER COLUMN "expenseId" SET NOT NULL;

-- AddForeignKey
ALTER TABLE "public"."Expense" ADD CONSTRAINT "Expense_otherConversion_fkey" FOREIGN KEY ("otherConversion") REFERENCES "public"."Expense"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."ExpenseParticipant" ADD CONSTRAINT "ExpenseParticipant_expenseId_fkey" FOREIGN KEY ("expenseId") REFERENCES "public"."Expense"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."ExpenseNote" ADD CONSTRAINT "ExpenseNote_expenseId_fkey" FOREIGN KEY ("expenseId") REFERENCES "public"."Expense"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Loading