Initial commit: nick docs
This commit is contained in:
113
02 - Data Models/BlogPost.md
Normal file
113
02 - Data Models/BlogPost.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: BlogPost
|
||||
tags: [data-model, mongoose]
|
||||
aliases: [Blog Post, Article, IBlogPost]
|
||||
---
|
||||
|
||||
# BlogPost
|
||||
|
||||
Editorial content for the marketplace's blog. Each post has a title, an auto-generated slug, rich `content`, optional cover image, gallery, and embedded videos (YouTube / Vimeo / Aparat / other). Carries publication workflow (`draft` / `published` / `archived`), denormalised author info, SEO metadata, and counters for views, likes, and comments. Two pre-save hooks handle slug generation and `publishedAt` stamping.
|
||||
|
||||
> [!note] Source
|
||||
> `backend/src/models/BlogPost.ts:39` — schema definition
|
||||
> `backend/src/models/BlogPost.ts:182` — model export
|
||||
|
||||
## Schema
|
||||
|
||||
| Field | Type | Required | Default | Validation | Index | Description |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `title` | String | yes | — | trim, maxlength 200 | — | Post title. |
|
||||
| `slug` | String | no | (auto-generated) | lowercase, trim | unique, sparse | URL slug. |
|
||||
| `description` | String | yes | — | maxlength 500 | — | Short summary. |
|
||||
| `content` | String | yes | — | — | — | Full body (markdown / HTML). |
|
||||
| `coverImage` | String | no | — | — | — | Hero image URL. |
|
||||
| `images[]` | String[] | no | — | — | — | Gallery URLs. |
|
||||
| `videos[].url` | String | yes | — | — | — | Video URL. |
|
||||
| `videos[].title` | String | no | — | — | — | Video title. |
|
||||
| `videos[].platform` | String | no | `youtube` | enum: `youtube` / `vimeo` / `aparat` / `other` | — | Platform. |
|
||||
| `videos[].embedId` | String | no | — | — | — | Embed id (if applicable). |
|
||||
| `author.id` | ObjectId → [[User]] | yes | — | — | — | Author user. |
|
||||
| `author.name` | String | yes | — | — | — | Denormalised author name. |
|
||||
| `author.avatar` | String | no | — | — | — | Avatar URL. |
|
||||
| `category` | String | yes | `tutorial` | enum: `tutorial` / `news` / `guide` / `tips` / `announcement` / `other` | yes (compound) | Editorial category. |
|
||||
| `tags[]` | String[] | no | — | trim | yes | Free-form tags. |
|
||||
| `status` | String | no | `draft` | enum: `draft` / `published` / `archived` | yes (compound) | Workflow state. |
|
||||
| `publishedAt` | Date | no | — | — | yes (compound) | Auto-set when status → `published`. |
|
||||
| `views` | Number | no | `0` | — | — | View counter. |
|
||||
| `likes` | Number | no | `0` | — | — | Like counter. |
|
||||
| `comments` | Number | no | `0` | — | — | Comment counter. |
|
||||
| `readTime` | Number | no | `5` | — | — | Estimated read time (minutes). |
|
||||
| `featured` | Boolean | no | `false` | — | yes (compound) | Front-page promotion. |
|
||||
| `seo.metaTitle` | String | no | — | — | — | SEO title. |
|
||||
| `seo.metaDescription` | String | no | — | — | — | SEO description. |
|
||||
| `seo.metaKeywords[]` | String[] | no | — | — | — | SEO keywords. |
|
||||
| `createdAt` | Date | auto | — | — | — | Mongoose timestamp. |
|
||||
| `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. |
|
||||
|
||||
Virtuals are enabled in `toJSON` and `toObject` even though none are declared on the schema.
|
||||
|
||||
## Virtuals
|
||||
|
||||
None defined (but enabled in serialisation).
|
||||
|
||||
## Indexes
|
||||
|
||||
Defined at `backend/src/models/BlogPost.ts:148-151`. Plus the implicit unique sparse index on `slug`:
|
||||
|
||||
- `{ status: 1, publishedAt: -1 }` — published feed.
|
||||
- `{ category: 1, status: 1 }` — category page.
|
||||
- `{ tags: 1 }` — tag lookup.
|
||||
- `{ featured: 1, status: 1 }` — featured posts.
|
||||
|
||||
## Pre/Post Hooks
|
||||
|
||||
| Hook | Behaviour |
|
||||
| --- | --- |
|
||||
| `pre('save')` (`backend/src/models/BlogPost.ts:154`) | Auto-generates `slug` from the title (English letters only) plus a timestamp suffix; falls back to `post-<timestamp>` for non-Latin titles. |
|
||||
| `pre('save')` (`backend/src/models/BlogPost.ts:175`) | When `status` is modified to `published` and `publishedAt` is empty, sets `publishedAt = new Date()`. |
|
||||
|
||||
## Instance Methods
|
||||
|
||||
None defined.
|
||||
|
||||
## Static Methods
|
||||
|
||||
None defined.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **References**: [[User]] (`author.id`).
|
||||
- **Referenced by**: none.
|
||||
|
||||
## State Transitions
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> draft
|
||||
draft --> published : author publishes
|
||||
published --> archived : admin archives
|
||||
published --> draft : unpublish
|
||||
archived --> published : restore
|
||||
archived --> [*]
|
||||
```
|
||||
|
||||
## Common Queries
|
||||
|
||||
```ts
|
||||
// Public feed
|
||||
BlogPost.find({ status: 'published' }).sort({ publishedAt: -1 }).limit(20);
|
||||
|
||||
// By slug (detail page)
|
||||
BlogPost.findOne({ slug, status: 'published' });
|
||||
|
||||
// Featured carousel
|
||||
BlogPost.find({ featured: true, status: 'published' }).sort({ publishedAt: -1 });
|
||||
|
||||
// Tag search
|
||||
BlogPost.find({ tags: tag, status: 'published' });
|
||||
|
||||
// Increment views atomically
|
||||
BlogPost.updateOne({ _id }, { $inc: { views: 1 } });
|
||||
```
|
||||
|
||||
Related: [[User]].
|
||||
Reference in New Issue
Block a user