# APP.yaml # # Single source-of-truth specification for the app, organized by feature/domain area. # Built per .junie/guidelines.md (authoring phase). MILESTONES.yaml is written from this # file afterward — do not create/update it until asked. # # Section conventions (use within any section as needed): # notes: free-form rationale, considerations, examples (prose or short list) # decisions_log: append-only list of resolved questions -> {question, decision, rationale} # open_questions: structured list of items still to clarify -> {question, options?} # (empty list when none remain) # # This is a skeleton layout. Sections below are placeholders to be filled in and adapted # (added/removed/renamed) as the project's ideas are gathered. --- app: name: SiteWeaverCMS use_milestones: true description: > A new PHP CMS, whose goal is to be modular, with easy to build and understand templates, built-in module installer (using Zip file uploader), with an admin interface even a non-tech person finds inviting. notes: - Publicly viewable front-end (posts, pages) - Auth required back-end (Media manager, User manager, Role-based Auth manager, Post/Page manager, Theme manager, Plugin/Module manager) - URL should be in the form of `https://domain.tld/{page-slug}` - Security & Reliability: - Built-in 2FA (TOTP/Email) - Automatic Basic Security Audits for Plugins: - no eval - no exec - no remote code execution - Content Management: - Primary language configurable in Configuration Settings - Configuration settings to list/check/select which other languages the site supports - Content Versioning & Rollbacks (History) - Rich Text Editor (producing clean HTML) - Built-in multi-language support (i18n) - i18n Translation Engine: - Database-backed with `t()` (or t-like method) helper for UI strings. - Multi-Locale Content Support: - storage for Pages, Posts, and Media. - Translate for Me Service: - Service to auto-translate Post/Page/etc to languages selected in Configuration Settings - Button to trigger Service - Process: - Text of Page/Post is sent to a self-hosted LibreTranslate instance, along with the list of approved languages - LibreTranslate instance returns the translated text - Translated text is stored in the {Page|Post}_Versions table, with the language identifier in the `locale` column - composer_require: - composer require jefs42/libretranslate decisions_log_ref: ".decisions/app_decisions.yaml" open_questions: [] suggestions: [] glossary: # Domain-specific terms and their meanings, so everyone uses the same vocabulary. terms: - term: $theme variable definition: > An object variable exposed to themes, containing the metadata described in the theme.md (or admin.theme.md) file. This will include Theme Name, Author, Description, Date, Version. Inside the theme, it is accessed as $theme->author, $theme->name, etc - term: 2FA (Two-Factor Authentication) definition: > A login security step requiring a second verification factor (e.g., a TOTP code or an emailed code) in addition to the password. - term: Admin Theme definition: > A theme to alter the look and feel of the Admin section of the CMS. This does not alter the frontend that visitors to the site will see. This only alters the backend admin section. - term: Automated Cache Warming on Publish definition: > When a Page or Post is published, the system will pre-cache the Page/Post, so the visitors will always receive the fastest response (a cached page/post vs rendered-on-demand page/post) - term: Blog Post Author Permissions definition: > The permissions granted to the author of a blog post (the Author role). This includes things like Publishing, Editing, Un-Publishing, Deleting, Saving (prior to publish), Allowing comments on Post - term: Comments System definition: > The functionality of the CMS dedicated to the comment section. This allows Users to post comments (when the Author has turned comments on), but does its best to deter spammers, scammers, etc. - term: Content Versioning & Rollbacks (History) definition: > Everytime a Page or Blog Post is saved, a revision is saved in the database. This way, if the altered changes are not wanted or unapproved, the Page/Post can be rolled back to a previous iteration easily. - term: CRUD definition: > Create, Read, Update, Delete — the four basic operations for managing a resource/record. - term: Faceted Search definition: > A search interface combining free-text search with multiple simultaneous filters (facets) - e.g. categories/tags and date ranges - that narrow results as they're applied. - term: Focal Point Selector definition: > Selecting the focus point of an image. When an image is cropped or overflow hidden, this is the "center" of what is shown. It is the focal point. - term: Frontend Theme definition: > A theme to alter the look and feel of the frontend of the CMS. This alters the frontend that visitors to the site will see. This does not alter the backend admin section. - term: Granular Permissions definition: > Role-based authorization. Setting the permissions each role has the capability to perform. Creating, Reading, Updating, Deleting each individual section or area of the CMS (blog posts, pages, users, media, etc) - term: Hooks (theme.md) definition: > The section of theme.md (the theme metadata file) that tells the theme renderer what asset files (css, js) need to be loaded and where (header, footer, attached to partials, etc) - term: i18n definition: > Internationalization — designing/building software so it can be adapted to different languages/locales without changing the code. - term: i18n Translation Engine definition: > This is the engine that drives the internationalization. When the content on the site is translated from one language to another, this is the engine that does the translating. - term: JIT (Just-In-Time) Generation definition: > Generating a derived asset (e.g. a resized/cropped image variant) on demand at request time, instead of pre-generating every possible variant in advance. - term: Modular DB Migrations definition: > Every Plugin/Module is responsible for its own database migrations. What tables or columns in a table that it relies on to be present in order to run. - term: Multi-Locale Content Support definition: > Storing Pages, Posts, and Media content as JSON-per-locale, so each supported language/locale has its own independently maintained version of a resource. - term: Orphaned Media Watcher definition: > A background service that scans the entries in the Media Manager, and scans the contents of Pages and Posts to find when certain media entries are no longer being used. If a media entry has been orphaned (no longer in use), it is a candidate for deletion, to save space on the server. - term: Page Builder Core definition: > The CMS is a robust Page Builder. Whether that page is a basic web page (Home, About Us, etc) a list of Blog Entries, a Calendar, etc, it is all about the Page. - term: Partial (template partial) definition: > A small portion of the overall page. The navbar on the webpage, if separated out into its own template would be a good example. If you separate the top, bottom, and "main" sections of a webpage out into their own template files, each of those files would be partials, that are then rendered into one larger full template file. - term: PDO definition: > PHP Data Objects — PHP's built-in database access layer providing a common interface across different database engines. - term: Preview Cache Warming definition: > After saving a Page or Post alteration, you are able to Preview how it looks prior to publishing it to Production. When you click the link to preview it, the system will cache it, so that when you Publish it, the cache is already present for when your visitors want to view the page. If you alter the page/post further, before publishing, the cache will be rendered stale, and will be overwritten by the next Preview Cache Warming or Publish Cache Warming. - term: Rich Text Editor definition: > A WYSIWYG (What You See Is What You Get) editor for formatting text content that produces clean HTML markup. - term: Service Provider definition: > A class responsible for registering/bootstrapping a plugin's or component's functionality (routes, migrations, admin sections, etc.) with the core application. - term: Site backup definition: > This is a process, kicked off in the admin section, to backup the site files and database. - term: Storage Interface / Storage Abstraction definition: > A common interface (`StorageInterface::get($path)`, `put($path, $data)`, `delete($path)`) that each storage backend (Local Disk, S3, Nextcloud, Linode Object-Storage) implements. The rest of the CMS (media upload, JIT generation, etc.) talks only to this interface, making it easy to swap or add storage providers without touching the calling code. - term: theme.md / admin.theme.md definition: > These are the metadata files for the CMS frontend themes and admin themes. They will contain information on the theme name, author, date, descriptions, hooks, etc - term: Theme Editor definition: > This is a built-in file editor, in the admin section, that allows an Admin or other authorized role to make minor modifications to the theme files without the need to change the files physically and upload them to the server. The theme files are edited in place on the server. - term: TOTP definition: > Time-based One-Time Password — a short-lived numeric code generated from a shared secret and the current time, used as a second authentication factor. notes: [] decisions_log_ref: ".decisions/glossary_decisions.yaml" open_questions: [] scope: in_scope: - Page Builder - Role Based Auth - User Management - Blog plugin - Media Manager - Theme/Plugin Management - i18n - Comments - Site Backup - Site Restore - Search out_of_scope: - Multi-tenancy (SaaS) - Hosted/SaaS offering - Mobile app - API-only approach architecture: notes: - Architecture & Scalability: - Single-site CMS, no multi-tenancy (see decisions_log) description: > SiteWeaverCMS is a Laravel 13.x-based CMS with a modular architecture, designed for self-hosted use. It features a clean separation of concerns between the core system and plugins/themes, with a focus on developer productivity and extensibility. decisions_log_ref: ".decisions/architecture_decisions.yaml" open_questions: [] plugins_architecture: description: > The plugin architecture allows for extending the core functionality of SiteWeaverCMS through modular components. Each plugin can register its own routes, services, and configuration settings. notes: - plugin_metadata_spec: required_fields: - name - author - description - date - version - core_version format: core_version: semantic version string (e.g., "13.x") - Plugins: - Admin section Plugins List Page: - Lists each plugin (its name, brief description, and active status): - Active: Plugin is installed and switched on - Deactivated: Plugin is installed, but switched off - Install - Error: Plugin threw an error during Install - Clicking on Plugin name sends you to Plugin Detail Page for that Plugin - Plugin Detail Page: - Lists Plugin metadata details (plugin.md) - Toggle to switch Plugin Active/Deactivated status - Service Provider-based architecture - All plugins, officially included by default or not, reside in the `site/plugins` directory, and are built as a plugin. - Built-in Plugin Support: - Blog (included by default): - URL should be in the form of `https://domain.tld/{published-4-digit-year}/{published-2-digit-month}/{published-2-digit-day}/{post-title-slug}` - Example https://example.com/2026/08/26/my-new-dog - The Published Date for the post is always the published date of the original version. Follow up versions keep the same published date. - Blog Category Pages: - URL should be in the form of `https://domain.tld/category/{blog_category_slug}` - Shows a list of Blog Posts that are tagged with the category in the url - Blog Posts MUST belong to at least 1 category - A "Uncategorized" blog category will exist - Blog Posts do not need to have Blog Tags, but may have many - Blog Category vs Blog Tag: - Categories: - Broad, high‑level groupings that organize a blog’s content into major topics. - Affects URL, changes are rare and deliberate - Names are meant to be stable, descriptive, and representative of major topics (e.g., Travel, Food, Finance). - Tags: - Specific, descriptive keywords that indicate particular subjects, themes, or details within a post. - Changes are easy, and do not affect URLs. - Names can be more creative or specific (e.g., vegan, budget‑travel, 2024‑updates). - Blog Category Examples: - Technology - Programming - Front-End - Vehicles - Blog Tag Examples: - ReactJS - SEO - Ford Mustang - Blog Post Comments: - comments are auto-approved (no manual approval needed), but may be deleted/redacted by Admin or other User Role with proper permissions - Modular DB Migrations: - Each plugin/component has a `migrations` folder - The migrations folder resides inside the plugin directory (`plugins/blog/migrations/{migration files}`) - CMS scans and runs them automatically on install - Plugin table names should be in the form of {plugin_name}_{table_name} - Example - Blog_Posts, Blog_Post_Versions - Plugin Upload: - ZIP directory upload (Admin selects "Plugin" or "Theme" type) - Uninstall/Remove: Available only when Plugin/Module is Deactivated. - Runs reverse migrations (regress DB changes). - Deletes the plugin directory from `plugins/`. - Configuration Settings: - If Plugin requires configuration, can add a section to Configuration Settings in Admin - Admin Section: - Plugin can add it's own section to Admin section: - Example: Blog Plugin adds Posts so Author can create Blog Posts on site. - Front-end: - Plugin can include it's own template partials for specialized output, or be outputted by installed theme files: - Example: A calendar Plugin can include it's own calendar.php template file for outputting the calendar decisions_log_ref: ".decisions/plugin_architecture_decisions.yaml" open_questions: [] themes_architecture: description: > The theme architecture provides a flexible way to customize the look and feel of SiteWeaverCMS. Themes can be easily installed, switched, and customized through the admin interface. notes: - Themes: - Default theme (provided by default) supports Light/Dark switching, and set to System by default - Admin theme supports Light/Dark switching, and set to System by default - Laravel Blade templates (accessible for non-UI devs) - Uses `$this->themeUrl($asset)` to produce a url to the asset in the theme directory - Uses `$this->adminThemeUrl($asset)` to produce a url to the asset in the admin theme directory - exposes `$theme` variable to templates: - $theme variable contains all the theme meta data (with lowercase variables) with exception of hooks: - the hooks section uses it's own set of methods, listed below (in Frontend Theme directory structure and Admin Theme directory structure) - "Example: `$theme->title` contains the title of the theme as specified in theme.md Title" - "Example: `$theme->author` contains the author of the theme as specified in theme.md Author" - Theme Metadata via `theme.md` (frontend theme): - Title - Author - Description - Date - Version - Core Version - Hooks: - css: - ... list of css files to add to theme head (path relative to theme root) - js: - header: - ... list of js files to add to theme head (path relative to theme root) - footer: - ... list of js files to add to theme footer (path relative to theme root) - "{template_name}": - ... list of js files to add at the top of a template before rendering it (path relative to theme root) - Theme Metadata via `admin.theme.md` (admin theme): - same metadata as frontend theme, only file name is `admin.theme.md` instead of `theme.md` - Simplified architecture (avoiding WordPress-style convolution) - Theme Upload: - ZIP directory upload via Admin section - Uninstall/Remove: - Available only for themes not currently in use. - Deletes the theme directory from `themes/`. - Triggered via button on Theme List page (Delete button per list item) - Note: Themes should not make DB changes on install. - Frontend Theme directory folder structure: - theme.md (required) - layout.php (required) - partials: - optional, unless layout.php uses `$theme->partial()` - ... (any files called by layout.php using the `$theme->partial($path)` method) - subdirectories allowed in $path - "`$theme->partial('footer/contact-block')` would render `partials/footer/contact-block.php`" - assets: - css: - $theme->css() (outputs all the link tags for the list of css files in theme.md hooks/css) - js: - $theme->js('header') (outputs all the script tags for the list of js files listed in theme.md hooks/js/header) - $theme->js('footer') (outputs all the script tags for the list of js files listed in theme.md hooks/js/footer) - $theme->js($template_name) (outputs all the script tags for the list of js files listed in theme.md hooks/js/{template_name}) - images: - $theme->imageUrl($image_name) (outputs the url to the image (not the whole img tag, only the url). Subdirectory path allowed) - Admin Theme directory structure: - same directory structure as frontend theme, except: - partials are rendered using `$theme->adminPartial()`, not `$theme->partial()` - css is output via `$theme->adminCss()`, not `$theme->css()` - js is output via `$theme->adminJs($section)`, not `$theme->js($section)` - Admin section Frontend Theme Page: - Lists each theme (its name and screenshot.jpg): - If Active: - shows "Active" - cannot be deleted (no delete button shown) - If not Active: - Shows "Activate" button and "Delete" button - When Activate button is clicked, Deactivates previous Active theme, Activates current Theme - Only one Admin Theme may be active at a time. - Clicking on Theme name sends you to Admin Theme Detail Page for that Theme - Frontend Theme Detail Page: - Lists Theme metadata details (theme.md) - If Active, shows "Active", else provides Activate button - Admin section Admin Theme Page: - Lists each theme (its name and screenshot.jpg): - If Active: - shows "Active" - cannot be deleted (no delete button shown) - If not Active: - Shows "Activate" button and "Delete" button - When Activate button is clicked, Deactivates previous Active theme, Activates current Theme - Only one Admin Theme may be active at a time. - Clicking on Theme name sends you to Admin Theme Detail Page for that Theme - Admin Theme Detail Page: - Lists Theme metadata details (admin.theme.md) - If Active, shows "Active", else provides Activate button - Theme Editor File Versioning: - Each save made through the in-place Theme Editor creates a new version of the saved file, so an accidental bad edit can be reverted without needing the full Site Restore feature. decisions_log_ref: ".decisions/themes_architecture_decisions.yaml" open_questions: [] components: description: > SiteWeaverCMS consists of several key components that work together to provide a complete content management solution. These include the core system, plugins, themes, and various service components. decisions_log_ref: ".decisions/components_decisions.yaml" open_questions: [] notes: [] data_model: description: > SiteWeaverCMS uses a relational database model with support for multiple storage engines. The data model is designed to be extensible through plugins and themes. notes: - Site Settings: - Site Title/Description - Global SEO Settings - Locales table uses `is_supported` to indicate supported locales, and `is_default` marks the default locale. - Blog Settings: - Blog Title - Inherits locales support from Site Settings - Configurable Blog "Home" URL segment (Admin/Editor can set the entry point for the blog plugin) - Database Tables: - Site_Settings: - fields: - id: int, not null - name: string(255), not null - setting: string(255), null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign_key: created_by (Users.id) - foreign_key: updated_by (Users.id) - index: name - Locales: - fields: - id: int, not null - code: string(7), not null - name: string(100), not null - direction: enum('ltr','rtl'), not null, default='ltr' - is_supported: bool, not null, default=false - is_default: bool, not null, default=false - created_at: datetime, not null, default=now() - updated_at: datetime, null - indexes: - primary_key: id - unique: code - unique: (CASE WHEN is_default = true THEN 1 ELSE NULL END) - index: name - Users: - fields: - id: int, not null - username: string(255), not null - password: string(255), not null - f_name: string(255), not null - l_name: string(255), null - email: string(255), not null - avatar: string(255), null, comment="user account image, uses default if null" - failed_login_attempts: int, not null, default=0 - locked_until: datetime, null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign_key: created_by (Users.id) - foreign_key: updated_by (Users.id) - unique: username - unique: email - User_Totp: - fields: - id: int, not null - user_id: int, not null - secret: string(32), not null - enabled: boolean, not null, default=true - created_at: datetime, not null - updated_at: datetime, null - indexes: - primary_key: id - foreign_key: user_id (Users.id) - User_password_reset: - fields: - id: int, not null - user_id: int, not null - token: string(42), not null, unique - expires_at: datetime, not null - created_at: datetime, not null - indexes: - primary_key: id - foreign_key: user_id (Users.id) - User_email_verification: - fields: - id: int, not null - user_id: int, not null - token: string(42), not null - expires_at: datetime, not null - created_at: datetime, not null - indexes: - primary_key: id - foreign_key: user_id (Users.id) - unique: token - Roles (Stores each logical role (e.g., Developer, Admin, Editor, Viewer)): - fields: - id: int, not null - name: string(255), not null - description: string(255), null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - unique: name - foreign_key: created_by (Users.id) - foreign_key: updated_by (Users.id) - Permissions (Stores each distinct permission (e.g., read:document, delete:user)): - fields: - id: int, not null - name: string(255), not null - type: string(255), null - action: string(255), null - description: string(255), null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - unique: name - foreign_key: created_by (Users.id) - foreign_key: updated_by (Users.id) - Role_Permission (Junction table that maps many roles to many permissions): - fields: - role_id: int, not null - permission_id: int, not null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: (role_id, permission_id) - foreign_key: role_id (Roles.id) - foreign_key: permission_id (Permissions.id) - foreign_key: created_by (Users.id) - foreign_key: updated_by (Users.id) - User_Role (Junction table that maps many users to many roles): - fields: - user_id: int, not null - role_id: int, not null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: (user_id, role_id) - foreign key: user_id (Users.id) - foreign key: role_id (Roles.id) - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Pages: - fields: - id: int, not null - slug: string(255), comment="human‑readable URL part" - title: string(255) - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - published_at: datetime, null - (set once on first publish (first version publish), never updated) - indexes: - primary_key: id - unique: slug - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Page_Versions: - fields: - id: int, not null - page_id: int, not null - version_number: int, not null - (incremental per page per locale, starts at 1) - locale: string(7), not null - content: text, not null, comment="the full HTML or templating markup" - meta_json: json/text, null, comment="extra fields like seo_title, meta_description, tags" - created_at: datetime, not null - created_by: int, not null - is_published: bool, not null, default=false - published_at: datetime, null - indexes: - primary_key: id - foreign_key: page_id (Pages.id) - foreign key: created_by (Users.id) - Blog_Settings (Blog plugin): - fields: - id: int, not null - name: string(255), not null - setting: string(255), null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - unique: name - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Blog_Posts (Blog plugin): - fields: - id: int, not null - slug: string(255), not null, comment="human‑readable URL part" - title: string(255), not null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - published_at: datetime, null - (set once on first publish (first version publish), never updated) - indexes: - primary_key: id - unique: slug - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Blog_Post_Versions (Blog plugin): - fields: - id: int, not null - post_id: int, not null - version_number: int, not null (incremental per post_id per locale, starts at 1) - locale: string(7), not null - extract: text, null (if null, extract is pulled from content) - content: text, not null, comment="the full HTML or templating markup" - meta_json: json/text, null comment="extra fields like seo_title, meta_description, tags" - created_at: datetime, not null - created_by: int, not null - is_published: bool, not null, default=false - published_at: datetime, null - indexes: - primary_key: id - foreign_key: post_id (Blog_Posts.id) - foreign_key: created_by (Users.id) - Blog_Categories (Blog plugin): - fields: - id: int, not null - name: string(255), not null - slug: string(255), not null - description: text, null - indexes: - primary_key: id - unique: name - unique: slug - Blog_Tags (Blog plugin): - fields: - id: int, not null - name: string(255), not null - description: text, null - indexes: - primary_key: id - unique: name - Blog_Post_Categories (Blog plugin): - fields: - post_id: int, not null - category_id: int, not null - created_at: datetime, not null, default=now() - created_by: int, not null - indexes: - primary_key: (post_id, category_id) - foreign_key: post_id (Blog_Posts.id) - foreign_key: category_id (Blog_Categories.id) - foreign key: created_by (Users.id) - Blog_Post_Tags (Blog plugin): - fields: - post_id: int, not null - tag_id: int, not null - created_at: datetime, not null, default=now() - created_by: int, not null - indexes: - primary_key: (post_id, tag_id) - foreign_key: post_id (Blog_Posts.id) - foreign_key: tag_id (Blog_Tags.id) - foreign key: created_by (Users.id) - Blog_Comments: - fields: - id: int, not null - post_id: int, not null - user_id: int, not null - content: text, not null - likely_spam: boolean, not null - created_at: datetime, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign_key: post_id (Blog_Posts.id) - foreign_key: user_id (Users.id) - foreign key: updated_by (Users.id) - Media: - fields: - id: int, not null - filename: string(255), not null - path: string(255), not null - storage_backend: string(255), not null - mime_type: string(75), not null - height: int, not null - width: int, not null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Media_Metadata: - fields: - id: int, not null - media_id: int, not null - name: string(255), not null - alt: text, null, default=null - locale: string(3), not null - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign_key: media_id (Media.id) - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Plugins: - fields: - id: int, not null - name: string(255), not null - version: string(10), not null - core_version: string(10), not null - status: enum('Active','Deactivated','Error'), not null, default='Deactivated' - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Frontend_Themes: - fields: - id: int, not null - name: string(255), not null - enabled: boolean, not null, default=false, comment="Only one may be enabled at a time. Currently controlled by app code, not DB constraints" - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Admin_Themes: - fields: - id: int, not null - name: string(255), not null - enabled: boolean, not null, default=false, comment="Only one may be enabled at a time. Currently controlled by app code, not DB constraints" - created_at: datetime, not null, default=now() - created_by: int, not null - updated_at: datetime, null - updated_by: int, null - indexes: - primary_key: id - foreign key: created_by (Users.id) - foreign key: updated_by (Users.id) - Backups: - fields: - id: int, not null - filename: string(255), not null - size: bigint, not null - created_at: datetime, not null, default=now() - indexes: - primary_key: id - index: filename example_seed_data_for_roles: > INSERT INTO Roles (name, description) VALUES ('Developer', 'Full access to all CMS features and settings. Can create Admins'), ('Admin', 'Full access to all CMS features and settings.'), ('Editor', 'Can create, edit, and delete content, but cannot change site settings.'), ('Author', 'Can create and edit their own content; can view drafts.'), ('User', 'Basic frontend read‑only access to published content. No backend admin access. Can comment on Blog Posts'); -- 2. Insert a set of permissions that are common to most CMSes INSERT INTO Permissions (name, type, action, description) VALUES ('manage:site', NULL, 'configure', 'Change site‑wide settings (e.g., logo, navigation).'), ('publish:page', 'page', 'publish', 'Publish a page to the public site.'), ('unpublish:page', 'page', 'unpublish', 'Remove a page from public view.'), ('edit:page', 'page', 'edit', 'Modify an existing page.'), ('delete:page', 'page', 'delete', 'Permanently delete a page.'), ('manage:user', NULL, 'admin', 'Create, edit, disable, or delete user accounts.'), ('view:draft', 'draft', 'view', 'Read unpublished drafts.'), ('edit:draft', 'draft', 'edit', 'Edit an unpublished draft.'), ('publish:draft', 'draft', 'publish', 'Publish a draft to the public site.'); -- 3. Assign permissions to the default roles (many‑to‑many via Role_Permission) -- The composite PK (role_id, permission_id) guarantees no duplicate rows. INSERT INTO Role_Permission (role_id, permission_id) SELECT r.id, p.id FROM Roles r, Permissions p WHERE r.name = 'Admin' AND p.name IN (SELECT name from Permissions); INSERT INTO Role_Permission (role_id, permission_id) SELECT r.id, p.id FROM Roles r, Permissions p WHERE r.name = 'Editor' AND p.name IN ('publish:page','unpublish:page', 'edit:page','delete:page', 'view:draft','edit:draft'); INSERT INTO Role_Permission (role_id, permission_id) SELECT r.id, p.id FROM Roles r, Permissions p WHERE r.name = 'Author' AND p.name IN ('publish:page','edit:page', 'edit:draft','publish:draft'); seed_data_for_locales: > INSERT INTO Locales (code, name, direction) VALUES ("en","English","ltr"), ("sq","Albanian","ltr"), ("ar","Arabic","rtl"), ("az","Azerbaijani","ltr"), ("eu","Basque","ltr"), ("bn","Bengali","ltr"), ("bg","Bulgarian","ltr"), ("ca","Catalan","ltr"), ("zh-Hans","Chinese","ltr"), ("zh-Hant","Chinese (traditional)","ltr"), ("cs","Czech","ltr"), ("da","Danish","ltr"), ("nl","Dutch","ltr"), ("eo","Esperanto","ltr"), ("et","Estonian","ltr"), ("fi","Finnish","ltr"), ("fr","French","ltr"), ("gl","Galician","ltr"), ("de","German","ltr"), ("el","Greek","ltr"), ("he","Hebrew","rtl"), ("hi","Hindi","ltr"), ("hu","Hungarian","ltr"), ("id","Indonesian","ltr"), ("ga","Irish","ltr"), ("it","Italian","ltr"), ("ja","Japanese","ltr"), ("ko","Korean","ltr"), ("ky","Kyrgyz","ltr"), ("lv","Latvian","ltr"), ("lt","Lithuanian","ltr"), ("ms","Malay","ltr"), ("nb","Norwegian","ltr"), ("fa","Persian","rtl"), ("pl","Polish","ltr"), ("pt","Portuguese","ltr"), ("pt-BR","Portuguese (Brazil)","ltr"), ("ro","Romanian","ltr"), ("ru","Russian","ltr"), ("sr","Serbian","ltr"), ("sk","Slovak","ltr"), ("sl","Slovenian","ltr"), ("es","Spanish","ltr"), ("sw","Swahili","ltr"), ("sv","Swedish","ltr"), ("tl","Tagalog","ltr"), ("th","Thai","ltr"), ("tr","Turkish","ltr"), ("uk","Ukrainian","ltr"), ("ur","Urdu","rtl"), ("vi","Vietnamese","ltr"); decisions_log_ref: ".decisions/data_model_decisions.yaml" open_questions: [] deployment: description: > SiteWeaverCMS is designed for self-hosted deployment with support for various web server configurations and environments. decisions_log_ref: ".decisions/deployment_decisions.yaml" open_questions: [] notes: [] security: description: > SiteWeaverCMS implements security best practices including input validation, csrf on every form, form data cleansing (no