🏷️ 属性サービス
属性サービスは、型付き値とカテゴリ関連付けを含む属性エンティティ管理のための完全なREST APIを提供します。Nodeblocks関数コンポジションアプローチを使用して構築され、MongoDBとシームレスに統合されます。
🚀 クイックスタート
import express from 'express';
import { MongoClient } from 'mongodb';
import { middlewares, services } from '@nodeblocks/backend-sdk';
const { nodeBlocksErrorMiddleware } = middlewares;
const { attributeService } = services;
const client = new MongoClient('mongodb://localhost:27017').db('dev');
express()
.use(
attributeService(
{
attributes: client.collection('attributes'),
identity: client.collection('identity'),
},
{
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret',
},
user: {
typeIds: {
admin: '100',
guest: '000',
user: '001',
},
},
}
)
)
.use(nodeBlocksErrorMiddleware())
.listen(8089, () => console.log('属性サービスが起動しました'));
📋 エンドポイント概要
メソッド | パス | 説明 | 認証 |
---|---|---|---|
POST | /attributes | 新規属性を作成 | Bearerトークン必須(管理者) |
GET | /attributes/:attributeId | IDで属性を取得 | 公開アクセス |
GET | /attributes | 属性リストを取得 | 公開アクセス |
PATCH | /attributes/:attributeId | 属性を更新 | Bearerトークン必須(管理者) |
DELETE | /attributes/:attributeId | 属性を削除 | Bearerトークン必須(管理者) |
🏷️ 属性操作
1. 属性作成
# テキスト属性
curl -X POST http://localhost:8089/attributes \
-H "Authorization: Bearer ADMIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "ブランド",
"description": "製品ブランド",
"type": "text",
"key": "brand",
"required": true,
"categories": ["electronics", "clothing"]
}'
# 選択肢属性
curl -X POST http://localhost:8089/attributes \
-H "Authorization: Bearer ADMIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "サイズ",
"description": "製品サイズ",
"type": "select",
"key": "size",
"required": true,
"options": [
{ "value": "XS", "label": "エクストラスモール" },
{ "value": "S", "label": "スモール" },
{ "value": "M", "label": "ミディアム" },
{ "value": "L", "label": "ラージ" },
{ "value": "XL", "label": "エクストララージ" }
],
"categories": ["clothing"]
}'
# 数値属性
curl -X POST http://localhost:8089/attributes \
-H "Authorization: Bearer ADMIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "重量",
"description": "製品重量(kg)",
"type": "number",
"key": "weight",
"required": false,
"validation": {
"min": 0,
"max": 1000,
"precision": 2
},
"unit": "kg",
"categories": ["electronics", "home"]
}'
📊 データスキーマ
属性エンティティ
interface Attribute {
id: string; // 一意識別子
name: string; // 属性名(必須)
description?: string; // 属性説明
key: string; // キー(一意、URL安全)
type: AttributeType; // 属性タイプ
required: boolean; // 必須フラグ
categories: string[]; // 関連カテゴリ
options?: AttributeOption[]; // 選択肢(select/multiselect用)
validation?: ValidationRules; // バリデーションルール
unit?: string; // 単位(number用)
defaultValue?: any; // デフォルト値
metadata?: Record<string, any>; // 追加メタデータ
createdAt: string; // 作成日時
updatedAt: string; // 更新日時
}
type AttributeType =
| 'text' // テキスト
| 'number' // 数値
| 'boolean' // ブール値
| 'date' // 日付
| 'select' // 単一選択
| 'multiselect' // 複数選択
| 'color' // 色
| 'url' // URL
| 'email'; // メールアドレス
interface AttributeOption {
value: string;
label: string;
color?: string;
icon?: string;
}
interface ValidationRules {
min?: number; // 最小値/最小長
max?: number; // 最大値/最大長
precision?: number; // 小数点以下桁数
pattern?: string; // 正規表現パターン
unique?: boolean; // 一意性チェック
}
🎯 属性タイプ例
テキスト属性
{
"name": "モデル番号",
"type": "text",
"key": "model_number",
"validation": {
"pattern": "^[A-Z0-9-]+$",
"minLength": 3,
"maxLength": 20
}
}
選択肢属性
{
"name": "色",
"type": "select",
"key": "color",
"options": [
{ "value": "red", "label": "赤", "color": "#FF0000" },
{ "value": "blue", "label": "青", "color": "#0000FF" },
{ "value": "green", "label": "緑", "color": "#00FF00" }
]
}
数値属性
{
"name": "価格",
"type": "number",
"key": "price",
"unit": "円",
"validation": {
"min": 0,
"precision": 0
}
}
ブール属性
{
"name": "防水",
"type": "boolean",
"key": "waterproof",
"defaultValue": false
}
🔍 検索とフィルタリング
属性検索
# カテゴリでフィルタ
curl -X GET "http://localhost:8089/attributes?category=electronics"
# タイプでフィルタ
curl -X GET "http://localhost:8089/attributes?type=select"
# 必須属性のみ
curl -X GET "http://localhost:8089/attributes?required=true"
# 名前で検索
curl -X GET "http://localhost:8089/attributes?search=サイズ"
カテゴリ用属性取得
curl -X GET "http://localhost:8089/attributes?categories=electronics,clothing"
レスポンス例:
{
"attributes": [
{
"id": "brand-uuid",
"name": "ブランド",
"type": "text",
"key": "brand",
"required": true,
"categories": ["electronics", "clothing"]
},
{
"id": "size-uuid",
"name": "サイズ",
"type": "select",
"key": "size",
"required": true,
"categories": ["clothing"],
"options": [
{ "value": "S", "label": "スモール" },
{ "value": "M", "label": "ミディアム" },
{ "value": "L", "label": "ラージ" }
]
}
]
}
🎯 高度な使用方法
動的フォーム生成
// カテゴリに基づく動的フォーム生成
const generateProductForm = async (categoryId: string) => {
const attributes = await getAttributesByCategory(categoryId);
const formFields = attributes.map(attr => ({
name: attr.key,
label: attr.name,
type: mapAttributeTypeToInputType(attr.type),
required: attr.required,
options: attr.options,
validation: attr.validation,
defaultValue: attr.defaultValue
}));
return {
categoryId,
fields: formFields
};
};
属性値検証
// 属性値の動的検証
const validateAttributeValue = (attribute: Attribute, value: any): boolean => {
switch (attribute.type) {
case 'text':
return validateTextAttribute(value, attribute.validation);
case 'number':
return validateNumberAttribute(value, attribute.validation);
case 'select':
return validateSelectAttribute(value, attribute.options);
case 'boolean':
return typeof value === 'boolean';
default:
return true;
}
};
カスタム属性タイプ
// カスタム属性タイプの拡張
const customAttributeTypes = {
'image': {
validate: (value) => isValidImageUrl(value),
render: (value) => `<img src="${value}" alt="Product Image" />`
},
'currency': {
validate: (value) => isValidCurrency(value),
format: (value) => formatCurrency(value, 'JPY')
}
};
🧪 テスト例
describe('属性サービス', () => {
test('テキスト属性作成が成功する', async () => {
const attributeData = {
name: 'ブランド',
type: 'text',
key: 'brand',
required: true,
categories: ['electronics']
};
const response = await request(attributeApp)
.post('/attributes')
.set('Authorization', `Bearer ${adminToken}`)
.send(attributeData)
.expect(201);
expect(response.body.message).toBe('属性が正常に作成されました');
});
test('選択肢属性の検証が機能する', async () => {
const selectAttribute = await createSelectAttribute();
// 有効な値
expect(validateAttributeValue(selectAttribute, 'S')).toBe(true);
// 無効な値
expect(validateAttributeValue(selectAttribute, 'invalid')).toBe(false);
});
test('重複キーを拒否する', async () => {
await createAttribute({ key: 'duplicate-key' });
await request(attributeApp)
.post('/attributes')
.set('Authorization', `Bearer ${adminToken}`)
.send({ key: 'duplicate-key', name: '重複' })
.expect(400);
});
});