Commit Graph

2101 Commits

Author SHA1 Message Date
kolaente 8afbdf2deb fix(events): defer event dispatch for team operations
Convert events.Dispatch to events.DispatchOnCommit in team and team
member CRUD. Also removes premature s.Commit() from TeamMember.Delete
since the handler manages the transaction lifecycle.

Refs #2315
2026-03-03 12:46:34 +01:00
kolaente dd7f7de518 fix(events): defer event dispatch for project operations
Convert events.Dispatch to events.DispatchOnCommit in project CRUD,
project-user sharing, and project-team sharing.

Refs #2315
2026-03-03 12:46:34 +01:00
kolaente fe459c9297 fix(events): defer event dispatch for task sub-entities
Convert events.Dispatch to events.DispatchOnCommit in task assignees,
comments, attachments, relations, and kanban bucket operations.

Refs #2315
2026-03-03 12:46:34 +01:00
kolaente 3eb289262f fix(events): defer task event dispatch until after transaction commit
Convert events.Dispatch to events.DispatchOnCommit in Task.Create,
updateSingleTask, Task.Delete, and triggerTaskUpdatedEventForTaskID.
Events are now dispatched by the handler after s.Commit(), ensuring
webhook listeners see committed data.

Fixes #2315
2026-03-03 12:46:34 +01:00
kolaente 217a481162 feat(handlers): dispatch pending events after transaction commit
All generic CRUD handlers now call events.DispatchPending(s) after
s.Commit() and events.CleanupPending(s) on rollback paths. This is
preparation for switching model methods from events.Dispatch to
events.DispatchOnCommit.

Refs #2315
2026-03-03 12:46:34 +01:00
kolaente 564573bdd5 feat(events): add DispatchOnCommit/DispatchPending for deferred event dispatch
Events dispatched inside model methods run before the transaction commits,
causing listeners (especially webhooks) that open new sessions to read
stale data. These new functions allow accumulating events during a
transaction and dispatching them only after commit.

Refs #2315
2026-03-03 12:46:34 +01:00
Dominik Pschenitschni d94429d33c fix(caldav): parse timestamps in configured timezone 2026-03-03 12:18:48 +01:00
kolaente 51f789bf5c fix(e2e): drain event handlers and stop browser between tests
Async event handlers (via Watermill) from the previous test can hold
SQLite connections, starving the next test's fixture setup PATCH request.

Three changes fix this:

1. Track in-flight event handler goroutines with a WaitGroup.
2. Call WaitForPendingHandlers() in the test endpoint before
   truncating/inserting data.
3. Navigate the browser to about:blank in fixture teardown to stop
   notification polling and other frontend requests between tests.
2026-03-03 10:41:19 +01:00
kolaente 39acdac531 fix(caldav): eliminate nested db session in CalDAV auth
checkUserCaldavTokens called user.GetCaldavTokens which creates its own
db.NewSession(), while the caller (BasicAuth) already holds an open
session. With SQLite this caused a deadlock because the second session
blocks on the write lock held by the first session in the same goroutine.

Add GetCaldavTokensWithSession that accepts an existing session and use
it from checkUserCaldavTokens.
2026-03-03 10:41:19 +01:00
kolaente 530973c475 fix(auth): make SameSite=None conditional on HTTPS for refresh cookie
SameSite=None requires Secure=true per browser spec. When running over
plain HTTP (local dev, e2e tests), browsers reject or downgrade the
cookie, breaking session refresh. Fall back to SameSite=Lax for HTTP
while keeping SameSite=None for HTTPS (needed for the Electron desktop
app cross-origin scenario).
2026-03-03 10:41:19 +01:00
kolaente 98f2893ffe fix(db): use WAL mode for SQLite and temp file for ephemeral databases
Three SQLite connection issues are fixed:

1. The refactoring in 26c0f71 accidentally dropped _busy_timeout from
   the file-based SQLite connection string. Without it, concurrent
   transactions get instant SQLITE_BUSY errors instead of waiting.

2. _txlock=immediate forced ALL transactions (including reads) to
   acquire the write lock at BEGIN, serializing all database access.
   WAL mode makes this unnecessary: readers use snapshots and never
   block writers, so the SHARED-to-RESERVED deadlock cannot occur.

3. In-memory shared cache (file::memory:?cache=shared) uses table-level
   locking where _busy_timeout is ineffective (returns SQLITE_LOCKED,
   not SQLITE_BUSY) and concurrent connections deadlock. Replace with a
   temp file using WAL mode for proper concurrency.
2026-03-03 10:41:19 +01:00
kolaente 26c0f71b6c
fix(db): use immediate txlock for SQLite instead of MaxOpenConns(1)
MaxOpenConns(1) caused Go-level deadlocks: when two goroutines needed
database connections concurrently, the second blocked forever waiting
for the single connection pool slot. This broke CI (sqlite web tests
timed out after 45min, e2e tests hung).

The actual "database is locked" errors were caused by SQLite's default
deferred transaction locking: two connections both acquire SHARED locks,
then deadlock when both try to promote to RESERVED for writing. SQLite
detects this instantly and returns SQLITE_BUSY, bypassing busy_timeout.

_txlock=immediate fixes this by acquiring the write lock at BEGIN time.
The second concurrent transaction waits (up to busy_timeout) instead of
deadlocking. Combined with WAL mode (concurrent readers + single writer),
this handles concurrency correctly without restricting the Go connection
pool.
2026-03-02 14:03:33 +01:00
kolaente 28f98a7a96
fix(auth): use SameSite=None for refresh token cookie to fix desktop app
SameSite=Strict prevents the browser from sending the HttpOnly refresh
token cookie in cross-origin contexts like the Electron desktop app,
where the page runs on localhost but the API is on a remote host. This
caused sessions to expire quickly because refresh requests never
included the cookie.

SameSite=None allows cross-origin sending while HttpOnly still prevents
JavaScript from reading the cookie value (XSS protection).

Resolves #2309
2026-03-02 13:54:10 +01:00
kolaente 79dbb40985
fix(db): prevent SQLite "database is locked" errors under concurrent writes
Configure SQLite connections with WAL journal mode, a 5-second busy
timeout, shared cache, and a max of 1 open connection. SQLite only
supports a single writer at a time, so without these settings concurrent
API requests (e.g. bulk task creation) would immediately fail with
"database is locked" instead of waiting and retrying.
2026-03-02 12:37:55 +01:00
Weijie Zhao 3ca4913fcb
fix: use MinPositionSpacing threshold in calculateNewPositionForTask (#2320)
calculateNewPositionForTask only checked for lowestPosition == 0 before
triggering a full position recalculation. Extremely small position
values (e.g. 3.16e-285) passed this check, causing new tasks to get
meaningless positions via the index * 2^16 fallback, breaking sort
order.

Use the same < MinPositionSpacing threshold that
createPositionsForTasksInView and the position update handler already
use.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-02 09:14:38 +01:00
kolaente 23d84e7811
fix(views): assign default position when creating new project views
When creating a new view without specifying a position, it defaulted to
0, causing it to always sort before all other views. Apply
calculateDefaultPosition to assign a unique position based on the view
ID, consistent with how projects, tasks, and buckets handle this.

Fixes go-vikunja/vikunja#2319
2026-03-02 08:35:35 +01:00
kolaente a7e4a4f4af
fix(migration): support space-separated date format in TickTick importer
Fixes https://github.com/go-vikunja/vikunja/issues/2324
2026-03-02 08:35:35 +01:00
kolaente 412215ee2f fix(auth): correctly delete older password reset tokens in cron 2026-02-27 14:44:26 +01:00
kolaente 5c2195f9fc fix(auth): remove password reset token after use 2026-02-27 14:44:26 +01:00
kolaente 3d5ad73d4f
fix(filter): recover from datemath panic on malformed date filter values
The go-datemath lexer panics with "scanner internal error" when given
certain malformed inputs like "no" (it starts recognizing "now" but
hits EOF). Wrap datemath.Parse in a recover so the panic becomes a
regular error, allowing the fallback date parser to handle it gracefully.

Closes go-vikunja/vikunja#2307
2026-02-26 16:09:13 +01:00
Frederick [Bot] 599d05dd5e [skip ci] Updated swagger docs 2026-02-25 13:05:32 +00:00
kolaente d1e1cb3b4f test(api): add tests for password validation in reset and update flows
- Add httpCodeGetter interface to handle ValidationHTTPError in test helper
- Add test case for password too short in password reset
- Add test case for password too short in password update
- Fix existing test data to use valid 8+ char passwords
2026-02-25 13:44:56 +01:00
kolaente 89c17d3b23 feat(api): enforce password validation on reset and update flows
Add bcrypt_password validation to password reset and update endpoints:
- Add validation tag to PasswordReset.NewPassword struct field
- Add validation tag to UserPassword.NewPassword struct field
- Add c.Validate() calls in both handlers
- Fix off-by-one error in bcrypt_password validator (use <= 72 not < 72)

Password requirements: min 8 chars, max 72 bytes (bcrypt limit)
2026-02-25 13:44:56 +01:00
kolaente 39da47e435 fix: detect and fail on oversized zip entries instead of silent truncation
Replace io.LimitReader with a new readZipEntry helper that reads one extra
byte to detect when content exceeds maxZipEntrySize (500MB). This prevents
silent data corruption where partial file bytes would be stored as if the
upload succeeded.

The import now fails with ErrFileTooLarge instead of accepting truncated
content for attachments and background blobs.
2026-02-25 13:01:00 +01:00
kolaente db4fa9a4b6 fix(restore): extract preValidateTableData to reduce cyclomatic complexity 2026-02-25 13:01:00 +01:00
kolaente f3ac0574c0 fix(auth): use checked type assertions for all JWT claims 2026-02-25 13:01:00 +01:00
kolaente 1b3d8dc59c fix(restore): pre-validate all table data JSON before wiping database 2026-02-25 13:01:00 +01:00
kolaente 9fd5b62fde fix(restore): limit zip entry read size to prevent decompression bombs 2026-02-25 13:01:00 +01:00
kolaente 329c07f24b fix(attachments): use mime.FormatMediaType for Content-Disposition header 2026-02-25 13:01:00 +01:00
kolaente 7e7e778d49 fix(db): validate table names and quote identifiers in raw SQL 2026-02-25 13:01:00 +01:00
kolaente 9d19a04550 fix(migration): use checked type assertion for background file id 2026-02-25 13:01:00 +01:00
kolaente fc5ab844de fix(migration): limit zip entry read size to prevent decompression bombs 2026-02-25 13:01:00 +01:00
kolaente 6815cdbda4 fix(migration): reject zip entries with path traversal in vikunja-file import 2026-02-25 13:01:00 +01:00
kolaente bbe1a2bbd0 refactor(utils): extract ContainsPathTraversal to shared utils package 2026-02-25 13:01:00 +01:00
kolaente c2cf5ba1c5 fix(restore): validate migration data before wiping database
Move archive validation (migration file existence and slice bounds
check) before the database wipe. Previously a malformed archive
would first destroy the database and then panic, leaving the
instance in an irrecoverable state with total data loss.

Now the migration data is fully parsed and validated before any
destructive operations occur.
2026-02-25 13:01:00 +01:00
kolaente 3c0ea7099e fix(restore): validate database file names in zip archive
Check that database entries in the zip have a .json suffix and a
non-empty base name before slicing the extension off. This prevents
a panic from index-out-of-range when the filename is too short.
Also use TrimPrefix instead of ReplaceAll for correctness.
2026-02-25 13:01:00 +01:00
kolaente 7971500467 fix(restore): sanitize config file path to prevent zip slip
Use filepath.Base() on the config file name from the zip archive
before passing it to os.OpenFile, ensuring the config file is
always written to the current directory regardless of what path
the zip entry claims to have.
2026-02-25 13:01:00 +01:00
kolaente 12dca5f0b0 fix(restore): reject zip entries with path traversal sequences
Validate all zip entry names during restore to reject entries
containing directory traversal sequences (e.g. ../../../pwned.txt).
This prevents a Zip Slip attack where a malicious archive could
write files outside the intended extraction directory.
2026-02-25 13:01:00 +01:00
kolaente b6155d525c
feat(cli): reorganize repair commands under unified 'vikunja repair' parent (#2300)
Consolidate four scattered repair/maintenance CLI commands into a unified `vikunja repair` parent command with subcommands.
2026-02-25 11:50:09 +00:00
kolaente a5b1a90c42 refactor: remove typesense support
Typesense was an optional external search backend. This commit fully
removes the integration, leaving the database searcher as the only
search implementation.

Changes:
- Delete pkg/models/typesense.go (core integration)
- Delete pkg/cmd/index.go (CLI command for indexing)
- Simplify task search to always use database searcher
- Remove Typesense event listeners for task sync
- Remove TypesenseSync model registration
- Remove Typesense config keys and defaults
- Remove Typesense doctor health check
- Remove Typesense initialization from startup
- Clean up benchmark test
- Add migration to drop typesense_sync table
- Remove golangci-lint suppression for typesense.go
- Remove typesense-go dependency
2026-02-25 12:15:28 +01:00
kolaente 71657fce30 feat: add repair-projects CLI command 2026-02-25 11:56:25 +01:00
kolaente ad307a3499 feat: add RepairOrphanedProjects function 2026-02-25 11:56:25 +01:00
kolaente 963235c0ce test: add failing tests for RepairOrphanedProjects 2026-02-25 11:56:25 +01:00
kolaente 9e050fe40e test: add orphaned project fixture for repair-projects command 2026-02-25 11:56:25 +01:00
kolaente 107a92f573 fix: commit transaction in session cleanup cron
RegisterSessionCleanupCron opens a transaction via db.NewSession() but
never calls s.Commit(). The deferred s.Close() auto-rolls-back, making
the DELETE a no-op. Add the missing commit.
2026-02-25 11:03:02 +01:00
kolaente 2f680d041c fix: address review comments on session lifecycle
- user_export.go: Remove defer s.Close() from checkExportRequest since
  it returns the session to callers. Callers now own the session
  lifecycle with their own defer s.Close(). Close session on all error
  paths within checkExportRequest.

- user_delete.go: Close the read session immediately after Find() before
  the per-user deletion loop, avoiding a long-lived transaction holding
  locks unnecessarily.

- user/delete.go: Remove double s.Close() in notifyUsersScheduledForDeletion
  by closing immediately after Find() instead of using both defer and
  explicit close.

- caldav_token.go: Return nil token on Commit() error to prevent callers
  from using an unpersisted token.
2026-02-25 11:03:02 +01:00
kolaente b3d8a56364 fix: use caller's session in LDAP syncUserGroups to avoid nested transactions
syncUserGroups created its own db.NewSession() internally while being
called from AuthenticateUserInLDAP which already has an active session
with writes. In SQLite shared-cache mode this causes a lock conflict.

Pass the caller's session through instead, and add s.Commit() before
db.AssertExists calls in LDAP tests.
2026-02-25 11:03:02 +01:00
kolaente 2f718206f9 fix: add TestMain to caldav tests and fix session conflicts
Add a proper main_test.go for the caldav test package that initializes
the logger, config, test database, and event system. Previously, these
were initialized inline in TestSubTask_Create and TestSubTask_Update
relied on running after it (fragile test ordering).

Fix session handling in TestSubTask_Update: close the read session
before calling UpdateResource (which creates its own internal session)
to avoid SQLite lock conflicts from concurrent transactions.
2026-02-25 11:03:02 +01:00
kolaente a7086e5e49 fix: prevent session leaks and visibility issues in model tests
Two categories of fixes:

1. Use defer s.Close() instead of explicit s.Close() to prevent session
   leaks when require.FailNow() triggers runtime.Goexit(), which skips
   explicit close calls but runs deferred functions. Leaked sessions
   hold SQLite write locks that block all subsequent fixture loading.

2. Add s.Commit() before db.AssertExists/db.AssertMissing calls. These
   assertion helpers query via the global engine (not the test session),
   so they cannot see uncommitted data from the session's transaction.

For block-scoped sessions (kanban_task_bucket_test.go), wrap each block
in an anonymous function so defer runs at block boundary rather than
deferring to the enclosing test function.
2026-02-25 11:03:02 +01:00
kolaente 2a10b22c5c fix: use session-aware file creation to avoid nested transactions
files.Create() and files.CreateWithMime() internally create their own
sessions and transactions. When called from within an existing
transaction (now that db.NewSession() auto-begins), this creates nested
transactions that deadlock on SQLite.

Switch to files.CreateWithSession() and files.CreateWithMimeAndSession()
to participate in the caller's existing transaction instead.
2026-02-25 11:03:02 +01:00
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 eea59c33c7 fix: isolate deletion notifications into per-user transactions
On Postgres, a failed operation puts the transaction in an error state
where subsequent operations fail. The previous loop with continue would
keep trying to use a broken transaction. Each user now gets its own
transaction so a single notification failure doesn't affect others.
2026-02-25 11:03:02 +01:00
kolaente 312648d7d6 fix: remove transaction control from File.Delete to prevent premature commit/rollback
File.Delete() had s.Commit() and s.Rollback() calls that could
prematurely commit or abort an outer transaction when using a shared
session. The caller is now responsible for transaction management.
2026-02-25 11:03:02 +01:00
kolaente 1167b08e70 fix: handle Begin() error in db.NewSession() instead of ignoring it 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 fd77e041a1 fix: add transaction begin to db.NewSession()
All sessions now start with an active transaction. This makes
multi-statement write operations atomic — if any step fails, all
changes are rolled back instead of leaving the database in an
inconsistent state.

Callers must call s.Commit() for writes to persist. s.Close()
auto-rollbacks uncommitted transactions.
2026-02-25 11:03:02 +01:00
Frederick [Bot] 2cb0b84602 [skip ci] Updated swagger docs 2026-02-25 09:39:04 +00:00
kolaente 2ef693a7cf test: add session lifecycle tests
Integration tests covering session creation on login, refresh token
rotation, session listing, deletion, and session invalidation on
password change.
2026-02-25 10:30:25 +01:00
kolaente 8ee069a2a3 feat: add session-based auth with refresh token rotation
- Login creates a server-side session and sets an HttpOnly refresh
  token cookie alongside the short-lived JWT
- POST /user/token/refresh exchanges the cookie for a new JWT and
  rotates the refresh token atomically
- POST /user/logout destroys the session and clears the cookie
- POST /user/token restricted to link share tokens only
- Session list (GET) and delete (DELETE) routes for /user/sessions
- All user sessions invalidated on password change and reset
- CORS configured to allow credentials for cross-origin cookies
- JWT 401 responses use structured error code 11 for client detection
- Refresh token cookie name constants annotated for gosec G101
2026-02-25 10:30:25 +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 a6bdeb67b0 feat: add jwtttlshort config key for session tokens
Adds ServiceJWTTTLShort (default 600s) to control the lifetime of
short-lived JWTs issued during token refresh. The existing jwtttl
and jwtttllong keys remain for session expiry and long sessions.
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 0c7c07b3b8 fix: preserve teams external_id type when renaming on mysql
Avoid using the generic renameColumn helper for this migration on MySQL because it renames columns as BIGINT. Handle the teams oidc_id -> external_id rename with a MySQL-specific CHANGE statement that keeps VARCHAR(250) and remains idempotent.
2026-02-24 14:29:49 +01:00
kolaente 3d6c527b64 fix: cast bucket_configuration to text in postgres catchup query
The new catchup migration compared the postgres JSON column with string literals using !=, which fails in migration smoke tests. Use a PostgreSQL-specific WHERE clause with ::text and cover it with unit tests.
2026-02-24 14:29:49 +01:00
kolaente 99ac3e65b8 fix: add comprehensive catchup for bucket and filter format migrations
Convert bucket_configuration filters regardless of bucket_configuration_mode and catch remaining view-level filter values still stored in the legacy string format. Includes focused tests for the bucket conversion helper logic.

Fixes #2172

Fixes #2188

Fixes #2202
2026-02-24 14:29:49 +01:00
kolaente 4acad97688 fix: make teams oidc_id rename migration idempotent
Use the shared renameColumn helper for non-SQLite databases and skip the SQLite table rebuild when oidc_id is already absent. This prevents failures after partial upgrades where the column was already renamed.

Refs #2172

Refs #2285
2026-02-24 14:29:49 +01:00
kolaente b1534f1cc8 fix: replace tx.Sync() with explicit ALTER TABLE in webhooks migration
tx.Sync() fails on PostgreSQL because it tries to reconcile primary key constraints/indexes, causing index-drop errors. Use explicit ALTER TABLE ADD COLUMN with existence checks instead.

Fixes #2215
2026-02-24 14:29:49 +01:00
kolaente a13ecbd3cc
fix: prevent browser from caching API responses
Without explicit Cache-Control headers, browsers may heuristically cache
API JSON responses. This causes stale data to be served on normal page
refresh (F5) — for example, projects newly shared with a team not
appearing until the user performs a hard refresh (Ctrl+Shift+R).

Add Cache-Control: no-store to all API responses via middleware and
configure the service worker's NetworkOnly strategy to explicitly bypass
the browser HTTP cache for API requests.

Ref: https://community.vikunja.io/t/team-members-cannot-see-project/1876
2026-02-24 10:37:49 +01:00
kolaente 249b651692
fix: treat archived TickTick tasks as done during import
TickTick uses status "2" (Archived) for completed tasks that were
subsequently archived. The import only checked for status "1"
(Completed), causing archived tasks to be imported as open despite
having a completion timestamp.

Closes go-vikunja/vikunja#2278
2026-02-23 14:52:20 +01:00
kolaente 1ccc8dce3a
fix: load file content before generating attachment preview
LoadFileByID() was called after the preview branch, so GetPreview()
received a nil io.Reader causing a panic in image.Decode.
2026-02-22 09:28:25 +01:00
kolaente d222d4502a fix: escape attachment download filename 2026-02-22 00:00:11 +01:00
kolaente c6370bb739 fix: fall back to application/octet-stream when the file has no mime type stored 2026-02-22 00:00:11 +01:00
kolaente 55c122fb42 feat: add repair-file-mime-types CLI command
Add a CLI command that finds all files in the database with no MIME type
set, detects it from the stored file content, and updates the database.
This is useful for backfilling MIME types on files created before MIME
detection was added on upload.
2026-02-22 00:00:11 +01:00
kolaente 4915f535d0 fix: add Content-Disposition attachment header to task attachment downloads
Set Content-Disposition to "attachment" instead of "inline" so browsers
trigger a file download with the correct filename. Also move shared
response headers (Content-Type, Content-Length, Last-Modified) out of
the S3-specific branch so they apply to both storage backends.
2026-02-22 00:00:11 +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
Frederick [Bot] 05ff67b681 chore(i18n): update translations via Crowdin 2026-02-21 01:09:54 +00:00
Frederick [Bot] 3ce8c5bdda [skip ci] Updated swagger docs 2026-02-19 13:35:37 +00: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 0026c74fb5 fix(tests): properly assert sort order including task47 in web tests
Restore full sort-order assertions that verify task47 appears at the
end of priority-sorted results. The previous fix incorrectly removed
the trailing `]` which meant the tests no longer verified the last
element in the sorted array.
2026-02-19 12:40:29 +01:00
kolaente c3e223887d fix(tests): update web test assertions for new task47 fixture
Remove trailing `]` from JSON substring assertions in priority sort
tests since task47 now appears after the previously-last task in the
sort order.
2026-02-19 12:40:29 +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
kolaente 6733ac4e22 test: add task #47 with reminders outside window for bug #2245
Add a second reminder to task 2 (in 2019, outside the test window)
and create task #47 with two reminders that straddle the test window
(2018-08-01 and 2019-03-01) but neither falls inside it. This exposes
the multi-row matching bug where separate EXISTS subqueries can match
different rows in the same sub-table.
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
Quiwy 6dbc108be8
feat(auth): allow LDAP authentication with anonymous bind (#2226)
As discussed on Matrix, Vikunja currently prevents users from using LDAP
authentication if the server allows anonymous binds (common in local
environments like YunoHost). The application would previously trigger a
`log.Fatal` if `AuthLdapBindDN` or `AuthLdapBindPassword` were left
empty in the configuration.

#### **How this fixes the problem:**

* **Validation:** Removed the strict requirement for Bind credentials in
`InitializeLDAPConnection`.
* **Connection Logic:** Updated `ConnectAndBindToLDAPDirectory` to
attempt an `UnauthenticatedBind` from the `go-ldap` library when no
credentials are provided.
* **Safety:** If a Bind DN is provided, the behavior remains unchanged
(authenticated bind).

#### **Testing:**

* Tested manually on a **YunoHost** instance by replacing the binary.
* Confirmed that Vikunja now successfully starts and authenticates users
via the local LDAP (localhost) without requiring a service account.
* Added a basic unit test in `pkg/modules/auth/ldap/ldap_test.go` to
ensure the initialization logic doesn't crash with empty credentials.

*Note: This is my first contribution to a Go project (assisted by an LLM
for syntax). Feedback on code style is more than welcome!*
2026-02-17 22:24:35 +01:00
John Starich b2715bb56d refactor: use Go idioms for running tests 2026-02-17 18:01:05 +01:00
John Starich 591a646f84 refactor: remove environment variable requirements for go test 2026-02-17 18:01:05 +01:00
Micah 31da3c4533
fix(migration): make migration from Microsoft Todo work for those with previously migrated wunderlist accounts (#2126) 2026-02-17 16:54:03 +01:00
Martin Lindvik e3695c17c6
feat: add Swedish for language selection (#2248)
The Swedish translations were finished on crowdin recently but I noticed
that the language selection was still missing so I went ahead and added
it.
2026-02-17 14:32:01 +00:00
kolaente 79d0942780 fix: use DelPrefix in upload avatar FlushCache to clear all cached sizes
FlushCache was using keyvalue.Del with the base key
(avatar_upload_{userID}) but the actual cache entries are stored with
size suffixes (avatar_upload_{userID}_{size}). The Del call targeted a
key that never existed, so cached avatars were never invalidated.

Switch to keyvalue.DelPrefix to delete all size variants at once,
matching the pattern the gravatar provider already uses correctly.
2026-02-13 09:31:28 +01:00
kolaente c93fa1b4ae test: add failing test for upload avatar FlushCache
The test populates the cache with multiple size-suffixed keys
and verifies that FlushCache removes all of them. Currently fails
because FlushCache uses Del with the base key which doesn't match
the actual size-suffixed cache keys.
2026-02-13 09:31:28 +01:00
Frederick [Bot] be4fb77981 chore(i18n): update translations via Crowdin 2026-02-10 01:25:29 +00:00
kolaente e90cb2631d fix(auth): remove unnecessary fields from JWT token payloads
Remove email, name, emailRemindersEnabled, and isLocalUser from user JWT
claims, and isLocalUser from link share JWT claims. These fields are never
used from the token - the backend always fetches the full user from the
database by ID, and the frontend fetches user data from the /user API
endpoint immediately after login.

Also simplify GetUserFromClaims to only extract id and username, and
remove the now-unnecessary email override in the frontend's
refreshUserInfo.
2026-02-08 21:30:07 +01:00
kolaente 0e05d1cc9d
fix(log): write each log category to its own file (#2206)
Previously, `makeLogHandler()` hardcoded "standard" as the logfile name
passed to `getLogWriter()`, causing all log categories (`database`,
`http`, `events`, `mail`) to write to `standard.log` instead of their
own files.

Add a logfile parameter to `makeLogHandler()` so each caller specifies
its category name, producing `database.log`, `http.log`, `echo.log`,
`events.log`, and `mail.log` as expected.

Fixes https://github.com/go-vikunja/vikunja/issues/2177
2026-02-08 15:22:58 +00:00
kolaente b6974ffcfd
feat: add UNSIGNED-PAYLOAD config option for S3-compatible stores (#2205)
Adds `files.s3.disablesigning` config option that sends
`UNSIGNED-PAYLOAD` instead of computing SHA256 hashes for S3 request
signing which fixes `XAmzContentSHA256Mismatch` errors with
S3-compatible providers like Ceph RadosGW and Clever Cloud Cellar

Resolves https://github.com/go-vikunja/vikunja/issues/2181
2026-02-08 15:03:19 +00:00
kolaente bcfde14b14 fix(backgrounds): stream unsplash download to temp file instead of memory
Use a temp file instead of io.ReadAll to avoid buffering the entire
Unsplash image in RAM, which could cause OOM with large images or
high maxsize configuration.
2026-02-08 15:31:25 +01:00
kolaente 0d395a9e5d refactor(files): remove redundant seek operations in writeToStorage
Move the seek-to-start into the local file branch only and simplify
contentLengthFromReadSeeker to seek to end then back to 0 instead of
saving/restoring the original position. This reduces the S3 upload
path from 5 seek operations to 2.
2026-02-08 15:31:25 +01:00
kolaente 56a0ea44cf fix(backgrounds): avoid integer overflow in max size calculation
Keep maxSize as uint64 and cast safely when comparing with
resp.ContentLength to avoid potential integer overflow.
2026-02-08 15:31:25 +01:00
kolaente ea78e87147 fix(dump): limit copy size to prevent decompression bombs
Use io.CopyN with a max size limit when extracting files from zip
archives during restore to prevent potential DoS via decompression bombs.
2026-02-08 15:31:25 +01:00
kolaente 19f6e4b7c9 fix(backgrounds): enforce max file size for unsplash downloads
Check Content-Length and use io.LimitReader to prevent OOM from
unexpectedly large unsplash responses before buffering into memory.
2026-02-08 15:31:25 +01:00
kolaente 41b511b322 fix(files): seek to start before writing for consistent behavior
Both local and S3 backends now seek to position 0 before writing,
ensuring consistent behavior regardless of the reader's current offset.
2026-02-08 15:31:25 +01:00
kolaente ab705d7d21 fix(dump): stream files during restore to avoid memory pressure
Use a temporary file instead of io.ReadAll when restoring attachments
from a dump. This prevents loading entire files into memory, which could
cause OOM errors for large attachments during restore.
2026-02-08 15:31:25 +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
kolaente 728a3e4f7b fix(files): require io.ReadSeeker for S3 uploads, remove temp file fallback
The S3 upload path used temp files (vikunja-s3-upload-*) to buffer
non-seekable readers. In Docker containers with restrictive permissions,
these temp files could not be created, causing "permission denied"
errors for avatar and background image uploads.

By changing the file storage API (Create, CreateWithMime,
CreateWithMimeAndSession, Save) to require io.ReadSeeker instead of
io.Reader, the temp file fallback is no longer needed and is removed.
This enforces at the type level that all callers provide seekable
readers, preventing this class of bug from recurring.

Closes go-vikunja/vikunja#2185
2026-02-08 15:31:25 +01:00
kolaente 7fce4694fa
refactor(db): extract testable ResolveDatabasePath function (#2193)
Add DatabasePathConfig struct and ResolveDatabasePath function that
takes all dependencies as parameters, making it easier to test path
resolution logic in isolation. Should also fix the reported cases.

Resolves #2189
2026-02-08 10:47:57 +00:00
kolaente acbf751ba0
feat(doctor): add user namespace detection and improved storage diagnostics (#2180)
This PR adds support for detecting and handling Linux user namespaces (commonly used in rootless Docker containers) and improves error diagnostics when file storage validation fails.

Docs PR: https://github.com/go-vikunja/website/pull/289

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 11:57:35 +01:00
Frederick [Bot] e6e7b26a6e [skip ci] Updated swagger docs 2026-01-30 14:14:52 +00: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
kolaente a89b1bed85
feat(doctor): add detailed file diagnostics for local storage (#2179)
When using local file storage, the doctor command now reports:
- Whether the files directory exists
- Directory permissions (octal mode)
- Directory owner and group with uid/gid (Unix)
- Ownership mismatch warning if Vikunja runs as a different user
- Total number of stored files and their combined size

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-30 10:23:39 +00:00
XiangCany d238385199
fix(files): make sure base directory exists when using local file system (#2166)
Resolves  #2162
2026-01-27 13:11:44 +01:00
kolaente 3aa1e90d7f
feat: add vikunja doctor command for diagnostic checks (#2165)
Add a new `vikunja doctor` CLI command that performs diagnostic checks.

Checks performed:

- **System**: Version, Go version, OS/arch, running user, working
directory
- **Configuration**: Config file path, public URL, JWT secret, CORS
origins
- **Database**: Connection test, server version
(SQLite/MySQL/PostgreSQL)
- **Files**: Storage path, writability, disk space (Unix only)
- **Optional services** (when enabled):
  - Redis: Connection ping
  - Typesense: Health endpoint
  - Mailer: SMTP connection
  - LDAP: Bind authentication test
  - OpenID Connect: Discovery endpoint for each configured provider
2026-01-27 09:12:31 +00:00
kolaente 28593e6460
fix: use dark shadows for email template in dark mode (#2155) 2026-01-26 15:46:44 +01:00
kolaente b21c9acb0d fix(routes): restore SPA routing after Echo v5 upgrade
In Echo v5, the 404 error for unmatched routes implements the
HTTPStatusCoder interface but is not a *HTTPError. This caused
the static middleware to fail to catch 404s and serve index.html
for SPA routes, leading to reloading SPA routes returning 404.

Caused by regression introduced in 9a61453e8.

Fixes #2149
Fixes #2152
2026-01-25 11:07:48 +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 4df8da549e fix(auth): scope query binding
Resolves https://github.com/go-vikunja/vikunja/issues/2146
2026-01-24 17:51:35 +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
renovate[bot] 843ef7218a
fix(deps): update module github.com/fclairamb/afero-s3 to v0.4.0 (#2087) 2026-01-15 16:21:16 +01:00
rhclayto c5969d9898
feat: add configurable gravatar-compatible base URL (#2083)
This adds the ability to set a base URL for a Gravatar-compatible avatar
service (Gravatar itself, or Libravatar, for instance). The default will
be www.gravatar.com, so nothing will change from current behaviour unless
the user explicitly configures another URL.

Resolves #2082
2026-01-13 14:58:08 +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 e085fcaef2
feat(migration/todoist): migrate from Sync API v9 to API v1 (#2072)
Migrates the Todoist migration module from the deprecated Sync API v9 to the new unified Todoist API v1.
2026-01-09 22:50:27 +01:00
kolaente 8987f0890a
fix(mail): disable queue when mailer disabled (#2069)
- Don't create the mail queue when the mailer is disabled to prevent
`SendMail()` from blocking indefinitely
- Add guard clause in `SendMail()` to return early when mailer is
disabled or queue is nil
- Add test to verify notifications don't block when mailer is disabled

This implements the changes from #1080 with the review feedback
addressed (using `package notifications` instead of `package
notifications_test`).


Closes #1080
2026-01-08 15:51:31 +01:00
kolaente 6c7a800bf4
fix: set log path before creating log handler (#2064)
Resolves #2020
2026-01-08 13:53:08 +00: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
kolaente 7365de257d
feat(files): validate file storage is writable on startup (#2053)
Adds startup validation that checks if the configured file storage is writable. To do this, Vikunja now tries to create a temporary file and clean it up afterwards.
2026-01-06 16:29:56 +01: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 6f0b685e38
fix: handle mixed-format bucket configurations in migration (#2033)
This change modifies the migration `20251001113831` to flexibly parse bucket configuration filters. 

This fixes this migration issue:

```
json: cannot unmarshal object into Go struct field bucketConfigurationCatchup.filter of type string
```

This occurred when a single `bucket_configuration` JSON array contained
mixed formats - some buckets with old string filters and some with
already-converted object filters.
2026-01-05 22:30:10 +01:00
kolaente c6fe4c1a6e fix(auth): retry up to three times when an auth provider cannot be reached
Resolves https://github.com/go-vikunja/vikunja/issues/2050
2026-01-05 21:50:40 +01: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 4afd223cd3
fix(files): upload should work with vhost style (#1994)
resolves https://github.com/go-vikunja/vikunja/issues/1905
2025-12-16 20:32:15 +00:00
Frederick [Bot] 3f48837b8c [skip ci] Updated swagger docs 2025-12-15 15:44:47 +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 62799c129b fix(caldav): do not assume the first element is the VTODO component
Cherry-Picked from https://github.com/go-vikunja/vikunja/pull/748#issuecomment-3649092134
2025-12-13 15:30:22 +01:00
Frederick [Bot] 803effbb8f chore(i18n): update translations via Crowdin 2025-12-12 00:59:39 +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
Frederick [Bot] 34c560eca8 chore(i18n): update translations via Crowdin 2025-12-10 00:59:00 +00:00
kolaente 542626fa7f
fix: deduplicate gravatar fetches to respect rate limits (#1955)
- avoid redundant concurrent Gravatar requests by coordinating fetches
per avatar cache key
- reuse cache lookups when requests are already cached and simplify
expiration checks
2025-12-08 22:42:58 +01:00
kolaente 0a78f7608a
feat: add --preserve-config flag to restore command (#1939)
Add a new `--preserve-config` flag to the restore command that allows
users to restore database and files from a dump while keeping their
existing configuration file untouched.
2025-12-07 21:44:45 +00:00
kolaente 112df4a752 fix(caldav): init logger in tests 2025-12-04 11:10:19 +01:00
kolaente da0822c3f4 feat(caldav): add more error logging 2025-12-04 10:54:31 +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
Frederick [Bot] cec8daba59 chore(i18n): update translations via Crowdin 2025-12-04 00:57:27 +00:00
Copilot 166da9763d
fix: handle MySQL 8 CREATE INDEX without IF NOT EXISTS support (#1903) 2025-11-28 15:57:54 +00: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
Frederick [Bot] f7e91f4b19 chore(i18n): update translations via Crowdin 2025-11-28 00:54:33 +00: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
kolaente 51512c1cb4
feat: migrate cypress e2e tests to playwright (#1739) 2025-11-27 16:34:48 +01:00
Frederick [Bot] 23a6ae19ea [skip ci] Updated swagger docs 2025-11-27 14:22:39 +00: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
kolaente a4aad79f53
fix: TickTick import (#1871)
This change fixes a few issues with the TickTick import:

1. BOM (Byte Order Mark) Handling: Added stripBOM() function to properly handle UTF-8 BOM at the beginning of CSV files
2. Multi-line Status Section: Updated header detection to handle the multi-line status description in real TickTick exports
3. CSV Parser Configuration: Made the CSV parser more lenient with variable field counts and quote handling
4. Test Infrastructure: Added missing logger initialization for tests
5. Field Mapping: Fixed the core issue where CSV fields weren't being mapped to struct fields correctly

The main problem was in the newLineSkipDecoder function where:
- Header detection calculated line skip count on BOM-stripped content
- CSV decoder was also stripping BOM and applying the same skip count
- This caused inconsistent positioning and empty field mapping

Rewrote the decoder to use a scanner-based approach with consistent BOM handling.

Resolves https://github.com/go-vikunja/vikunja/issues/1870
2025-11-25 22:32:39 +00:00
Frederick [Bot] fd9693cb6a chore(i18n): update translations via Crowdin 2025-11-24 00:59:57 +00:00
Weijie Zhao cfab3ff922
fix: update mention format to use custom HTML element with usernames (#1843) 2025-11-21 15:29:15 +01:00
rudd6617 b6dcde7f6c
chore(i18n): add Traditional Chinese locale and translations (#1839) 2025-11-20 13:42:31 +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
Frederick [Bot] 79aaa2a906 chore(i18n): update translations via Crowdin 2025-11-19 00:56: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 7729a3dcad
fix: HTML entity double-escaping in email notifications (#1829)
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-15 21:37:09 +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
Biagio00 5b42724205
fix(kanban): repeating tasks dates won't update when moved in done bucket (#1638) 2025-11-14 16:57:53 +00:00
Frederick [Bot] 7a0b55307b chore(i18n): update translations via Crowdin 2025-11-13 00:56:40 +00:00
Copilot 9c81afb7b2
feat: replace PNG-based initials avatar with SVG generation (#1802)
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-12 22:26:52 +00:00
kolaente dcfd096588
feat: allow setting dark custom logo
Resolves https://github.com/go-vikunja/vikunja/issues/1799
2025-11-12 21:07:01 +01:00
kolaente 8862b6f69d
fix(migration): return proper error message when request fails
Related to https://github.com/go-vikunja/vikunja/issues/1788
2025-11-12 20:25:17 +01:00
kolaente 9efc0baf50
fix(migration): add retry to migration request helper
Resolves https://github.com/go-vikunja/vikunja/issues/1788
2025-11-12 20:10:32 +01:00
Frederick [Bot] a153fc8c25 [skip ci] Updated swagger docs 2025-11-11 22:08:06 +00:00
Mithilesh Gupta 01a84dd2d5
feat: add comment count to tasks (#1771) 2025-11-11 23:00:05 +01:00
kolaente 22fc19cd24
fix: ignore filter_include_nulls from views
The filter_include_nulls property from the filter in a view would override the property set through the query string. Because we don't have a way in the UI to set this for filters in views, this makes the setting pretty opaque and unpredictable. Since we want to remove the nulls option anyways, we can just ignore it here.

Resolves https://github.com/go-vikunja/vikunja/issues/1781
2025-11-11 11:04:33 +01:00
Weijie Zhao 43a5ae1309
feat: enable user mentions in task description & comments (#1754) 2025-11-09 19:42:38 +01:00
Weijie Zhao 4fe0763010
fix: properly quote email sender names containing @ symbols (#1768)
When user names contain @ symbols, the email library fails to parse
the sender address format "Name @ Symbol via Vikunja <email@domain.com>".
This fix uses Go's net/mail.Address to properly format the sender
address according to RFC 5322, which automatically quotes names
containing special characters.

Fixes the error: "getting sender address: no FROM address set"
2025-11-07 11:44:24 +01:00
Panagiotis Papadopoulos f83bd60915
fix: 403 http error code on failed login (#1756) 2025-11-06 08:40:46 +01:00
Weijie Zhao bc1368abcc
feat: add S3 file storage support (#1688) 2025-11-06 08:37:04 +01:00
kolaente 541a38456e
chore(deps): update golangci-lint to 2.6.0 (#1737) 2025-10-31 17:28:52 +00:00
kolaente c8837aeaeb
fix(filters): support project filter in parentheses (#1647)
The filter regex pattern was not matching values inside parentheses correctly.
The lookahead pattern only allowed `&&`, `||`, or end-of-string after filter
values, but when filters are wrapped in parentheses like `( project = Filtertest )`,
the closing `)` appears after the value.

Fixed by adding `\)` to the lookahead pattern so it correctly handles closing
parentheses. This allows the project filter (and other filters) to work
properly when nested in parentheses.

- Added tests for project filters in parentheses (both frontend and backend)
- Backend tests confirm the backend already handled this correctly
- Frontend regex pattern now matches the backend behavior

Fixes #1645
2025-10-13 11:10:22 +02:00
kolaente 2a43f9b076 fix(reminders): refactor and check permissions when fetching task users 2025-10-09 13:33:27 +02:00
kolaente 9358954c90 fix: cleanup team memberships, assignments and subscriptions when users lose access to a project 2025-10-09 13:33:27 +02:00
kolaente 2dd36ad0a9
fix(sharing): make editing link share comments work
Resolves https://github.com/go-vikunja/vikunja/issues/1510
2025-10-09 10:53:18 +02:00
kolaente 7da2942ca6
fix: correctly set database path on windows (#1616) 2025-10-09 08:38:01 +00:00
kolaente db6b82a002
fix: task.comment.deleted triggers panic in event listener which sends webhook (#1621)
Co-authored-by: Gabriel <fossecruor@gmail.com>
2025-10-08 21:46:57 +00:00
kolaente d33e742961
chore: make condition simpler 2025-10-07 10:56:03 +02:00
kolaente 1b02f78eee
fix(filter): check date boundary after timezone conversion
Resolves https://github.com/go-vikunja/vikunja/issues/1605
2025-10-07 10:55:22 +02:00
kolaente 67ebd876d3
fix(views): migrate filter bucket configuration
Resolves https://github.com/go-vikunja/vikunja/issues/1580
2025-10-01 11:54:24 +02:00
kolaente ec89b08fd5
fix(attachments): extend upload file size to form data (#1577)
Resolves https://github.com/go-vikunja/vikunja/issues/1494
2025-09-30 22:23:07 +00:00
kolaente 31c1f98270
fix(caldav): remove METHOD:PUBLISH from caldav exports (#1576) 2025-09-30 18:16:07 +00:00
kolaente ff8e98e6e2
fix: process multiple reminders in the same time window (#1564)
Resolves https://github.com/go-vikunja/vikunja/issues/1550
2025-09-29 10:43:12 +02:00
Frederick [Bot] 48d202f3ce chore(i18n): update translations via Crowdin 2025-09-26 00:51:58 +00:00
Frederick [Bot] faa4231523 chore(i18n): update translations via Crowdin 2025-09-21 00:57:00 +00:00
Frederick [Bot] d53f4079ae chore(i18n): update translations via Crowdin 2025-09-19 00:53:14 +00:00
Frederick [Bot] 3397d88f0e chore(i18n): update translations via Crowdin 2025-09-18 00:51:01 +00:00
kolaente fb426a6e22
fix(webhook): actually fetch project before enriching details
This fixes a bug where the project is fetched before adding more details
through ReadOne since ReadOne does not fetch the project. In the normal
project reading flow through the api, this is done in the permission
check.

Resolves https://github.com/go-vikunja/vikunja/issues/1498
2025-09-17 17:04:39 +02:00
Frederick [Bot] 57b4e27278 chore(i18n): update translations via Crowdin 2025-09-17 00:51:22 +00:00
kolaente 0506b9215a
fix(filters): initialize task positions for saved filters (#1477) 2025-09-11 17:39:56 +02:00
kolaente f0cb752f2c
fix(task): preserve done timestamp when moving tasks between projects (#1470) 2025-09-11 17:07:43 +02:00
Frederick [Bot] f1de3a9c19 [skip ci] Updated swagger docs 2025-09-11 07:53:45 +00:00
kolaente e5e0413b70
fix(task): ensure done_at can never be set by user (#1461) 2025-09-11 07:45:42 +00:00
Frederick [Bot] 801ddf19e4 [skip ci] Updated swagger docs 2025-09-10 16:49:05 +00:00
kolaente db123674a7
feat: share logic for bulk update (#1456)
This change refactors the bulk task update logic so that it updates all fields a single task update would update as well.

Could be improved in the future so that it is more efficient, instead of calling the update function repeatedly. Right now, this reduces the complexity by a lot and it should be fast enough for most cases using this.

Resolves #1452
2025-09-10 16:40:59 +00:00
Frederick [Bot] 70175c31c2 [skip ci] Updated swagger docs 2025-09-04 16:33:42 +00:00
kolaente 1b5a9dbdea refactor: use helper function to check user local 2025-09-04 18:09:21 +02:00
kolaente b8afdcf62d fix(user): do not reject 2fa for local users
https://github.com/go-vikunja/vikunja/issues/1402
2025-09-04 18:09:21 +02:00
kolaente bd74733632
fix: show pagination controls for task comments (#1413)
Resolves https://community.vikunja.io/t/task-comment-pagination-in-1-0-0-rc1/3988
2025-09-04 16:04:05 +00:00
renovate[bot] a519312d55
fix(deps): update module github.com/threedotslabs/watermill to v1.5.1 (#1391) 2025-09-02 18:11:45 +00:00
Copilot 70ff047588
fix(avatar): recover gracefully from broken avatar cache (#1379) 2025-09-02 14:03:58 +00:00
kolaente 15ea38183c
fix: bypass Typesense in user export (#1385) 2025-09-02 15:49:17 +02:00
Frederick [Bot] 4b47f0c3cd chore(i18n): update translations via Crowdin 2025-09-02 00:54:00 +00:00
kolaente bd310f50ad
fix: show error when user list filter is empty (#1372) 2025-09-01 21:08:13 +00:00
kolaente ed04638726
fix(task): only load first comments page when loading comments with task
Resolves
https://community.vikunja.io/t/task-comment-pagination-in-1-0-0-rc1/3988
2025-08-31 21:58:46 +02:00
andreymal 1047e62978
feat(i18n): add pluralization rules for Russian (#1334) 2025-08-31 17:40:41 +00:00
Copilot c7a26d81fe
fix(auth): do not panic with invalid openid provider configuration (#1354) 2025-08-31 07:17:50 +00:00
Copilot 5ca637a7e6
feat(auth): add oauth require availability configuration on startup (#1358) 2025-08-30 22:15:20 +00:00
kolaente e29561e49c
fix: require publicurl when cors enabled (#1351) 2025-08-29 20:51:31 +00:00
kolaente fd8a36c9bb
fix: use correct filepath
Resolves https://github.com/go-vikunja/vikunja/issues/1345
2025-08-29 17:20:43 +02:00
Frederick [Bot] f5c6295f42 chore(i18n): update translations via Crowdin 2025-08-19 00:56:39 +00:00
kolaente e2e9b28d4e
feat(config): validate publicurl 2025-08-18 11:37:44 +02:00
Frederick [Bot] a5407e3f99 chore(i18n): update translations via Crowdin 2025-08-18 01:01:59 +00:00
kolaente 0039ec045e
fix(auth): fail when link share token is not parsable 2025-08-14 17:11:57 +02:00
kolaente 31611e13a4
chore(deps): update golangci-lint to 2.4.0 (#1291) 2025-08-14 10:50:49 +00:00
Frederick [Bot] 6a91862384 [skip ci] Updated swagger docs 2025-08-13 09:23:47 +00:00
kolaente a81a3ee0e5
feat!: rename right to permission (#1277) 2025-08-13 11:05:05 +02:00
Frederick [Bot] a81b546f99 chore(i18n): update translations via Crowdin 2025-08-07 01:02:44 +00:00
Dominik Pschenitschni feb17792e4
fix(user): persist status on email updates (#1084) 2025-08-04 14:07:00 +00:00
kolaente 0a44f53759
feat(caldav): return proper caldav intervals instead of FREQ=SECONDLY (#1230) 2025-08-04 13:41:28 +02:00
Frederick [Bot] 544960af65 [skip ci] Updated swagger docs 2025-08-03 20:46:06 +00:00
Copilot 7762d7746e
fix: make user data export download return 404 for nonexistent files (#1227) 2025-08-03 20:36:15 +00:00
Frederick [Bot] 5e65f223bc [skip ci] Updated swagger docs 2025-08-03 11:35:15 +00:00
kolaente ebaf4a0aa0 feat(settings): show extra settings links on user settings page 2025-08-03 13:25:32 +02:00
kolaente da0f6fb366 feat(auth): allow passing custom settings links to user account via openid claims 2025-08-03 13:25:32 +02:00
kolaente 9acba7a245 refactor: schedule user deletion 2025-08-01 17:06:02 +02:00
kolaente e5c860afec feat(plugins): allow plugins to register routes 2025-08-01 17:06:02 +02:00
kolaente b08b43953b feat(plugins): add rudimentary plugin system 2025-08-01 17:06:02 +02:00