Commit Graph

1003 Commits

Author SHA1 Message Date
kolaente
cbfd0e63ed fix: pass pointer to xorm Update to avoid hash panic in transaction mode
In transaction mode, xorm stores the bean argument as a map key in
afterUpdateBeans. Since Task contains slices and maps (unhashable
types), passing a Task value causes "hash of unhashable type" panic.
Passing a pointer (&ot) fixes this since pointers are always hashable.
2026-02-25 11:03:02 +01:00
kolaente
2188c7a79d fix: add missing Commit() to event listeners and cron jobs
With db.NewSession() now starting real transactions, all sessions that
do writes must explicitly commit. These listeners and cron jobs were
previously relying on auto-commit mode where each SQL statement was
committed immediately. Without explicit Commit(), the writes are
silently rolled back on Close(), and the held write locks cause
"database is locked" errors for subsequent requests on SQLite.
2026-02-25 11:03:02 +01:00
kolaente
23176bb8e1 test: add regression test for atomic parent project deletion
Verify that deleting a parent project atomically deletes all child
projects, including archived children and deeply nested hierarchies.
Also add missing defer s.Close() to existing delete test cases.
2026-02-25 11:03:02 +01:00
kolaente
49bba7f830 fix: eliminate nested database sessions to prevent table locks
Refactor functions that created their own sessions when called from
within existing transactions, which caused "database table is locked"
errors in SQLite's shared-cache mode.

Changes:
- Add files.CreateWithSession() to reuse caller's session
- Refactor DeleteBackgroundFileIfExists() to accept session parameter
- Add variadic session parameter to notifications.Notify() and
  Notifiable.ShouldNotify() interface
- Update all Notify callers (~17 sites) to pass their session through
- Use files.CreateWithSession in SaveBackgroundFile and NewAttachment
- Fix test code to commit sessions before assertions
2026-02-25 11:03:02 +01:00
kolaente
a6e6f252db refactor: remove redundant Begin() calls after NewSession auto-begins
Since NewSession() now auto-begins a transaction, explicit Begin()
calls are redundant (xorm's Begin() is a no-op when already in a
transaction). Removing them reduces confusion.

Special case: user_delete.go's loop previously called Begin/Commit
per user on a shared session. Restructured to create a new session
per user deletion so each gets its own transaction.
2026-02-25 11:03:02 +01:00
kolaente
764d3569ce fix: close leaked database sessions
Add defer s.Close() to sessions that were never closed:
- auth.GetAuthFromClaims inline session
- models.deleteUsers cron function
- notifications.notify database insert
2026-02-25 11:03:02 +01:00
kolaente
c9c250fb1c fix: add missing Commit() to write callers
After NewSession() auto-begins a transaction, callers that perform
writes must explicitly call Commit() for changes to persist. Without
this, writes are silently rolled back when Close() is called.

Affected callers:
- user deletion notification cron
- caldav token generation/deletion
- token cleanup cron
- mark-all-notifications-read endpoint
- saved filter view cron
- project background delete
- typesense reindex
- export cleanup cron
- task last-updated listener
- saved filter view listener
- SSO team cleanup cron
- migration status start/finish
- background set/remove handlers
- orphaned task position cleanup
- file creation
2026-02-25 11:03:02 +01:00
kolaente
b3d0b2f697 feat: add Session model with CRUD, permissions, and cleanup cron
- Session struct with UUID primary key, hashed refresh token, device
  info, IP address, and last-active tracking
- Token generation via generateHashedToken (SHA-256, 128 random bytes)
- CreateSession, GetSessionByRefreshToken, GetSessionByID
- Atomic RotateRefreshToken with WHERE on old hash to prevent replays
- ReadAll scoped to authenticated user (link shares rejected)
- Delete scoped to owning user (link shares rejected)
- Hourly cleanup cron for expired sessions based on is_long_session
- ErrSessionNotFound error type with HTTP 404 mapping
2026-02-25 10:30:25 +01:00
kolaente
04e60472b7 feat: add sessions table migration
Adds the `sessions` table for storing server-side session records.
Each row tracks a session ID (UUID), user ID, hashed refresh token,
device info, IP address, and timestamps.
2026-02-25 10:30:25 +01:00
kolaente
6de82db7e4 fix: decouple webhook dispatch from email/mailer config
The reminder and overdue crons now always run and dispatch webhook
events. Email notifications are only sent when both
ServiceEnableEmailReminders and MailerEnabled are true. Webhook
dispatch errors are logged but no longer abort the cron run.
2026-02-24 20:24:56 +01:00
kolaente
54aacd3707 feat: dispatch TaskOverdueEvent from overdue cron 2026-02-24 20:24:56 +01:00
kolaente
626e731ae4 feat: dispatch TaskReminderFiredEvent from reminder cron 2026-02-24 20:24:56 +01:00
kolaente
83dc7537c4 feat: register reminder and overdue events for webhooks 2026-02-24 20:24:56 +01:00
kolaente
e04c1a3d2e feat: add TaskReminderFiredEvent and TaskOverdueEvent types 2026-02-24 20:24:56 +01:00
kolaente
519f66a51f fix: detect and store mime type when creating file attachments
Use mimetype.DetectReader in files.Create to automatically detect and
store the MIME type from file content. This fixes task attachment
previews, S3 Content-Type headers, and UI display for newly uploaded
files.
2026-02-22 00:00:11 +01:00
kolaente
c12bbbe93e feat(comments): support order_by query parameter in comments API
Add an OrderBy field to the TaskComment struct with a query:"order_by"
tag so that the frontend can request ascending or descending comment
order. The value is validated to only accept "asc" or "desc", defaulting
to "asc". Also adds the corresponding swagger @Param annotation.
2026-02-19 14:20:52 +01:00
kolaente
1943d6993c fix: only merge range comparators in sub-table filter grouping
Restrict the AND-joined sub-table filter merging to range comparators
(>, >=, <, <=) only. Equality and negative comparators (=, !=, in,
not in) must remain as separate EXISTS/NOT EXISTS subqueries because
each matching value lives in its own row.

Merging equality filters like `labels = 4 && labels = 5` into a single
EXISTS would produce an unsatisfiable condition (no single row has
label_id=4 AND label_id=5). Merging negative filters like
`labels != 4 && labels != 5` into NOT EXISTS(label_id IN 4 AND
label_id IN 5) would be trivially true.

Also fix the join tracking to use the first filter's join type
(how the group connects to the previous element) instead of the last.
2026-02-19 12:40:29 +01:00
kolaente
302b58dac0 style: fix alignment in test case 2026-02-19 12:40:29 +01:00
kolaente
a93f6bf160 test: add OR-joined reminder filter regression test
Verify that OR-joined conditions on the same sub-table still produce
separate EXISTS subqueries and match independently, as expected.
2026-02-19 12:40:29 +01:00
kolaente
d1901f46c3 test: update expected task index after adding task #47 fixture
Task #47 in project 1 has index 32, so the next auto-assigned index
is now 33 instead of 18.
2026-02-19 12:40:29 +01:00
kolaente
c034e431cb fix: merge AND-joined sub-table filters into single EXISTS subquery
When multiple AND-joined filter conditions target the same sub-table
(e.g., reminders > X && reminders < Y), they are now combined into
a single EXISTS subquery so that all conditions must be satisfied by
the same row. Previously, each condition generated a separate EXISTS
subquery that could match different rows, causing false positives.

Fixes #2245
2026-02-19 12:40:29 +01:00
kolaente
cd72231502 test: add failing test for sub-table filter multi-row matching bug #2245
Add task47 variable (with reminders straddling the test window) and new
test cases that verify AND-joined sub-table filters match the same row.
The test "filtered reminder dates should not match task with reminders
outside window" will fail until the fix is applied.
2026-02-19 12:40:29 +01:00
Mattia Maglie
8779a28d1d fix: prevent duplicated sql condition in filters (#1546)
Proposing the fix as in #1545

Co-authored-by: mattia.maglie <mattia.maglie@alispa.com>
Co-authored-by: kolaente <k@knt.li>
2026-02-18 17:02:25 +01:00
John Starich
591a646f84 refactor: remove environment variable requirements for go test 2026-02-17 18:01:05 +01:00
kolaente
82933a0836 test(files): update tests for io.ReadSeeker API
- Replace custom testfile structs with bytes.NewReader
- Remove readerOnly wrapper and non-seekable reader tests (no longer
  possible at the type level)
- Update S3 unit tests to remove temp file assertions
2026-02-08 15:31:25 +01:00
kolaente
dbd74491c4 fix(files): update all callers to provide seekable readers for S3 uploads
Update all code paths that pass file content to the storage layer to
provide io.ReadSeeker instead of io.Reader:

- Avatar upload: use bytes.NewReader instead of bytes.Buffer
- Background upload handler: use bytes.NewReader instead of bytes.Buffer
- Unsplash background: buffer response body into bytes.NewReader
- Dump restore: buffer zip entry into bytes.NewReader
- Migration structure: pass bytes.NewReader directly instead of wrapping
  in io.NopCloser
- Task attachment: change NewAttachment parameter from io.ReadCloser to
  io.ReadSeeker
2026-02-08 15:31:25 +01:00
rhclayto
cf029cef0c feat: add option to send Basic Auth header with webhook requests (#2137)
Resolves https://github.com/go-vikunja/vikunja/issues/2136
Docs PR: https://github.com/go-vikunja/website/pull/284
2026-01-30 15:07:31 +01:00
renovate[bot]
9a61453e86 fix(deps): update module github.com/labstack/echo/v4 to v5 (#2131)
Closes https://github.com/go-vikunja/vikunja/pull/2133

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2026-01-24 20:38:32 +01:00
kolaente
731b7c3001 fix: avoid mutating global http.DefaultClient in webhook proxy (#2145)
Fixes a bug where the webhook HTTP client was mutating `http.DefaultClient` (the global singleton), causing ALL HTTP requests in the application to use the webhook proxy. This broke OIDC authentication and other external HTTP calls when webhook proxy was configured.

Fixes #2144
2026-01-24 13:58:47 +01:00
kolaente
0cd25f47e5 fix: populate complete entity data in deletion event webhooks (#2135)
Fixes webhook payloads for deletion events that were previously
containing incomplete or empty entity data. This occurred because
entities were being deleted from the database before the webhook event
was dispatched.

## Changes

This PR implements four targeted fixes to ensure complete entity data in
deletion event webhooks:

### 1. TaskAssignee Deletion (`pkg/models/listeners.go`)
- Extended `reloadEventData()` to fetch full assignee user data by ID
- Webhook payload now includes complete user object (username, email,
timestamps, etc.)

### 2. TaskComment Deletion (`pkg/models/task_comments.go`)
- Modified `Delete()` to call `ReadOne()` before deletion
- Ensures comment text, author, and timestamps are included in webhook
payload
- Follows the same pattern used by `Task.Delete()` and
`TaskAttachment.Delete()`

### 3. TaskAttachment Deletion (`pkg/models/task_attachment.go`)
- Extended `ReadOne()` to fetch the `CreatedBy` user
- Webhook payload now includes file creator information

### 4. TaskRelation Deletion (`pkg/models/task_relation.go`)
- Modified `Delete()` to fetch complete relation including `CreatedBy`
user before deletion
- Webhook payload now includes relation timestamps and creator
information

Fixes #2125
2026-01-24 12:50:18 +01:00
kolaente
534483f237 fix(filter): ensure year is always within mysql allowed range
Fixes https://github.com/go-vikunja/vikunja/issues/2077
2026-01-09 23:11:22 +01:00
kolaente
39b4568bc5 refactor: centralize HTTP error handling (#2062)
This changes the error handling to a centralized HTTP error handler in `pkg/routes/error_handler.go` that converts all error types to proper HTTP responses. This simplifies the overall error handling because http handler now only need to return the error instead of calling HandleHTTPError as previously.
It also removes the duplication between handling errors with and without Sentry.

🐰 Hop along, dear errors, no more wrapping today!
We've centralized handlers in a shiny new way,
From scattered to unified, the code flows so clean,
ValidationHTTPError marshals JSON supreme!
Direct propagation hops forward with glee,
A refactor so grand—what a sight to see! 🎉
2026-01-08 10:02:59 +00:00
kolaente
4f31300915 fix(export): use os-level temp file to create user data export 2026-01-08 10:37:51 +01:00
kolaente
49af08d3f6 feat(filters): add UI for marking saved filters as favorites (#2055)
This PR adds UI support for marking saved filters as favorites. The backend already supports the `is_favorite` field for saved filters, but the frontend didn't expose this functionality. Users can now favorite/unfavorite saved filters just like regular projects.
2026-01-07 16:21:41 +00:00
Samuel Rodda
3a47c062da fix(filters): preserve IsFavorite for saved filters in ReadOne (#2031)
- Saved filters' `IsFavorite` field was not being properly returned when
fetched as pseudo-projects via `/projects/{id}`
- This caused favorited filters to appear in both the Favorites and
Filters sections initially, but then disappear from Favorites after
clicking on them (navigating to the filter)

Fixes #1989

Co-authored-by: iamsamuelrodda <iamsamuelrodda@users.noreply.github.com>
2026-01-06 15:17:17 +00:00
kolaente
a9adc3490d fix(positions): detect and repair duplicate task positions automatically (#1998)
Relates to:
https://community.vikunja.io/t/reordering-not-possible-position-value-the-same-for-different-tasks/4078

Duplicate positions can occur due to race conditions or historical bugs, causing tasks to appear in the wrong order or jump around when the page is refreshed.

This change adds a `repair-task-positions` CLI command to detect and resolve task position conflicts, with dry-run preview option.
Also implemented automatic conflict detection and resolution to ensure
unique task positions.

🐰 Positions once conflicted, clustered tight,
But now we nudge them back into the light!
MinSpacing guards precision from decay,
While conflicts heal and duplicates give way. 
2025-12-20 19:38:28 +01:00
kolaente
82558dba87 fix(filters): ensure saved filter views never have position=0 (#1996)
Fixes #724 - Tasks in saved filter views get `position: 0` when they first appear in the filter, causing drag-and-drop sorting to not persist correctly.

**Changes:**
- Remove harmful `Position: 0` inserts from cron job and
`SavedFilter.Update` - `RecalculateTaskPositions` already creates
positions with proper values, so the intermediate inserts created a race
window
- Add on-demand position creation when fetching tasks for saved filter
views - safety net for newly matching tasks before the cron runs
- Add 5 new tests covering the fix and regression scenarios

🐰 Positions once zero, now bloom with care,
Sorted with grace, no more despair,
When filters call and tasks appear,
Numbers spring up, crystal clear!
Issue 724 hops away—
Sorting's fixed to stay, hooray! 🎉
2025-12-16 22:13:40 +00:00
kolaente
0b3decd869 fix: ensure API consistency for /tasks and empty array responses (#1988)
- Renames the `/tasks/all` endpoint to `/tasks` for consistency with
other collection endpoints like `/projects` and `/labels`
- Returns `[]` instead of `null` for empty pagination results across all
list endpoints
- Updates the frontend service to use the new endpoint path
- Updates API token tests to use the new endpoint path

Fixes #1984
2025-12-15 15:34:13 +00:00
kolaente
fb7764d9f1 feat: format user mentions with display names in email notifications (#1930)
Email notifications now display user mentions with inline avatar images for improved visual recognition and easier identification. Mentions gracefully fall back to display names if avatars are unavailable.
2025-12-10 12:39:05 +01:00
kolaente
d4eccccbfe fix(reminders): only send reminders to active users 2025-12-10 10:56:19 +01:00
Copilot
7cf2a6886e fix: clear error when duplicating project with uploaded background (#1926)
Resolves https://github.com/go-vikunja/vikunja/issues/1745

- [x] Understand the issue from GitHub issue #1745
- [x] Analyze the codebase to locate the bug in
`duplicateProjectBackground` function
- [x] Fix the bug: return nil explicitly at the end of
duplicateProjectBackground
- [x] Add test for duplicating a project with an uploaded background (as
subtest)
- [x] Run tests and verify the fix
- [x] Run code review and address any feedback
- [x] Run CodeQL security scan

## Summary of Changes

### Problem
When duplicating a project with an uploaded (non-Unsplash) background
image, users encounter an internal server error (HTTP 500). The backend
logs show: `file was not downloaded from unsplash [FileID: X]`

### Root Cause
The `duplicateProjectBackground` function in
`pkg/models/project_duplicate.go` uses named returns. When
`GetUnsplashPhotoByFileID` returns `ErrFileIsNotUnsplashFile` for an
uploaded background, the error was intentionally ignored (to proceed
with copying the file) but not cleared from the named return variable.
This caused the error to be returned at the end of the function via the
bare `return` statement, triggering a 500 response.

### Solution
Changed the bare `return` at the end of `duplicateProjectBackground` to
`return nil` explicitly.

### Changes
1. **`pkg/models/project_duplicate.go`**: Changed bare `return` to
`return nil` at the end of `duplicateProjectBackground`
2. **`pkg/models/project_duplicate_test.go`**: Added subtest "duplicate
project with uploaded background" to `TestProjectDuplicate`

### Testing
- All existing tests pass
- Added subtest to `TestProjectDuplicate` for uploaded background
scenario (project 35 with non-Unsplash background)

### Security Summary
- No security vulnerabilities found by CodeQL
- Code review passed

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> # Duplicate project with uploaded background - Implementation Plan
> 
> ## Overview
> Users encounter an internal server error when duplicating a project
that uses an uploaded background image (non-Unsplash). The b
> ackend attempt to copy the background leaves a non-Unsplash error
(`ErrFileIsNotUnsplashFile`) in a named return value, causing
> the duplication API call to fail even though the error should be
ignored. We need to adjust the duplication flow to allow upload
> ed backgrounds and add regression tests.
> 
> ## Current State Analysis
> - Project duplication calls `duplicateProjectBackground` to copy the
background file. The helper tries to copy a downloaded Unsp
> lash image and returns `ErrFileIsNotUnsplashFile` for uploaded files.
> - In the duplication code, the error variable is not cleared after
intentionally ignoring this specific error, so the function s
> till returns the error and triggers a 500 response.
> - There are no automated regression tests covering project duplication
with uploaded backgrounds.
> 
> ### Key Discoveries
> - The duplication logic treats Unsplash and uploaded backgrounds
differently and only clears the Unsplash download error, leavin
> g the non-Unsplash error set.
> - The API currently works for Unsplash backgrounds but fails for
uploaded backgrounds due to the lingering error value.
> 
> ## Desired End State
> - Duplicating a project succeeds for both Unsplash and uploaded
backgrounds.
> - Uploaded background files (and their metadata) are copied correctly
to the new project when possible, or gracefully skipped wi
> thout failing duplication.
> - Regression tests cover duplication with both background types to
prevent future regressions.
> 
> ## What We're NOT Doing
> - No changes to the background upload endpoints or UI selection
workflow.
> - No changes to Unsplash download behavior or quota handling.
> - No new migration or database schema changes.
> 
> ## Implementation Approach
> 1. Fix backend duplication error handling so uploaded backgrounds do
not cause a fatal error.
> 2. Add backend tests to cover duplication with uploaded backgrounds
and Unsplash backgrounds (success paths) and verify duplicat
> ion works without returning 500 errors.
> 3. Ensure tests document the expected behavior and guard against
regressions.
> 
> ## Phase 1: Fix duplication error handling
> ### Overview
> Make project duplication tolerate uploaded backgrounds by clearing or
not propagating `ErrFileIsNotUnsplashFile` once it has bee
> n intentionally ignored.
> 
> ### Changes Required
> - **File:** `pkg/models/projects.go` (or relevant duplication helper)
> - Adjust `duplicateProjectBackground` (or the calling logic) to reset
the named return error after handling `ErrFileIsNotUnspl
> ashFile`, ensuring the function returns `nil` when no real error
occurs.
> - Keep existing behavior for other errors and for Unsplash downloads.
> 
> ### Success Criteria
> - Uploaded background duplication no longer returns an internal server
error.
> - Unsplash background duplication remains functional and still
surfaces real errors.
> 
> ## Phase 2: Add regression tests
> ### Overview
> Add automated tests verifying project duplication works for both
uploaded and Unsplash backgrounds.
> 
> ### Changes Required
> - **File:** `pkg/models/projects_test.go` (or closest existing test
file for project duplication)
> - Add a test that sets up a project with an uploaded background file,
duplicates the project, and asserts duplication succeeds
>  and the duplicated project has an appropriate background reference.
> - Add/adjust test coverage for Unsplash background duplication to
confirm unchanged behavior.
> - Use existing fixtures or temporary files as needed for uploaded
background setup.
> 
> ### Success Criteria
> - Tests fail on current main branch but pass after the fix.
> - Tests validate that duplication completes without 500 errors for
both background types.
> 
> ## Testing Strategy
> - Automated Go tests via `mage test:filter` targeting the new
duplication tests.
> - Optionally run the broader suite (`mage test:feature`) if time
permits to ensure no regressions.
> 
> ## Manual Verification
> 1. Create a project and upload a background via the UI; duplicate it;
observe duplication succeeds and background is present or
> gracefully handled.
> 2. Create a project with an Unsplash background; duplicate it; verify
duplication succeeds.
> 3. Check API responses for duplication calls to ensure no internal
server errors.


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-12-04 10:16:16 +01:00
kolaente
7f5a08b316 fix(tasks): make sure all users get overdue reminder mails (#1901)
Fixes a regression introduced in 2a43f9b076

Resolves https://github.com/go-vikunja/vikunja/issues/1581
2025-11-28 11:06:47 +01:00
kolaente
869a8b0ab9 fix(sharing): use the highest team sharing permission when sharing the same project with multiple teams (#1894) 2025-11-27 22:25:06 +01:00
Mithilesh Gupta
7dddc5dfa2 feat: task unread tracking (#1857)
---------

Co-authored-by: Mithilesh Gupta <guptamithilesh@protonmail.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-27 15:14:42 +01:00
Weijie Zhao
cfab3ff922 fix: update mention format to use custom HTML element with usernames (#1843) 2025-11-21 15:29:15 +01:00
Copilot
9d0633268a fix: prevent panic in webhook listener when fetching project (#1848)
This fixes a panic that occurred when handling webhooks. The code was
incorrectly using webhook.CreatedByID (user ID) to fetch a project,
when it should use webhook.ProjectID. This could cause GetProjectSimpleByID
to return nil if no project exists with that ID.

Additionally, added a nil check before calling project.ReadOne() to prevent
a nil pointer dereference panic when accessing p.ID.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-19 14:43:08 +00:00
Copilot
5f795bb531 fix: self-assignment notification to use "themselves" instead of repeating username (#1836)
When a user assigns a task to themselves, notifications to other users now
correctly say "User A assigned Task #123 to themselves" instead of
"User A assigned Task #123 to User A"

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-17 23:07:48 +00:00
kolaente
9b78584734 fix(events): only trigger task.updated once when marking task done
Resolves https://github.com/go-vikunja/vikunja/issues/1724
2025-11-16 11:01:15 +01:00
kolaente
f96601bf18 fix(webhook): make sure the payload always contains a fully loaded project 2025-11-16 10:48:53 +01:00
Copilot
f2a1348c51 feat: add thread IDs to task notification emails for client-side threading (#1826)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-15 18:58:32 +01:00