Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 102 additions & 98 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,108 +1,112 @@
# AGENTS.md - AIアシスタント向けガイド

## 目的
このドキュメントは、GitHub CopilotやClaudeなどのAIアシスタントがこのレポジトリ(techblog_cms)を理解し、効果的に支援するためのガイドラインを提供します。レポジトリの構造、技術スタック、開発プロセスを明確にし、AIが適切なコード生成やアドバイスを行えるようにします。

## レポジトリ概要
techblog_cmsは、Djangoベースの技術ブログコンテンツ管理システムです。Docker Composeを使用したコンテナ化環境で、Nginxによるリバースプロキシ、PostgreSQLデータベース、Redisキャッシュ、Let's EncryptによるSSL証明書の自動管理を統合しています。プロダクションレディな構成を目指し、セキュリティとスケーラビリティを重視しています。

## 技術スタック
- **バックエンド**: Django 4.2, Python 3.11
- **データベース**: PostgreSQL
- **キャッシュ**: Redis
- **Webサーバー**: Nginx (リバースプロキシ + 静的ファイル配信)
- **コンテナ化**: Docker, Docker Compose
- **SSL証明書**: Let's Encrypt (Certbot)
- **テスト**: pytest, Django Test Framework
# AGENTS.md - Guide for AI Assistants

## Purpose
This document provides guidelines for AI assistants like GitHub Copilot and Claude to understand this repository (techblog_cms) and assist effectively. It clarifies the repository structure, tech stack, and development process to enable AI to generate appropriate code and provide sound advice.

## Repository Overview
techblog_cms is a Django-based technical blog content management system. It operates in a containerized environment using Docker Compose, integrating Nginx reverse proxy, PostgreSQL database, Redis cache, and automated SSL certificate management via Let's Encrypt. The configuration prioritizes security and scalability, aiming for production readiness.

## Technology Stack
- **Backend**: Django 4.2, Python 3.11
- **Database**: PostgreSQL
- **Cache**: Redis
- **Web Server**: Nginx (Reverse Proxy + Static File Delivery)
- **Containerization**: Docker, Docker Compose
- **SSL Certificates**: Let's Encrypt (Certbot)
- **Testing**: pytest, Django Test Framework
- **CI/CD**: GitHub Actions
- **セキュリティ**: HTTPS強制, セキュリティヘッダー, 環境変数管理
- **フロントエンド**: HTML/CSS (Tailwind CSS), JavaScript (最小限)
- **Security**: HTTPS enforcement, security headers, environment variable management
- **Frontend**: HTML/CSS (Tailwind CSS), JavaScript (minimal)

## プロジェクト構造
## Project Structure
```
techblog_cms/
├── app/ # Djangoアプリケーション
│ ├── techblog_cms/ # メインDjangoアプリ
├── app/ # Django application
│ ├── techblog_cms/ # Main Django app
│ │ ├── __init__.py
│ │ ├── settings.py # Django設定(環境変数使用)
│ │ ├── urls.py # URLマッピング
│ │ ├── views.py # ビュー関数
│ │ ├── wsgi.py # WSGIエントリーポイント
│ │ └── templates/ # HTMLテンプレート
│ └── requirements.txt # Python依存関係
├── nginx/ # Nginx設定
│ │ ├── settings.py # Django settings (using environment variables)
│ │ ├── urls.py # URL mapping
│ │ ├── views.py # View functions
│ │ ├── wsgi.py # WSGI entry point
│ │ └── templates/ # HTML templates
│ └── requirements.txt # Python dependencies
├── nginx/ # Nginx configuration
│ ├── conf.d/
│ │ └── default.conf # Nginx設定ファイル
│ └── Dockerfile # Nginxコンテナ定義
├── scripts/ # ユーティリティスクリプト
│ ├── init-letsencrypt.sh # SSL証明書初期化
│ └── renew-cert.sh # SSL証明書更新
├── static/ # 静的ファイル
├── tests/ # テストファイル
├── docker-compose.yml # コンテナオーケストレーション
├── Dockerfile.* # 各種Dockerfile
├── requirements.txt # プロジェクト全体の依存関係
└── pytest.ini # pytest設定
│ │ └── default.conf # Nginx configuration file
│ └── Dockerfile # Nginx container definition
├── scripts/ # Utility scripts
│ ├── init-letsencrypt.sh # Initialize SSL certificate
│ └── renew-cert.sh # Renew SSL certificate
├── static/ # Static files
├── tests/ # Test files
├── docker-compose.yml # Container orchestration
├── Dockerfile.* # Various Dockerfiles
├── requirements.txt # Project-wide dependencies
└── pytest.ini # pytest configuration
```

## 開発ガイドライン

### コーディング標準
- **Python**: PEP 8準拠、Blackフォーマッター使用
- **Django**: Djangoベストプラクティスに従う
- **Docker**: マルチステージビルド、セキュリティスキャン(Trivy)
- **セキュリティ**: 環境変数で機密情報を管理、HTTPS強制

### 環境変数
重要な設定は環境変数で管理:
- `DEBUG`: デバッグモード(本番ではFalse)
- `SECRET_KEY`: Djangoシークレットキー
- `DATABASE_URL`: PostgreSQL接続文字列
- `REDIS_URL`: Redis接続文字列
- `ALLOWED_HOSTS`: 許可ホストリスト

### テスト
- pytestを使用したユニットテストと統合テスト
- CI/CDで自動テスト実行
- カバレッジレポート生成

## AIアシスタントへの指示

### コード生成時の考慮事項
1. **セキュリティ優先**: 機密情報は環境変数を使用。ハードコーディング禁止。
2. **コンテナ化対応**: Dockerベストプラクティスに従い、軽量イメージを作成。
3. **Djangoベストプラクティス**: ビュー関数、モデル、テンプレートの適切な分離。
4. **エラーハンドリング**: 適切な例外処理とログ出力。
5. **パフォーマンス**: データベースクエリの最適化、キャッシュの活用。

### 支援時のガイドライン
1. **新規機能追加**: 既存の構造に準拠。必要に応じてマイグレーション作成。
2. **バグ修正**: テストケースを追加し、リグレッションを防ぐ。
3. **ドキュメント更新**: コード変更時はREADME.mdやこのAGENTS.mdを更新。
4. **依存関係**: 新しいパッケージ追加時はrequirements.txtを更新。
5. **Docker設定**: 変更時はdocker-compose.ymlと関連Dockerfileを確認。

### 禁止事項
- ハードコーディングされたパスワードやAPIキー
- 非効率なデータベースクエリ
- セキュリティホール(SQLインジェクション、XSSなど)
- 不要な依存関係の追加

### 推奨ツール使用
- **コード編集**: replace_string_in_file または insert_edit_into_file
- **ファイル作成**: create_file
- **ターミナル実行**: run_in_terminal(Dockerコマンドなど)
- **テスト実行**: runTests
- **ファイル検索**: grep_search または semantic_search

## 貢献ガイド
1. developブランチからフィーチャーブランチを作成
2. 変更を実装し、テストを追加
3. プルリクエストを作成し、レビューを依頼
4. マージ後、必要に応じてドキュメント更新

## 連絡先
質問や改善提案は、GitHub IssuesまたはPull Requestsをご利用ください。
## Development Guidelines

### Coding Standards
- **Python**: PEP 8 compliant, using Black formatter
- **Django**: Follow Django best practices
- **Docker**: Multi-stage builds, security scanning (Trivy)
- **Security**: Manage sensitive info via environment variables, enforce HTTPS

### Environment Variables
Critical settings managed via environment variables:
- `DEBUG`: Debug mode (False in production)
- `SECRET_KEY`: Django secret key
- `DATABASE_URL`: PostgreSQL connection string
- `REDIS_URL`: Redis connection string
- `ALLOWED_HOSTS`: List of allowed hosts


### Testing
- Unit and integration testing using pytest
- Automated test execution via CI/CD
- Coverage report generation


## Instructions for AI Assistant


### Considerations for Code Generation
1. **Security First**: Use environment variables for sensitive data. Do not hardcode.
2. **Containerization Ready**: Follow Docker best practices to create lightweight images.
3. **Django Best Practices**: Properly separate view functions, models, and templates.
4. **Error Handling**: Implement proper exception handling and log output.
5. **Performance**: Optimize database queries and utilize caching.

### Guidelines for Support
1. **Adding New Features**: Adhere to existing structure. Create migrations as needed.
2. **Bug Fixes**: Add test cases to prevent regressions.
3. **Document Updates**: Update README.md and this AGENTS.md when modifying code.
4. **Dependencies**: Update requirements.txt when adding new packages.
5. **Docker Configuration**: Verify docker-compose.yml and related Dockerfiles when making changes.

### Prohibited Actions
- Hard-coded passwords or API keys
- Inefficient database queries
- Security vulnerabilities (SQL injection, XSS, etc.)
- Adding unnecessary dependencies

### Recommended Tool Usage
- **Code Editing**: `replace_string_in_file` or `insert_edit_into_file`
- **File Creation**: `create_file`
- **Terminal Execution**: run_in_terminal (e.g., Docker commands)
- **Test Execution**: runTests
- **File Search**: grep_search or semantic_search

## Contribution Guide
1. Create a feature branch from the develop branch
2. If the current branch is main, create the feature branch from main.
2. Implement changes and add tests.
3. Create a pull request and request a review.
4. After merging, update documentation as needed.

## Contact
Please use GitHub Issues or Pull Requests for questions or improvement suggestions.

---
最終更新: 2025年8月31日
Last updated: August 31, 2025
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ isort>=5.13,<6.0

# Documentation
markdown>=3.5,<4.0
linkify-it-py>=2.0,<3.0

# Optional: Image processing
# Pillow>=10.1,<11.0
Expand Down
13 changes: 11 additions & 2 deletions techblog_cms/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid
from django.db import models
from django.utils.text import slugify
from django.urls import reverse
Expand Down Expand Up @@ -51,9 +52,17 @@ class Article(models.Model):
tags = models.ManyToManyField(Tag, blank=True)
image = models.ImageField(upload_to='articles/', blank=True, null=True)

def _generate_unique_slug(self):
base_slug = slugify(self.title) or 'article'
while True:
hash_fragment = uuid.uuid4().hex[:8]
candidate = f"{base_slug}-{hash_fragment}"
Comment on lines +55 to +59

Choose a reason for hiding this comment

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

[P1] Truncate base slug before appending UUID fragment

The new _generate_unique_slug always appends a hyphen plus an 8‑character UUID fragment to the slugified title. Because the slug field uses Django’s default SlugField(max_length=50), any title whose slugified form exceeds 41 characters now produces a slug longer than the column and will fail to save with a database error. Previously only colliding slugs grew past 50, so many long but valid titles are now rejected. Consider limiting base_slug to 41 characters before appending the suffix or increasing the field’s max_length.

Useful? React with 👍 / 👎.

if not Article.objects.filter(slug=candidate).exclude(pk=self.pk).exists():
return candidate

def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
self.slug = self._generate_unique_slug()
if not self.excerpt and self.content:
self.excerpt = self.content[:200]
super().save(*args, **kwargs)
Expand All @@ -62,4 +71,4 @@ def __str__(self):
return self.title

def get_absolute_url(self):
return reverse('article', kwargs={'slug': self.slug})
return reverse('article_detail', kwargs={'slug': self.slug})
5 changes: 4 additions & 1 deletion techblog_cms/templates/article_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,10 @@
<header class="mb-6">
<h1 class="text-3xl font-bold text-gray-800 mb-2">{{ article.title }}</h1>
<div class="text-sm text-gray-500">
<span>Published: {{ article.created_at|date:"M d, Y" }}</span>
<span>Published: {{ article.created_at|date:"M d, Y H:i" }}</span>
{% if article.updated_at and article.updated_at != article.created_at %}
<span class="ml-4">Updated: {{ article.updated_at|date:"M d, Y H:i" }}</span>
{% endif %}
{% if article.category %}
<span class="ml-4">Category:
<a href="{% url 'category' article.category.slug %}" class="text-blue-500 hover:underline">
Expand Down
11 changes: 6 additions & 5 deletions techblog_cms/templates/article_editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
<div class="flex flex-col lg:flex-row gap-6">
<!-- エディタ -->
<div class="editor-section flex-1 min-w-0 flex flex-col">
<h2 class="mb-5 text-xl font-semibold">記事エディタ</h2>
<h2 class="mb-5 text-xl font-semibold">{% if article %}記事編集{% else %}記事エディタ{% endif %}</h2>

{% if error %}
<div class="alert alert-danger" style="margin-bottom: 20px;">{{ error }}</div>
{% endif %}

<form method="post" id="articleForm" action="{% url 'article_new' %}" class="flex flex-col h-full">
<form method="post" id="articleForm" action="{% if article %}{% url 'article_edit' article.slug %}{% else %}{% url 'article_new' %}{% endif %}" class="flex flex-col h-full">
{% if not IS_TESTING %}
{% csrf_token %}
{% endif %}

<div class="form-group mb-4">
<label for="title" class="font-bold">タイトル</label>
<input id="title" name="title" type="text" required
value="{{ title_value|default:'' }}"
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-200" />
</div>

Expand All @@ -40,13 +41,13 @@ <h2 class="mb-5 text-xl font-semibold">記事エディタ</h2>
<textarea id="content" name="content"
class="flex-1 w-full px-3 py-2 border border-gray-300 rounded-md font-mono focus:outline-none focus:ring focus:ring-blue-200 resize-y"
style="min-height: 24rem;"
placeholder="ここに記事を書いてください..."></textarea>
placeholder="ここに記事を書いてください...">{{ content_value|default:'' }}</textarea>
</div>

<!-- ボタンエリア -->
<div class="button-group mt-4 flex gap-3">
<button type="submit" name="action" value="publish" class="px-5 py-2 rounded bg-blue-600 text-white hover:bg-blue-700">🚀 公開</button>
<button type="submit" name="action" value="save" class="px-5 py-2 rounded bg-gray-700 text-white hover:bg-gray-800">💾 保存</button>
<button type="submit" name="action" value="publish" class="px-5 py-2 rounded bg-blue-600 text-white hover:bg-blue-700">{% if article %}🚀 更新して公開{% else %}🚀 公開{% endif %}</button>
<button type="submit" name="action" value="save" class="px-5 py-2 rounded bg-gray-700 text-white hover:bg-gray-800">{% if article %}💾 下書きを更新{% else %}💾 保存{% endif %}</button>
<a href="{% url 'dashboard' %}" class="px-5 py-2 rounded border border-gray-400 text-gray-700 hover:bg-gray-50">戻る</a>
</div>
</form>
Expand Down
4 changes: 4 additions & 0 deletions techblog_cms/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ <h2 style="margin: 0;">記事一覧</h2>
<small style="color: #6b7280; margin-left: 10px;">{{ a.created_at|date:'Y-m-d H:i' }}</small>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<a href="{% url 'article_edit' a.slug %}"
style="color: #2563eb; text-decoration: none; font-weight: 600;">
編集
</a>
{% if not a.published %}
<span style="background-color: #6b7280; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold;">下書き</span>
{% endif %}
Expand Down
5 changes: 3 additions & 2 deletions techblog_cms/templatetags/markdown_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ def markdown_to_html(text):
html = markdown.markdown(text, extensions=[
'extra', # Extra features like tables, footnotes, and raw HTML
'codehilite', # Code highlighting with Pygments
'toc', # Table of contents
'toc', # Table of contents
'fenced_code', # Fenced code blocks
'nl2br', # Convert newlines to <br>
'nl2br', # Convert newlines to <br>
'linkify', # Auto-link plain URLs
], extension_configs={
Comment on lines 16 to 23

Choose a reason for hiding this comment

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

[P0] Ensure linkify extension is installed or referenced correctly

Adding 'linkify' to the Markdown extensions list causes markdown.markdown() to import a module literally named linkify, but the repo only adds linkify-it-py, which does not provide that module. Calling markdown_to_html now raises ModuleNotFoundError: No module named 'linkify', breaking article rendering and the markdown preview. Use the fully qualified 'markdown.extensions.linkify' (and keep linkify-it-py as the optional dependency) or install the third‑party mdx_linkify package so the extension can be imported.

Useful? React with 👍 / 👎.

'codehilite': {
'linenums': False, # Disable line numbers
Expand Down
Loading