Commit Graph
59 Commits
Author SHA1 Message Date
arnef b543b46ad2 fix(web): prefill birthday in contact form for imported contacts
Docker Image bauen und veröffentlichen / docker (push) Successful in 20m27s
v2026.9.1
2026-09-06 19:27:28 +02:00
arnef 8e946964ec fix(caldav): expand RRULE for recurring events 2026-09-06 15:49:09 +02:00
arnef f35121b3dc ci: drop release trigger from docker workflow 2026-09-03 19:09:38 +02:00
arnef 83483f5cb2 docs: add AGPL-3.0 license
Docker Image bauen und veröffentlichen / docker (release) Successful in 20m49s
Docker Image bauen und veröffentlichen / docker (push) Successful in 20m57s
v2026.9.0
2026-09-03 06:20:07 +02:00
arnef 5e3cc3513c chore(ci): migrate to Gitea Actions 2026-09-02 21:53:14 +02:00
arnef f34041d889 feat(web): show ICS subscription events in detail view 2026-09-02 20:02:48 +02:00
arnef e18c83b618 feat(web): add calendar event detail view 2026-09-01 19:10:17 +02:00
arnef 1f383878ab refactor(web): simplify address to single free-form value
Docker Image bauen und veröffentlichen / docker (push) Canceled after 26s
2026-09-01 07:02:40 +02:00
arnef c945e785c8 feat(web): split contact name into first and last name 2026-08-31 22:08:53 +02:00
arnef ea917b4148 feat(web): add file sorting to files browser 2026-08-31 05:57:35 +02:00
arnef 995441e917 docs: update versioning scheme in README 2026-08-30 20:55:21 +02:00
arnef 36e3fecc11 feat: allow dots in resource/folder names 2026-08-30 20:16:05 +02:00
arnef 6a95e27e31 chore: remove example config file 2026-08-30 19:59:04 +02:00
arnef e8c4dadfea chore: fmt code 2026-08-30 19:58:06 +02:00
arnef 2e43e32a9f refactor: clean up nidusctl CLI 2026-08-30 19:54:34 +02:00
arnef 9b9cbe2f6f refactor: update migration logic 2026-08-30 19:54:34 +02:00
arnef 0ee0874458 refactor: streamline session management 2026-08-30 19:54:34 +02:00
arnef b364bee265 refactor: simplify config validation 2026-08-30 19:54:34 +02:00
arnef dd77a61b18 refactor: unify config and data structures 2026-08-30 19:54:34 +02:00
arnef d3780e9e32 refactor: align import paths with module name 2026-08-30 18:51:45 +02:00
arnef e96a0b8c92 Merge pull request 'feat: implement unified directory structure with automatic migration for CalDAV and CardDAV' (#4) from bug/3 into main
Reviewed-on: #4

closes #3
2026-08-30 12:04:12 +00:00
arnef 0567fea729 docs: remove CHANGES_SUMMARY.md (superseded by AGENTS.md) 2026-08-30 13:57:16 +02:00
arnef 5af3a8508b docs: update AGENTS.md with current commands and features
- Add web-deps, nidusctl, and migrate commands to build section
- Document ICS/webcal subscriptions and Birthdays calendar
- Note Migrate() is idempotent
- Add additional database tables
- Include Docker/CI and database migration details
2026-08-30 13:54:55 +02:00
arnef 8d2d8d3972 fix: update web files root to use unified path structure 2026-08-30 13:04:40 +02:00
arnef 2fd39c8180 refactor: unify data directory structure
Unify user data directory from fragmented layout to consistent nested format:

- Move WebDAV from: data/files/<username> → data/<username>/files/
- Move CalDAV from: data/<username>/cal-<name> → data/<username>/calendars/<name>
- Move CardDAV from: data/<username>/card-<name> → data/<username>/addressbooks/<name>

Changes:
- internal/store/store.go: Update collectionPath() to map collection names
- internal/store/migrate.go: Add idempotent Migrate() method
- internal/store/migrate_test.go: Comprehensive migration tests
- internal/webdav/handler.go: Use new unified path structure
- cmd/server/main.go: Auto-run migration on startup
- tools/nidusctl/main.go: Add migrate subcommand
- Update tests to verify new structure

URL endpoints unchanged - only on-disk structure modified. All tests pass.
2026-08-30 12:15:31 +02:00
arnefandCopilot f463c01f0f Add a standalone docker-compose.yml example using the pre-built image
Complements the existing "build: ." example (for a full checkout) with
a minimal compose file that just pulls git.arnef.de/arnef/nidus:latest,
for users who only want to run the container without cloning the repo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 22:00:20 +02:00
arnefandCopilot cd0761c9d1 Remove TLS/Reverse proxy and Dependencies sections from README
Both were low-value docs (TLS config is already in the Configuration
reference table; Go/JS dependencies are visible in go.mod and
web/package.json) that just added noise.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 21:59:05 +02:00
arnefandCopilot ddc9f4ddee Align Docker release workflow with the ebooks project's registry setup
Docker Image bauen und veröffentlichen / docker (push) Successful in 20m29s
Switch from a dynamically-derived registry/GITHUB_TOKEN login to the
same pattern already proven in the ebooks project: a fixed
git.arnef.de/arnef/nidus registry/image, docker/login-action with
REGISTRY_USERNAME/REGISTRY_PASSWORD repo secrets, docker/metadata-action
for tag/label generation (version, major.minor, latest, short SHA), and
a final buildx/system prune cleanup step. Also add a manual
workflow_dispatch trigger.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
v1.0.0
2026-08-21 20:02:23 +02:00
arnefandCopilot f7375d7eba Fix Docker build, ship nidusctl in the image, document user setup via compose
Docker release / docker (push) Failing after 26s
- Bump the builder image to golang:1.25-alpine to match go.mod's "go
  1.25.0" requirement — the previous 1.22 base failed to build at all.
- Stop COPYing the git-ignored config.yaml (which doesn't exist in a
  fresh checkout, breaking CI/release builds); ship config.example.yaml
  as the image's default instead, same as it's already overridden via
  a volume mount in docker-compose.yaml.
- Build and include the nidusctl binary in the runtime image so users
  can create their first user/calendars/address books straight from a
  running container via `docker compose exec`, without installing Go.
- README: document that Docker workflow, mention the pre-built
  multi-arch images published by the new release CI, and drop the
  "Client configuration" section — how to configure a given DAV client
  is out of scope for this project's docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 16:37:44 +02:00
arnefandCopilot 63b59a73fe Add CI workflow to build and publish multi-arch Docker images on tag/release
Builds linux/amd64 + linux/arm64 images via Buildx/QEMU whenever a "v*"
tag is pushed or a release is published, and pushes them (tagged with
the version and "latest") to this repository's own container registry.
Written in plain GitHub Actions syntax, which Gitea Actions runs as-is.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 16:37:29 +02:00
arnefandCopilot 26044366de Update README: project name, AI note, and current Web UI feature set
- Explain the "nidus" name (Latin for "nest").
- Note that AI coding assistants were used during development and that
  not everything has been fully reviewed yet.
- Bring the Web UI section up to date with the Files, Contacts,
  Calendar, and Account areas that already existed but weren't
  documented.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 09:16:08 +02:00
arnefandCopilot 9f8bfba420 Improve mobile layout of the dashboard, contacts, and calendar pages
- Layout nav wraps gracefully on narrow screens instead of overflowing
  (username truncates, links/logout reflow).
- Dashboard resource cards and share rows wrap instead of clipping
  when names/usernames are long.
- Contacts list hides secondary columns (organization/email below
  md, phone below sm) so the name and actions stay usable on phones.
- Calendar month/week grids get a horizontally scrollable wrapper
  with a sane minimum width and smaller cell padding on mobile so the
  7-day grid stays legible instead of being squeezed unreadably thin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 09:16:01 +02:00
arnefandCopilot b2f39bd1f6 Add delete, inline preview, and mobile list layout to the file browser
- DELETE /files/{path} recursively removes a file/folder (root itself
  can't be deleted, 404 on missing paths); wired to a new "Delete"
  action in the web UI with a confirmation prompt.
- Files now open inline (Content-Disposition: inline) so browsers can
  play/preview natively-supported types (video, audio, images, PDF)
  directly instead of always forcing a download. A separate
  "Download" link (?download=1) still forces a save-as.
- Narrow screens get a stacked card list (name, size, modified date,
  download/delete actions) instead of a squeezed table, so nothing is
  hidden or requires horizontal scrolling; the table layout is kept
  unchanged for sm+ screens.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 09:15:40 +02:00
arnefandCopilot d0e1dab837 Use display name instead of username for shared calendar owners
Add db.DisplayName(username), returning the user's display name if
set, otherwise the raw username. Use it everywhere a shared calendar's
owner is shown: the web calendar legend ("shared by ..."), the "new
event"/edit calendar select label, the dashboard's "Shared with you"
list, and the CalDAV-side description/name-collision disambiguation
("Name (Owner)") shown to CalDAV clients like DAVx5.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 06:41:51 +02:00
arnefandCopilot 8b10386c91 Add account management page
Add a new /web/account page, reachable by clicking the username in
the nav, with two forms: updating display name/email, and changing
the password (requires the current password, min 8 chars, confirm
match). Add db.SetProfile to persist display name/email.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 06:32:18 +02:00
arnefandCopilot 1c039e6396 Add week view to web calendar with Month/Week toggle
Extract the shared per-calendar event-collection logic out of
buildMonthView into collectCalendarEvents, keyed by day so it can feed
either the existing 42-cell month grid or a new 7-cell week grid.

Add templates.WeekDay/WeekViewData, a WeekView templ, and a
viewSwitcher component shown in both views' headers to switch between
Month and Week. Add buildWeekView, handleCalendarWeek, parseWeekStart,
and mondayOf in internal/web/calendar.go, and register the new
/calendar/week route.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-21 06:19:59 +02:00
arnefandCopilot 28c55804d8 Fix unreadable event text when a calendar's color is white/very light
The month view rendered each event's text (and its little leading dot)
in the calendar's own color, on a very lightly tinted (~13% opacity)
background of that same color. For a light color like white or pale
yellow, the text ended up effectively invisible against its own
near-white background.

internal/web/templates/calendar.templ: add eventTextColor(hex), which
darkens colors above a perceived-luminance threshold (keeping the hue,
e.g. white -> a mid gray, pale yellow -> olive) while leaving already-
legible colors untouched; used for both the event text and its leading
dot. Also add a subtle ring to the event dot and to the calendar legend's
color swatch so a white/near-white swatch stays visible against the
page's white background, independent of the text-color fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 23:27:19 +02:00
arnefandCopilot b58a6ba4ea Disambiguate shared calendars with a name colliding with the owner's
If a calendar shared with a user has the same name as one of the user's
own calendars (or as another share from a different owner), DAVx5 would
show two calendars with an identical, indistinguishable title. The
shared calendar's CalDAV display name (Calendar.Name, exposed via
displayname/DAVx5's calendar list) now gets the owning user's username
appended in parentheses in that case, e.g. "personal (alice)" — the
underlying calendar path/URL is unaffected, and a user's own calendars
are never renamed (only shared ones can collide with something else).

internal/caldav/backend.go: calendarMeta split into calendarMeta (own
calendars, unchanged name) and calendarMetaNamed (explicit display name);
new sharedDisplayName(requester, owner, realName) checks requester's own
calendars and other shares for a collision. Wired into both
ListCalendars and GetCalendar (the latter used by direct
PROPFIND/REPORT against a shared calendar's own URL).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 23:20:17 +02:00
arnefandCopilot 213b22f821 Fix 500 on REPORT for recurring events with Windows timezone IDs
DAVx5 reported a 500 Internal Server Error on a time-range REPORT
against a calendar containing a recurring event whose DTSTART/DTEND used
a Windows-style TZID (e.g. "W. Europe Standard Time", as commonly written
by Outlook/Exchange and some Thunderbird/Lightning setups) instead of an
IANA zone name.

go-ical resolves TZID via a plain time.LoadLocation call, which only
understands IANA names. A simple decode of such an event succeeds (RRULE
dates aren't parsed eagerly), but expanding its recurrence - which
go-webdav's caldav.Filter does for every time-range REPORT - calls
Component.RecurrenceSet, which does call time.LoadLocation(tzid) and
fails with "ical: error parsing start time: unknown time zone ...".

Add internal/icalfix, a small shared helper that rewrites recognized
Windows timezone identifiers (TZID parameters and VTIMEZONE TZID: lines)
to their IANA equivalent in raw ICS bytes before decoding. Wire it into
every ical.NewDecoder call site: internal/caldav/backend.go's
decodeObject (fixes the reported bug), internal/web/calendar.go's event
rendering/ICS import, and internal/icssub's remote feed fetching, so a
subscribed feed with the same issue doesn't hit it either.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 22:23:11 +02:00
arnefandCopilot 1707ab4060 Add ICS/webcal HTTP subscription calendars
Users can now subscribe to a remote ICS/webcal feed from the dashboard
(name + color), the same way they set up a real calendar or the virtual
Birthdays calendar. Subscriptions are read-only, per-user, and share the
"/cal/home/<name>/" namespace with real calendars and "birthdays" (name
collisions are rejected in both directions).

- internal/db: new ics_subscriptions table + CRUD (internal/db/ics.go);
  CreateCalendarWithColor checks for a colliding subscription name.
- internal/icssub: shared HTTP-fetch + TTL cache (15 min) for remote ICS
  calendars, with webcal:// -> https:// rewriting and stale-on-error
  fallback, used by both the web UI and the CalDAV backend.
- internal/web: dashboard "Subscribe to an ICS/webcal calendar" form,
  color picker, delete button (internal/web/ics.go,
  templates/dashboard.templ); month view renders subscription events in
  their chosen color, read-only (internal/web/calendar.go).
- internal/caldav: subscriptions are exposed as read-only calendars
  (internal/caldav/ics.go) - listed in PROPFIND, events served via
  GET/REPORT, PUT/DELETE on individual events rejected with 403, but
  DELETE on the calendar itself unsubscribes; calendar-color is injected
  the same way as for real calendars and Birthdays.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 22:10:49 +02:00
arnefandCopilot 2cb6e98db6 Show birth year instead of age in birthday event titles
Title is now "🎂 Name (BirthYear)" instead of "🎂 Name (Age)".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 20:15:01 +02:00
arnefandCopilot b451f1a76e Make birthdays calendar color configurable and expose it via CalDAV
- Add a per-user birthday_calendars table (color, cascade-deletes with
  the user) and GetBirthdayCalendarColor/SetBirthdayCalendarColor in
  internal/db, plus a reservedCalendarNames guard ("birthdays") in
  CreateCalendarWithColor so no real calendar can collide with the
  synthetic one, whether created via the web UI, nidusctl, or CalDAV
  MKCALENDAR.
- Add a "Birthdays" virtual resource card to the dashboard (color
  picker only, no delete/share controls) backed by a new
  ResourceCard.Virtual flag and POST /web/resources/birthdays/color
  handler.
- Extract the birthday-parsing/generation logic shared by the web
  calendar view and CalDAV into internal/birthdays (ParseBirthday,
  Collect, OccurrenceDate, Summary) instead of duplicating it.
- Expose the Birthdays calendar over real CalDAV in
  internal/caldav/backend.go + birthdays.go: it's always listed for
  every user, generates one VEVENT per (contact, year) for a rolling
  window (current year -2..+8) with "🎂 Name (Age)" titles, is
  read-only (Put/Delete/DeleteCalendar all return 403), and its
  Apple/DAVx5 calendar-color is injected from the same per-user
  setting used by the dashboard/web view.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 20:09:59 +02:00
arnefandCopilot cd96b365d0 Add virtual Birthdays calendar computed from contacts
The combined month view now always includes a read-only "Birthdays"
calendar (ref "@birthdays"), generated on the fly from every contact's
BDAY field across the user's address books — no calendar objects are
stored for it. Each birthday appears as an all-day event titled
"🎂 Name (Age)" (age omitted if BDAY has no year, e.g. "--MM-DD"), colored
pink, and links to the contact's edit page instead of an event editor.
Handles both "YYYY-MM-DD"/"YYYYMMDD" and year-less vCard BDAY formats,
and skips Feb 29 birthdays in non-leap years.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 19:27:55 +02:00
arnefandCopilot adabfd22ca Add ICS import/export and shared calendar support to calendar UI
- Combine own and shared calendars into a single /web/calendar month
  view, each event colored per its source calendar.
- New event creation now uses a calendar <select> (only writable
  calendars offered); editing keeps the event's original calendar fixed.
- Shared calendars use an "owner~name" reference in URLs, resolved via
  resolveCalRef which also enforces read/write permissions from
  calendar_shares (read-only shares can view/export but not edit/delete).
- Add per-calendar ICS export (single event and export-all) and import,
  splitting multi-VEVENT uploads into individual stored objects while
  preserving VTIMEZONE definitions and source VERSION/PRODID.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 18:58:04 +02:00
arnefandCopilot cd2cfa6c06 Add web UI for CalDAV calendars (web/calendar)
- List of the user's own calendars (web/calendar)
- Month view per calendar with a Monday-first 6-week grid, showing
  all-day and timed events, colored dot matching the calendar's color
- Navigation between months (prev/next/today) via query params
- Create/edit/delete events: title, description, location, all-day
  toggle, start/end date+time
- Clicking a day cell prefills a new all-day event on that date; the
  "New event" button defaults to a one-hour slot starting next full hour
- Deleting an event or invalid time ranges (end before/equal start) are
  validated with inline error messages
- Verified round-trip with the real CalDAV protocol handler (events
  created via the web UI are correctly visible to a REPORT query)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 16:58:59 +02:00
arnefandCopilot 3fa0297715 Add web UI for WebDAV files and CardDAV contacts
Files browser (web/files):
- Alphabetical listing grouped folders-then-files
- Upload via button or drag-and-drop, including whole folders
- Folder creation
- Fixed layout for long filenames without spaces (table-fixed + break-all)

Contacts (web/contacts):
- Full CRUD for CardDAV contacts (create/edit/delete)
- VCF import (multi-card files) and export (single/all)
- Photo upload with preview, birthday field
- TYPE labels (private/business) for phone, email, address
- Repeatable multi-input rows for phones/emails/addresses instead of textareas
- Sanitizes a known malformed TYPE parameter pattern from some vCard
  exporters (e.g. Nextcloud Contacts) that otherwise caused phone numbers
  to be silently dropped on import

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 15:06:39 +02:00
arnefandCopilot 65cd24a192 Show plain calendar name for shared calendars, drop ~owner prefix
DAVx5/CalDAV clients displayed a shared calendar's DAV:displayname as
"alice~work" (the synthetic owner~name local identifier nidus uses
internally to keep a shared calendar's URL unique per subscriber).
That internal name is still needed for routing (it's still what appears
in the calendar's URL path), but there's no reason to show it to the
end user as the calendar's label.

calendarMeta() now always sets caldav.Calendar.Name to the plain
calendar name (e.g. "work"), while Path still uses the "owner~name"
local name. The web UI already showed the plain name via a separate
"shared by <owner>" entry, so it's unaffected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 10:09:41 +02:00
arnefandCopilot 5f72e58ad3 Fix calendar-color not applied by DAVx5 (duplicate propstat)
DAVx5 explicitly requests the Apple calendar-color property by name.
go-webdav's stock property map doesn't know it, so it always emitted a
404 Not Found propstat for it. Our injection was adding a *second*,
200 OK propstat with the color into the same <response>, producing a
response with two propstats for the same property name — invalid
multistatus that real clients (dav4jvm/DAVx5) resolved by preferring the
404, so the color was silently ignored.

Now the bogus 404-only propstat for calendar-color is stripped before
injecting the 200 OK one, leaving a single, valid propstat per response.
Verified against Nextcloud's documented behavior (single propstat with
the color) and against a live PROPFIND matching DAVx5's actual request
shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 07:04:49 +02:00
arnefandCopilot ebbc7a2a2b Add calendar color support (DAVx5 calendar-color)
Calendars can now have a color (hex, e.g. #3b82f6) that DAVx5 and other
CalDAV clients pick up via the Apple/dav4jvm calendar-color property.

- db: add calendars.color column with migration for existing DBs;
  CreateCalendarWithColor, SetCalendarColor, GetCalendarColor;
  ListCalendars now returns []Calendar{Name, Color} instead of []string
- caldav: since go-webdav's caldav.Backend interface has no extension
  point for vendor properties, wrap the handler with a response-rewriting
  middleware that injects <calendar-color xmlns="http://apple.com/ns/ical/">
  into PROPFIND responses for calendars that have a color set
- web: color picker on the "New calendar" form and an inline color swatch/
  picker on each calendar card (calendars only, not address books)
- nidusctl: `calendar create --color` flag and a new `calendar color`
  subcommand; `calendar list` now also prints the color if set

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-20 06:43:56 +02:00
arnefandCopilot 37399d1ceb Stack dashboard form fields on mobile instead of breaking layout
The new-calendar/new-address-book creation forms and the per-resource
share form used flex-row layouts that overflowed on narrow viewports.
Switch to flex-col on mobile, flex-row from sm: up, with inputs/buttons
going full-width below the sm breakpoint.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-19 12:57:05 +02:00