Reference
Every public route, panel REST endpoint, configuration parameter, field constraint and the layout of the media directory — for looking things up quickly.
A page for checking facts rather than reading start to finish. Everything is
taken from the sources of the main branch.
Public pages#
| Method | Address | What it does |
|---|---|---|
GET | / | front page: the 5 latest posts and the 2 latest albums |
GET | /records | the post feed, one item more than the page size |
GET | /records/page | a JSON slice of the feed for lazy loading |
GET | /records/read?id=N | a post page |
GET | /media | the album feed |
GET | /media/page | a JSON slice of the album feed |
GET | /media/album?id=N | a photo album; forwards on its own for a video album |
GET | /media/video_album?id=N | a video album |
GET | /author?id=N | an author page with all of their work |
GET | /tag?id=N | a tag page |
GET | /history | history — static |
GET | /about | about — static |
GET | /projects | projects — static |
GET | /education | educational programmes — static |
GET | /auth | the login form |
POST | /process_login | login processing, rate limited |
POST | /auth/logout | logout, session and cookie cleared |
GET | /go/{path} | a short link, resolved by internal forward |
GET | /sitemap.xml | the sitemap, cached for an hour |
GET | /robots.txt | a static file |
GET | /media/** | media files: served from disk when behind nginx |
* | /error | the error page |
The feed parameters page (zero-based) and type are optional. An unknown
type value causes no error — the filter simply is not applied. Valid values are
NEWS, STORY, ANNOUNCEMENT for posts and PHOTO, VIDEO for albums; case
does not matter.
Panel: content#
Requires the ROLE_ADMIN or ROLE_MODERATOR role. Mutating requests require the
CSRF token in the X-CSRF-TOKEN header.
| Method | Address | What it does | Logged |
|---|---|---|---|
GET | /dashboard | the panel front page | |
GET | /dashboard/tags | the tag manager page | |
POST | /dashboard/tags/create | create a tag | yes |
GET | /dashboard/tags/edit?id=N | tag data | |
PUT | /dashboard/tags/edit | change a tag | yes |
DELETE | /dashboard/tags/delete?id=N | delete a tag | yes |
GET | /dashboard/authors | the author manager page | |
POST | /dashboard/authors/create | create an author | yes |
GET | /dashboard/authors/edit?id=N | author data | |
PUT | /dashboard/authors/edit | change an author | yes |
DELETE | /dashboard/authors/delete?id=N | delete an author and all their content | yes |
GET | /dashboard/posts | the post manager page | |
GET | /dashboard/posts/required | author and tag lists for the form | |
POST | /dashboard/posts/create | create a post | yes |
GET | /dashboard/posts/edit?id=N | post data | |
PUT | /dashboard/posts/edit | change a post | yes |
DELETE | /dashboard/posts/delete?id=N | delete a post | yes |
GET | /dashboard/media | the media manager page | |
GET | /dashboard/media/page | a JSON slice of the media list | |
POST | /dashboard/media/upload | upload one file into the batchId directory | |
DELETE | /dashboard/media/upload | delete one file by its public path | |
POST | /dashboard/media/create | create an album record | yes |
GET | /dashboard/media/edit?id=N | album data | |
PUT | /dashboard/media/edit | change an album | yes |
DELETE | /dashboard/media/delete?id=N | delete an album and its files on disk | yes |
Panel: administration#
Requires the ROLE_ADMIN role.
| Method | Address | What it does | Logged |
|---|---|---|---|
GET | /dashboard/users | the user manager | |
POST | /dashboard/users/create | create a user | yes |
GET | /dashboard/users/edit?id=N | user data | |
PUT | /dashboard/users/edit | change a user | yes |
DELETE | /dashboard/users/delete?id=N | delete a user | yes |
GET | /dashboard/journal | the audit log page | |
GET | /dashboard/journal_data | log entries, paginated | |
GET | /dashboard/seo | the SEO manager | |
GET | /dashboard/seo/edit?path=… | the current title and description of an address | |
POST | /dashboard/seo/edit | change the title and description | yes |
POST | /dashboard/protection | turn protection mode on or off, set the token | yes |
GET | /dashboard/postprotectionxss | toggle post sanitization | yes |
POST | /dashboard/global_notification | banner text and visibility | yes |
POST | /dashboard/contacts/address | the organization address | yes |
POST | /dashboard/contacts/phone | the phone number | yes |
POST | /dashboard/contacts/email | the e-mail address | yes |
POST | /dashboard/shortcut | add a short link | yes |
DELETE | /dashboard/shortcut?key=… | remove a short link | yes |
POST | /dashboard/social_media | add a social link | yes |
DELETE | /dashboard/social_media?name=… | remove a social link by name | yes |
POST | /dashboard/clear_cache | flush all caches | yes |
GET | /actuator/health, /metrics, /info | Spring Boot telemetry |
Field constraints#
| Entity | Field | Rule |
|---|---|---|
| User | login | at least 4 characters, unique |
| User | password | at least 8 characters, at least 1 Latin letter and 1 digit |
| User | role, name | not blank |
| Post | title | 4–50 characters |
| Post | introduction | from 4 characters |
| Post | body | not blank |
| Post | type | NEWS, STORY or ANNOUNCEMENT |
| Post | author | exactly one, required on creation |
| Album | title | 4–50 characters |
| Album | batchId | required on creation, unique |
| Album | type | PHOTO or VIDEO |
| Album | author | exactly one, required on creation |
| Tag | name | 2–30 characters, unique |
| Tag | description | from 4 characters |
| Author | name | 2–30 characters |
| Author | about | unconstrained, optional |
When editing a user, an empty password means "keep the current one".
Upload constraints in the panel UI: the cover accepts .jpg only, one file, up
to 15 MB; album contents accept images and video up to 20 GB per file. The real
maximum is the smaller of spring.servlet.multipart.max-file-size and
client_max_body_size in nginx.
Configuration parameters#
Entries marked ! must be changed when deploying.
| Parameter | Default | Meaning |
|---|---|---|
server.port | ${SERVER_PORT:80} | ! the application port |
server.forward-headers-strategy | off | ! honour proxy headers; required for correct absolute addresses |
system.storage.path | a Windows path from the developer's machine | ! the root of the media storage |
web.storage.url-prefix | /media/ | the prefix of public file addresses |
web.storage.default-preview | /media/author.jpg | the fallback cover for albums without one |
web.storage.local-serving | true | ! serve files from Spring; set to false behind nginx |
spring.servlet.multipart.max-file-size | 10GB | ! per-file limit |
spring.servlet.multipart.max-request-size | 50GB | ! per-request limit |
spring.datasource.url | jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:otryadwebsite} | the database address |
spring.datasource.username | ${DB_USER:postgres} | ! the database user |
spring.datasource.password | ${DB_PASSWORD:postgres} | ! the database password |
spring.jpa.hibernate.ddl-auto | update | automatic schema creation |
spring.jpa.open-in-view | false | the session closes before rendering; leave it alone |
page-size | 6 | the page size in the feeds |
app.cache.lifetime | 60 | minutes a cache entry lives after last access |
app.cache.maxsize | 500 | entries per cache |
server.compression.enabled | true | compression for html, xml, text and JSON |
security.admin.login | a value committed to the repository | ! the first administrator's login |
security.admin.password | a value committed to the repository | ! the first administrator's password |
security.attempts.limit | 5 | never read anywhere; the limits are hardcoded |
management.endpoints.web.exposure.include | health,metrics,info | which telemetry is exposed |
dev | true | a development flag; has no business being on in production |
spring.thymeleaf.prefix | off | read templates from a directory on disk instead of the jar |
The environment variables SERVER_PORT, DB_HOST, DB_PORT, DB_NAME,
DB_USER and DB_PASSWORD are substituted into the corresponding properties.
Layout of the media directory#
<system.storage.path>/
├── 3f9c1b2e-.../ one album's directory, named after its batchId
│ ├── preview.jpg the cover; always this name, never in the gallery
│ ├── 01-first.jpg contents; gallery order is alphabetical
│ ├── 02-second.jpg
│ └── 03-video.mp4
└── 7a1d4c8f-.../
├── preview.jpg
└── 01-clip.mp4
A file's public address is assembled as web.storage.url-prefix + batchId +
/ + file name, that is /media/3f9c1b2e-.../01-first.jpg.
Database tables#
| Table | Contents |
|---|---|
posts | posts; indexed by type and date |
media_collections | albums; path holds the batchId and is unique |
content_authors | content authors |
content_tags | tags |
post_tags_map | the post-to-tag join |
collection_tags_map | the album-to-tag join |
users | panel staff |
action_logs | the audit log |
Caches#
| Name | What | Lifetime |
|---|---|---|
post | posts by id | 60 minutes after last access |
medium | albums by id | 60 minutes after last access |
author | authors with their work | 60 minutes after last access |
tag | tags with their material | 60 minutes after last access |
sitemap | sitemap addresses | 60 minutes after being written |
buckets | rate-limit counters | 24 hours, up to 10,000 entries |
Technologies and versions#
| What | Version or role |
|---|---|
| Java | 17 in the build settings; newer works too |
| Spring Boot | 4.0.2 |
| Starters | Web MVC, Data JPA, Security, Validation, Cache, Actuator, Thymeleaf, DevTools |
| PostgreSQL | driver in the dependencies; any supported server version |
| Caffeine | in-memory cache |
| Bucket4j | 8.10.0, rate limiting |
| AspectJ | auditing and limits |
| Lombok | getters, setters, logger |
| Thymeleaf | server-side templates |
| EasyMDE | the Markdown editor in the panel |
| Marked.js | Markdown rendering in the browser |
| Dropzone.js | drag-and-drop file uploads |
| SweetAlert2 | dialogs and notifications |
| H2 | tests only |
The build is Maven, with a wrapper (./mvnw). The artifact is an executable jar
at target/OtryadWebsite-1.0.jar. There is no frontend build: every library
ships as a ready-made file in the static resources.