Skip to content

feat: links#174

Open
pmig wants to merge 6 commits intomainfrom
pmig/dev-454-datei-public-sharing-links
Open

feat: links#174
pmig wants to merge 6 commits intomainfrom
pmig/dev-454-datei-public-sharing-links

Conversation

@pmig
Copy link
Copy Markdown
Contributor

@pmig pmig commented May 4, 2026

No description provided.

pmig added 2 commits May 4, 2026 18:01
Signed-off-by: Philip Miglinci <pmig@glasskube.com>
Signed-off-by: Philip Miglinci <pmig@glasskube.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new “public links” sharing feature across the stack (OpenAPI + Go backend + DB schema + Angular UI), enabling authenticated users to create/manage share links and unauthenticated viewers to browse/download shared dateien via an access token (optionally protected by a code).

Changes:

  • Added Link domain (event-sourced) with projections, SQLC queries, and server endpoints for link CRUD + scope-checked public access.
  • Extended OpenAPI spec and regenerated Go/Angular API bindings for link operations and public link browsing/downloading.
  • Added Angular UI for link management and a public link viewer route, plus dashboard actions to create/add items to links.

Reviewed changes

Copilot reviewed 76 out of 76 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pkg/api/zz_generated.models.go Adds OpenAPI-generated models/params/enums for links and public-link error handling.
internal/server/zz_generated.server.go Adds generated server interface/routes/response objects for link and public link endpoints.
internal/server/server.go Wires link.Service into the main server via new repo dependency.
internal/server/endpoints_public_links.go Implements public (unauthenticated) list/download endpoints for links.
internal/server/endpoints_links.go Implements authenticated link management endpoints (list/create/update/revoke/rotate/add/remove).
internal/link/service.go Implements link business logic (owner operations + public access verification/scope checks).
internal/link/repository.go Adds link repository backed by generic event-sourcing repository + projection updates.
internal/link/projections.go Projection updaters for link events (link_projection + link_datei_projection).
internal/link/mapping.go Maps link projections/aggregates into API Link responses (including counts/dateien).
internal/link/events.go Defines link domain events + registers them for deserialization; adds link event store wiring.
internal/link/aggregate.go Defines link aggregate commands/state and name validation.
internal/db/zz_generated.models.go Adds SQLC-generated DB models for link tables.
internal/db/zz_generated_schema.sql Updates schema snapshot used by SQLC generation to include link tables.
internal/db/sqlc.yaml Registers new SQL query files (link + link_event) for SQLC generation.
internal/db/migrations/sql/0_initial_schema.up.sql Adds link_event store, link_projection, and link_datei_projection schema + indexes/constraints.
internal/db/migrations/sql/0_initial_schema.down.sql Drops link tables on rollback.
internal/db/link.sql.go SQLC-generated Go queries for link projections, scope checks, and content counts.
internal/db/link.sql SQL queries for link projections, scope checks, and content counts.
internal/db/link_event.sql.go SQLC-generated Go queries for link event store operations.
internal/db/link_event.sql SQL queries for link event store operations.
internal/db/datei.sql.go Adds SQLC-generated helper query CountDateiProjectionsByIDs.
internal/db/datei.sql Adds CountDateiProjectionsByIDs query to validate link datei IDs.
internal/dateierrors/errors.go Adds new link/public-share error sentinels.
internal/cmd/serve/serve.go Wires link event store/repository into server startup.
frontend/src/util/format-bytes.ts Adds byte formatting utility for public link viewer.
frontend/src/util/download.ts Adds a shared “trigger browser download” helper.
frontend/src/app/services/links.service.ts Adds Angular service wrapping generated API calls for link operations and public viewer calls.
frontend/src/app/services/auth.service.ts Excludes /api/v1/public from authenticated-route logic so token interceptor doesn’t attach auth.
frontend/src/app/public-links/public-link-viewer/public-link-viewer.component.ts Adds public link viewer component logic (unlock code flow, listing, navigation, download).
frontend/src/app/public-links/public-link-viewer/public-link-viewer.component.html Adds viewer UI for public link browsing and code entry.
frontend/src/app/nav/nav.component.ts Adds RouterLinkActive import for nav highlighting.
frontend/src/app/nav/nav.component.html Adds nav entries for Files + Links using routerLink/routerLinkActive.
frontend/src/app/links/links-list/links-list.component.ts Adds authenticated “Links” page listing active/expired/revoked links with actions.
frontend/src/app/links/links-list/links-list.component.html Adds links table UI with share/copy/edit/rotate/revoke actions.
frontend/src/app/links/link-picker-dialog/link-picker-dialog.component.ts Adds dialog to pick an existing link to add selected dateien into.
frontend/src/app/links/link-picker-dialog/link-picker-dialog.component.html Adds picker dialog UI.
frontend/src/app/links/link-form-dialog/link-form-dialog.component.ts Adds create/edit dialog for link name/code/expiry and removing shared items.
frontend/src/app/links/link-form-dialog/link-form-dialog.component.html Adds create/edit dialog UI.
frontend/src/app/dashboard/dashboard.component.ts Adds dashboard actions to create links / add selection to links; uses new download helper.
frontend/src/app/dashboard/dashboard.component.html Adds selection toolbar + row menu entries for public link actions.
frontend/src/app/app.routes.ts Adds public route /share/:accessToken to load public link viewer.
frontend/src/app/app-logged-in.routes.ts Adds authenticated route /links to load links list page.
frontend/src/api/models/update-link-request.ts Generated Angular model for UpdateLinkRequest.
frontend/src/api/models/public-link-error-response.ts Generated Angular model for public link 403 error payload.
frontend/src/api/models/list-public-link-dateien-response.ts Generated Angular model for public public link listing response.
frontend/src/api/models/list-links-response.ts Generated Angular model for authenticated link listing response.
frontend/src/api/models/link.ts Generated Angular model for Link.
frontend/src/api/models/create-link-request.ts Generated Angular model for CreateLinkRequest.
frontend/src/api/models/add-datei-to-link-request.ts Generated Angular model for AddDateiToLinkRequest.
frontend/src/api/models.ts Exports new generated Angular models.
frontend/src/api/functions.ts Exports new generated Angular API functions for link operations.
frontend/src/api/fn/operations/update-link.ts Generated Angular client for PATCH /api/v1/links/{id}.
frontend/src/api/fn/operations/rotate-link-access-token.ts Generated Angular client for POST /api/v1/links/{id}/rotate.
frontend/src/api/fn/operations/revoke-link.ts Generated Angular client for DELETE /api/v1/links/{id}.
frontend/src/api/fn/operations/remove-datei-from-link.ts Generated Angular client for DELETE /api/v1/links/{id}/dateien/{dateiId}.
frontend/src/api/fn/operations/list-public-link-dateien.ts Generated Angular client for GET /api/v1/public/links/{accessToken}/dateien.
frontend/src/api/fn/operations/list-links.ts Generated Angular client for GET /api/v1/links.
frontend/src/api/fn/operations/download-public-link-datei.ts Generated Angular client for GET public download endpoint.
frontend/src/api/fn/operations/create-link.ts Generated Angular client for POST /api/v1/links.
frontend/src/api/fn/operations/add-datei-to-link.ts Generated Angular client for POST /api/v1/links/{id}/dateien.
api/paths/public_links_token_dateien.yaml Adds OpenAPI path for public listing endpoint.
api/paths/public_links_token_dateien_dateiid_download.yaml Adds OpenAPI path for public download endpoint.
api/paths/links.yaml Adds OpenAPI path for link list/create endpoints.
api/paths/links_id.yaml Adds OpenAPI path for link update/revoke endpoints.
api/paths/links_id_rotate.yaml Adds OpenAPI path for link access token rotation.
api/paths/links_id_dateien.yaml Adds OpenAPI path for adding a datei to a link.
api/paths/links_id_dateien_dateiid.yaml Adds OpenAPI path for removing a datei from a link.
api/openapi.yaml Registers new authenticated and public link routes in the OpenAPI spec.
api/components/schemas/UpdateLinkRequest.yaml Adds schema for UpdateLinkRequest.
api/components/schemas/PublicLinkErrorResponse.yaml Adds schema for public link code errors.
api/components/schemas/ListPublicLinkDateienResponse.yaml Adds schema for public link listing response.
api/components/schemas/ListLinksResponse.yaml Adds schema for link list response.
api/components/schemas/Link.yaml Adds schema for Link object.
api/components/schemas/CreateLinkRequest.yaml Adds schema for CreateLinkRequest.
api/components/schemas/AddDateiToLinkRequest.yaml Adds schema for AddDateiToLinkRequest.
AGENTS.md Updates contributor guidelines (no empty/placeholder CSS/SCSS files).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +81 to +86
return DownloadPublicLinkDatei200ApplicationoctetStreamResponse{
Body: result.Reader,
Headers: DownloadPublicLinkDatei200ResponseHeaders{
ContentDisposition: fmt.Sprintf(`attachment; filename="%v"`, result.ContentFileName),
ContentType: result.ContentType,
},
Comment on lines +104 to +119
<ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let row" class="w-12 text-right">
<button matIconButton (click)="download(row)" aria-label="Open or download">
<mat-icon>{{ row.isDirectory ? 'arrow_forward' : 'download' }}</mat-icon>
</button>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="download(row)"
class="cursor-pointer"
></tr>
Comment thread internal/link/projections.go
Comment on lines +73 to +80
func updateProjectionForLinkDateiRemoved(ctx context.Context, q *db.Queries, event *LinkDateiRemovedEvent) error {
if err := q.DeleteLinkDateiProjection(ctx, db.DeleteLinkDateiProjectionParams{
LinkID: event.ID,
DateiID: event.DateiID,
}); err != nil {
return fmt.Errorf("failed to delete link_datei_projection: %w", err)
}
return nil
Comment thread internal/link/service.go
Comment on lines +103 to +126
func (s *Service) ListLinks(ctx context.Context) (*ListLinksOutput, error) {
userID := authn.RequireContext(ctx).UserID
queries := db.New(s.db)

projections, err := queries.ListLinkProjectionsByOwner(ctx, userID)
if err != nil {
return nil, err
}

items := make([]api.Link, 0, len(projections))
for i := range projections {
dateien, err := queries.ListDateienByLink(ctx, projections[i].ID)
if err != nil {
return nil, err
}
counts, err := queries.CountLinkContents(ctx, projections[i].ID)
if err != nil {
return nil, err
}
mapped := MapProjectionToAPI(&projections[i], dateien, int(counts.FileCount), int(counts.FolderCount))
if mapped != nil {
items = append(items, *mapped)
}
}
Comment thread internal/link/service.go
Comment on lines +243 to +254
func (s *Service) RemoveDateiFromLink(ctx context.Context, linkID, dateiID uuid.UUID) error {
userID := authn.RequireContext(ctx).UserID

agg, err := s.loadOwnedAggregate(ctx, linkID, userID)
if err != nil {
return err
}

if err := agg.RemoveDatei(dateiID, time.Now()); err != nil {
return dateierrors.ErrLinkDateiNotShared
}
return s.repository.Save(ctx, agg)
Comment on lines +230 to +238
ref.afterClosed().subscribe((link) => {
if (!link) return;
const shareUrl = this.linksService.buildShareUrl(link.accessToken);
const snackRef = this.snackBar.open(`Public link "${link.name}" created`, 'Copy link', {
duration: 6000,
});
snackRef.onAction().subscribe(() => {
void navigator.clipboard.writeText(shareUrl);
});
Comment on lines +133 to +137
if (v.code.trim() !== '') {
body.code = v.code;
} else {
body.clearCode = true;
}
Signed-off-by: Philip Miglinci <pmig@glasskube.com>
Comment thread api/paths/links.yaml
Comment on lines +36 to +42
schema:
type: object
properties:
message:
type: string
required:
- message
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be in schemas maybe, also this is the first time we need a schema for an error

Comment on lines +37 to +45
protected readonly listResource = resource({
params: () => ({}),
loader: () => firstValueFrom(this.linksService.listLinks()),
});

// Only active links accept new dateien — revoked links are terminal.
protected readonly availableLinks = computed(() =>
(this.listResource.value() ?? []).filter((l) => !l.revokedAt),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this filtering can be done in the resource

Comment on lines +195 to +204
const ms = expiresAt.getTime() - Date.now();
if (ms <= 0) return 'Expired';
const minutes = Math.floor(ms / 60_000);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days >= 30) return `Expires on ${expiresAt.toLocaleDateString()}`;
if (days >= 1) return `Expires in ${days} ${days === 1 ? 'day' : 'days'}`;
if (hours >= 1) return `Expires in ${hours} ${hours === 1 ? 'hour' : 'hours'}`;
if (minutes >= 1) return `Expires in ${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`;
return 'Expires in less than a minute';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should definitely use a library for this. we used dayjs and datefns in the past. datefns should be preferred because it uses a more modular and modern architecture

>chevron_right</mat-icon
>
}
<button mat-button (click)="navigateToBreadcrumb(i)" [disabled]="last">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it looks strange that the last item is always grey. especially if there is only one item it looks very strange

pmig added 2 commits May 6, 2026 15:09
…blic-sharing-links

Signed-off-by: Philip Miglinci <pmig@glasskube.com>
Signed-off-by: Philip Miglinci <pmig@glasskube.com>
Copilot AI review requested due to automatic review settings May 6, 2026 15:57
@pmig
Copy link
Copy Markdown
Contributor Author

pmig commented May 7, 2026

PR review progress

Resolved (28 threads)

Trivial / cleanup

Bug fixes

Backend cleanup

Frontend cleanup

Extras (not in review, conversation-driven)

  • AGENTS.md: new "List Endpoints" convention (limit/offset, server-side filtering)
  • Compact matIconButton utility class (.mat-icon-button-compact) in global styles
  • Links page header restyled to match dashboard / trash (breadcrumb + toolbar); placeholder chip-listboxes added on dashboard / trash for vertical alignment

Open — needs design buy-in before any code

…blic-sharing-links

Signed-off-by: Philip Miglinci <pmig@glasskube.com>
@pmig pmig force-pushed the pmig/dev-454-datei-public-sharing-links branch from 229ddaf to 8675c06 Compare May 8, 2026 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants