diff --git a/core/urls.py b/core/urls.py index 5fe99a1..a3d1287 100644 --- a/core/urls.py +++ b/core/urls.py @@ -3,6 +3,7 @@ from django.urls import path, include from django.conf import settings from django.conf.urls.static import static from django.views.static import serve +from django.views.generic import TemplateView from links.views import LinkDetailView, LinkUpdateView, CustomLinkView from links.file_views import PublicFileView, import_image_view from django.urls import path, include, re_path @@ -41,6 +42,10 @@ urlpatterns = [ name='public-file', ), + # LLM-friendly discovery endpoints + path('llms.txt', serve, {'document_root': settings.STATIC_ROOT, 'path': 'llms.txt'}), + path('.well-known/ai-plugin.json', serve, {'document_root': settings.STATIC_ROOT, 'path': '.well-known/ai-plugin.json'}), + # Include main app URLs with locale path('', include('links.urls')), ] diff --git a/static/.well-known/ai-plugin.json b/static/.well-known/ai-plugin.json new file mode 100644 index 0000000..729b826 --- /dev/null +++ b/static/.well-known/ai-plugin.json @@ -0,0 +1,18 @@ +{ + "schema_version": "v1", + "name_for_human": "GoLinks", + "name_for_model": "golinks", + "description_for_human": "Manage short links, bookmarked pages, image collections, file uploads, and blog posts on your self-hosted GoLinks server.", + "description_for_model": "GoLinks is a self-hosted link and content management API. Use it to: bookmark URLs (server auto-extracts title/description/screenshot), create Markdown blog posts with tags, manage image collections with bulk upload, upload and share files via public token URLs, serve random images at any dimensions from a personal library, and import external images for self-hosting. All list endpoints are paginated with page/page_size params. No authentication required.", + "auth": { + "type": "none" + }, + "api": { + "type": "openapi", + "url": "/static/openapi.yaml", + "is_user_authenticated": false + }, + "logo_url": "/static/images/logo.png", + "contact_email": "", + "legal_info_url": "" +} diff --git a/static/llms.txt b/static/llms.txt new file mode 100644 index 0000000..e344a46 --- /dev/null +++ b/static/llms.txt @@ -0,0 +1,65 @@ +# GoLinks API + +> GoLinks is a self-hosted link and content management system. It lets you create short links, bookmark pages with auto-extracted metadata, manage image collections, upload files, write Markdown blog posts, and serve random images from a personal library. + +## What you can do with this API + +- **Bookmark pages** (`/api/pages`) — POST a URL and the server auto-fetches the title, description and screenshot in the background +- **Write blog posts** (`/api/posts`) — Create Markdown posts with tag categorisation +- **Manage image collections** (`/api/collections`) — Group images into named albums; upload multiple images at once +- **Upload & share files** (`/api/files`) — Upload any file, make it public with a shareable token URL, set expiry dates +- **Random image endpoint** (`/api/images/random/{width}/{height}/`) — Returns a random image from the server's library at any size; add `?fit=crop` to crop to exact dimensions +- **Import external images** (`/import/images/{url-without-scheme}`) — Reference an external image URL; the server downloads and self-hosts it in the background + +## API reference + +- Full OpenAPI 3.0.3 spec: /static/openapi.yaml +- Interactive docs: /ui/api-docs/ + +## Key patterns + +### Embedding images in Markdown +```markdown +![placeholder](https://your-domain/api/images/random/400/300/?fit=crop&v=1234567890) +![photo](https://your-domain/import/images/example.com/path/to/photo.jpg) +``` + +### File sharing workflow +1. Upload a file: `POST /api/files` (multipart/form-data, field: `files`) +2. Make it public: `POST /api/files/{id}/toggle-public` → get `public_url` +3. Share the URL: `/public/files/{token}/` — no authentication required + +### Pagination +All list endpoints accept `?page=` (1-based) and `?page_size=`. Responses are enveloped: +```json +{ "count": 42, "next": "...", "previous": null, "results": [...] } +``` + +## Authentication + +No authentication is currently required. + +## Operations quick reference + +| operationId | Method | Path | Description | +|---|---|---|---| +| listPages | GET | /api/pages | List bookmarked pages | +| createPage | POST | /api/pages | Bookmark a new URL | +| listPosts | GET | /api/posts | List blog posts | +| createPost | POST | /api/posts | Create a blog post | +| listCollections | GET | /api/collections | List image collections | +| createCollection | POST | /api/collections | Create a collection | +| deleteCollection | DELETE | /api/collections/{id} | Delete a collection | +| uploadImagesToCollection | POST | /api/collections/{id}/upload_images | Upload images | +| deleteImage | DELETE | /api/images/{id} | Delete an image | +| listFiles | GET | /api/files | List uploaded files | +| uploadFiles | POST | /api/files | Upload files | +| getFile | GET | /api/files/{id} | Get file metadata | +| deleteFile | DELETE | /api/files/{id} | Delete a file | +| downloadFile | GET | /api/files/{id}/download | Download/view file | +| toggleFilePublic | POST | /api/files/{id}/toggle-public | Toggle public sharing | +| setFileExpiry | POST | /api/files/{id}/set-expiry | Set expiry date | +| getPublicFile | GET | /public/files/{token}/ | Access public file | +| getRandomImage | GET | /api/images/random/ | Random image (default size) | +| getRandomImageSized | GET | /api/images/random/{w}/{h}/ | Random image at W×H | +| importExternalImage | GET | /import/images/{url} | Import & cache external image | diff --git a/static/openapi.yaml b/static/openapi.yaml index 0670aa8..f84d7d4 100644 --- a/static/openapi.yaml +++ b/static/openapi.yaml @@ -1,18 +1,80 @@ openapi: 3.0.3 info: title: GoLinks API - description: API documentation for GoLinks service + description: | + # GoLinks — Link & Content Management API + + GoLinks is a self-hosted link management system. It lets you create short links, + bookmark pages with auto-extracted metadata, manage image collections, upload files, + write blog posts, and serve random images from a personal library. + + ## Key concepts + + | Resource | What it is | + |---|---| + | **Page** | A bookmarked URL. When created, the server asynchronously fetches its title, description, and a screenshot. | + | **Post** | A Markdown blog post, optionally tagged. | + | **Collection** | A named group of uploaded images (e.g. a photo album). | + | **Image** | An image file belonging to a Collection. | + | **File** | A generic uploaded file. Files can be made public via a shareable token URL. | + | **Random image** | Serves a random image from the server's local image library, resized on the fly. Great for placeholder images. | + + ## Common patterns + + ### Pagination + All list endpoints accept `?page=` (1-based) and `?page_size=` query parameters. + Responses follow the envelope `{ count, next, previous, results[] }`. + + ### Image embedding + Use the random image endpoint as a live placeholder: + ``` + ![placeholder](https://your-domain/api/images/random/400/300/?fit=crop&v=1234567890) + ``` + Or import and self-host an external image: + ``` + ![photo](https://your-domain/import/images/example.com/path/photo.jpg) + ``` + + ### File sharing + Upload a file, call `POST /api/files/{id}/toggle-public` to get a public token, + then share `/public/files/{token}/` — no authentication required for the recipient. + + ## Authentication + No authentication is currently required on any endpoint. version: 1.0.0 + x-logo: + url: /static/images/logo.png servers: - url: http://localhost:8000 description: Local development server +tags: + - name: Pages + description: | + Bookmarked web pages. Creating a page triggers an async background job that fetches + the page title, description, and screenshot automatically. + - name: Posts + description: Markdown blog posts with optional tag categorisation. + - name: Collections + description: Named groups of images. Each collection can hold many uploaded image files. + - name: Images + description: | + Image management and random image serving. + The random image endpoint is useful as a live placeholder — pass `?v={timestamp}` + to bust the browser cache on each render. + - name: Files + description: | + Generic file uploads with optional public sharing via tokenised URLs. + Files can be made public/private and given an expiry date. + paths: /api/pages: get: + operationId: listPages + tags: [Pages] summary: List all pages - description: Retrieve a paginated list of all pages + description: Retrieve a paginated list of all bookmarked pages. parameters: - in: query name: page @@ -33,8 +95,13 @@ paths: $ref: '#/components/schemas/PageList' post: + operationId: createPage + tags: [Pages] summary: Create a new page - description: Create a new page with the provided data + description: | + Bookmark a URL. After creation the server starts an async background job that fetches + the page title, description, content, and a screenshot. + Poll `GET /api/pages` or check `processing_status` to see when it completes. requestBody: required: true content: @@ -51,8 +118,10 @@ paths: /api/posts: get: + operationId: listPosts + tags: [Posts] summary: List all posts - description: Retrieve a paginated list of all posts + description: Retrieve a paginated list of all Markdown blog posts. parameters: - in: query name: page @@ -73,8 +142,10 @@ paths: $ref: '#/components/schemas/PostList' post: + operationId: createPost + tags: [Posts] summary: Create a new post - description: Create a new post with the provided data + description: Create a Markdown blog post. Pass tag slugs in the `tags` array to categorise it. requestBody: required: true content: @@ -91,8 +162,10 @@ paths: /api/collections: get: + operationId: listCollections + tags: [Collections] summary: List all collections - description: Retrieve a paginated list of all image collections + description: Retrieve a paginated list of all image collections. parameters: - in: query name: page @@ -113,8 +186,10 @@ paths: $ref: '#/components/schemas/CollectionList' post: + operationId: createCollection + tags: [Collections] summary: Create a new collection - description: Create a new image collection + description: Create a new named image collection. requestBody: required: true content: @@ -131,8 +206,10 @@ paths: /api/collections/{collection_id}: delete: + operationId: deleteCollection + tags: [Collections] summary: Delete a collection - description: Delete a collection and all its images + description: Permanently delete a collection and all its images. parameters: - in: path name: collection_id @@ -151,8 +228,12 @@ paths: /api/collections/{collection_id}/upload_images: post: + operationId: uploadImagesToCollection + tags: [Collections] summary: Upload images to a collection - description: Upload one or more images to a collection with optional descriptions + description: | + Upload one or more images to a collection. Only `image/*` content types are accepted. + Optionally provide a parallel `descriptions[]` array — each entry maps to the image at the same index. parameters: - in: path name: collection_id @@ -203,8 +284,10 @@ paths: /api/images/{image_id}: delete: + operationId: deleteImage + tags: [Images] summary: Delete an image - description: Delete a specific image from a collection + description: Permanently delete a single image from its collection. parameters: - in: path name: image_id @@ -223,8 +306,10 @@ paths: /api/files: get: + operationId: listFiles + tags: [Files] summary: List all uploaded files - description: Retrieve a paginated list of all uploaded files + description: Retrieve a paginated list of all uploaded files. parameters: - in: query name: page @@ -240,8 +325,12 @@ paths: $ref: '#/components/schemas/FileList' post: + operationId: uploadFiles + tags: [Files] summary: Upload one or more files - description: Upload one or multiple files. Files are stored in FILE_UPLOADS_FOLDER on the server. + description: | + Upload one or multiple files via `multipart/form-data`. Files are stored server-side + in the configured `FILE_UPLOADS_FOLDER`. Returns an array of `FileUpload` objects. requestBody: required: true content: @@ -271,7 +360,10 @@ paths: /api/files/{id}: get: + operationId: getFile + tags: [Files] summary: Get file details + description: Fetch metadata for a single file by its UUID. parameters: - in: path name: id @@ -290,8 +382,10 @@ paths: description: File not found delete: + operationId: deleteFile + tags: [Files] summary: Delete a file - description: Deletes the file record and removes it from disk + description: Permanently delete the file record and remove it from disk. parameters: - in: path name: id @@ -307,8 +401,12 @@ paths: /api/files/{id}/download: get: + operationId: downloadFile + tags: [Files] summary: Download or view a file - description: Serves the file content. Images are served inline; other files as attachments. + description: | + Serve the raw file bytes. Images are returned with `Content-Disposition: inline` + (viewable in browser); all other files use `attachment` (triggers download). parameters: - in: path name: id @@ -324,8 +422,14 @@ paths: /api/files/{id}/toggle-public: post: + operationId: toggleFilePublic + tags: [Files] summary: Toggle public/private visibility - description: Makes a private file public (generating a public token) or makes a public file private immediately. + description: | + Toggle a file between public and private: + - **Private → Public**: generates a `public_token` and returns a `public_url` + (`/public/files/{token}/`) that anyone can access without authentication. + - **Public → Private**: immediately revokes the token; existing `public_url` links stop working. parameters: - in: path name: id @@ -353,7 +457,12 @@ paths: /api/files/{id}/set-expiry: post: + operationId: setFileExpiry + tags: [Files] summary: Set or clear expiry date for a public file + description: | + Set an expiry datetime for a public file. After the expiry the `public_url` returns 404. + Send `expires_at: null` (or omit the field) to remove the expiry and make the link permanent. parameters: - in: path name: id @@ -383,8 +492,12 @@ paths: /public/files/{token}/: get: + operationId: getPublicFile + tags: [Files] summary: Access a public file by token - description: Serves a publicly shared file. Returns 404 if the file is private or the link has expired. + description: | + Unauthenticated endpoint. Serves a publicly shared file by its opaque token. + Returns 404 if the file has been made private or if the expiry date has passed. parameters: - in: path name: token @@ -399,6 +512,8 @@ paths: /api/images/random/: get: + operationId: getRandomImage + tags: [Images] summary: Get a random image description: | Returns a randomly selected image from the server's image library, resized to the @@ -406,8 +521,6 @@ paths: **Cache busting:** Add a `?v={timestamp}` query parameter to prevent browsers from caching repeated calls (the server ignores this parameter). - tags: - - Images parameters: - in: query name: fit @@ -454,6 +567,8 @@ paths: /api/images/random/{width}/{height}/: get: + operationId: getRandomImageSized + tags: [Images] summary: Get a random image at specific dimensions description: | Returns a randomly selected image resized to `{width}×{height}` pixels. @@ -466,8 +581,6 @@ paths: ``` ![Random image](http://your-domain/api/images/random/300/450/?fit=crop&v=1234567890) ``` - tags: - - Images parameters: - in: path name: width @@ -531,6 +644,8 @@ paths: /import/images/{image_url}: get: + operationId: importExternalImage + tags: [Files] summary: Import and cache an external image description: | Imports an external image by URL and saves it to the local file store. @@ -554,8 +669,6 @@ paths: ``` ![Alt text](http://your-domain/import/images/example.com/path/to/image.jpg) ``` - tags: - - Files parameters: - in: path name: image_url