Compare commits
3 Commits
59b8ff76ea
...
276ae2dc9d
| Author | SHA1 | Date |
|---|---|---|
|
|
276ae2dc9d | |
|
|
1eb94565d5 | |
|
|
8f41c4736f |
20
STATUS.md
20
STATUS.md
|
|
@ -31,6 +31,17 @@ node_modules/.bin/tauri build
|
||||||
# → target/release/bundle/msi/Posimai Guard_0.1.0_x64_en-US.msi
|
# → target/release/bundle/msi/Posimai Guard_0.1.0_x64_en-US.msi
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### MCP 設定(mai 作業)
|
||||||
|
1. **Stripe キー確認・ローテーション**
|
||||||
|
- Stripe ダッシュボード → Developers → Logs で 4/11 の API リクエストを確認(使用元の特定)
|
||||||
|
- 既存の `sk_test_...` キーをロールオーバー(無効化)
|
||||||
|
- 新しいテスト用シークレットキーを発行(名前: `posimai-mcp`)
|
||||||
|
- [.mcp.json](.mcp.json) の `FILL_IN: Stripe ダッシュボードの sk_test_...` を置換
|
||||||
|
2. **Vercel トークン発行**
|
||||||
|
- [vercel.com/account/tokens](https://vercel.com/account/tokens) → Create → 名前: `claude-code-mcp`、Scope: Full Account
|
||||||
|
- [.mcp.json](.mcp.json) の `FILL_IN: vercel.com/account/tokens...` を置換
|
||||||
|
3. **Claude Code 再起動** → vercel・stripe MCP が有効になる
|
||||||
|
|
||||||
### ビジネス化
|
### ビジネス化
|
||||||
1. **Eiji に Stripe sandbox テストをお願いする**(購入フロー確認)
|
1. **Eiji に Stripe sandbox テストをお願いする**(購入フロー確認)
|
||||||
2. **日本酒アプリを完成させて展開**(mai 最優先)
|
2. **日本酒アプリを完成させて展開**(mai 最優先)
|
||||||
|
|
@ -38,6 +49,15 @@ node_modules/.bin/tauri build
|
||||||
4. Store デザイン確定(Eiji と A/B/C/D から選定)
|
4. Store デザイン確定(Eiji と A/B/C/D から選定)
|
||||||
5. Stripe 本番モード切り替え(上記完了後)
|
5. Stripe 本番モード切り替え(上記完了後)
|
||||||
|
|
||||||
|
## mai 作業待ち(AI では実行できない)
|
||||||
|
|
||||||
|
| タスク | 内容 | 優先度 |
|
||||||
|
|--------|------|--------|
|
||||||
|
| **article-keeper Firebase キー削除** | Firebase Console でプロジェクトごと削除(不要なら)。完了後に AI が article-keeper/ ディレクトリを削除・コミットする | 高(キーが GitHub 公開履歴に存在) |
|
||||||
|
| Stripe sandbox テスト | Eiji に購入フロー確認依頼 | 中 |
|
||||||
|
| 特商法ページ記入 | 事業者名・住所・電話番号 | 中 |
|
||||||
|
| Store デザイン確定 | Eiji と A/B/C/D から選定 | 中 |
|
||||||
|
|
||||||
## ブロック中
|
## ブロック中
|
||||||
|
|
||||||
| ブロック | 待ち先 |
|
| ブロック | 待ち先 |
|
||||||
|
|
|
||||||
60
server.js
60
server.js
|
|
@ -2481,9 +2481,17 @@ ${excerpt}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /together/members/:groupId — メンバー一覧
|
// GET /together/members/:groupId — メンバー一覧 ?u=username
|
||||||
r.get('/together/members/:groupId', async (req, res) => {
|
r.get('/together/members/:groupId', async (req, res) => {
|
||||||
|
const username = req.query.u;
|
||||||
|
if (!username) return res.status(400).json({ error: 'u (username) は必須です' });
|
||||||
try {
|
try {
|
||||||
|
const memberCheck = await pool.query(
|
||||||
|
'SELECT 1 FROM together_members WHERE group_id=$1 AND username=$2',
|
||||||
|
[req.params.groupId, username]
|
||||||
|
);
|
||||||
|
if (memberCheck.rows.length === 0) return res.status(403).json({ error: 'グループのメンバーではありません' });
|
||||||
|
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
'SELECT username, joined_at FROM together_members WHERE group_id=$1 ORDER BY joined_at',
|
'SELECT username, joined_at FROM together_members WHERE group_id=$1 ORDER BY joined_at',
|
||||||
[req.params.groupId]
|
[req.params.groupId]
|
||||||
|
|
@ -2546,12 +2554,20 @@ ${excerpt}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /together/feed/:groupId — フィード(リアクション・コメント数付き)
|
// GET /together/feed/:groupId — フィード(リアクション付き)
|
||||||
// ?limit=N&cursor=<ISO timestamp> でカーソルページネーション対応
|
// ?u=username&limit=N&cursor=<ISO timestamp>
|
||||||
r.get('/together/feed/:groupId', async (req, res) => {
|
r.get('/together/feed/:groupId', async (req, res) => {
|
||||||
|
const username = req.query.u;
|
||||||
|
if (!username) return res.status(400).json({ error: 'u (username) は必須です' });
|
||||||
try {
|
try {
|
||||||
|
const memberCheck = await pool.query(
|
||||||
|
'SELECT 1 FROM together_members WHERE group_id=$1 AND username=$2',
|
||||||
|
[req.params.groupId, username]
|
||||||
|
);
|
||||||
|
if (memberCheck.rows.length === 0) return res.status(403).json({ error: 'グループのメンバーではありません' });
|
||||||
|
|
||||||
const limit = Math.min(parseInt(req.query.limit) || 20, 50);
|
const limit = Math.min(parseInt(req.query.limit) || 20, 50);
|
||||||
const cursor = req.query.cursor; // shared_at の ISO タイムスタンプ
|
const cursor = req.query.cursor;
|
||||||
const params = [req.params.groupId];
|
const params = [req.params.groupId];
|
||||||
let cursorClause = '';
|
let cursorClause = '';
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
|
|
@ -2564,11 +2580,9 @@ ${excerpt}
|
||||||
COALESCE(
|
COALESCE(
|
||||||
json_agg(DISTINCT jsonb_build_object('username', r.username, 'type', r.type))
|
json_agg(DISTINCT jsonb_build_object('username', r.username, 'type', r.type))
|
||||||
FILTER (WHERE r.username IS NOT NULL), '[]'
|
FILTER (WHERE r.username IS NOT NULL), '[]'
|
||||||
) AS reactions,
|
) AS reactions
|
||||||
COUNT(DISTINCT c.id)::int AS comment_count
|
|
||||||
FROM together_shares s
|
FROM together_shares s
|
||||||
LEFT JOIN together_reactions r ON r.share_id = s.id
|
LEFT JOIN together_reactions r ON r.share_id = s.id
|
||||||
LEFT JOIN together_comments c ON c.share_id = s.id
|
|
||||||
WHERE s.group_id = $1 ${cursorClause}
|
WHERE s.group_id = $1 ${cursorClause}
|
||||||
GROUP BY s.id
|
GROUP BY s.id
|
||||||
ORDER BY s.shared_at DESC
|
ORDER BY s.shared_at DESC
|
||||||
|
|
@ -2585,9 +2599,17 @@ ${excerpt}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /together/article/:shareId — アーカイブ本文取得
|
// GET /together/article/:shareId — アーカイブ本文取得 ?u=username
|
||||||
r.get('/together/article/:shareId', async (req, res) => {
|
r.get('/together/article/:shareId', async (req, res) => {
|
||||||
|
const username = req.query.u;
|
||||||
|
if (!username) return res.status(400).json({ error: 'u (username) は必須です' });
|
||||||
try {
|
try {
|
||||||
|
const memberCheck = await pool.query(
|
||||||
|
'SELECT 1 FROM together_members m JOIN together_shares s ON s.group_id=m.group_id WHERE s.id=$1 AND m.username=$2',
|
||||||
|
[req.params.shareId, username]
|
||||||
|
);
|
||||||
|
if (memberCheck.rows.length === 0) return res.status(403).json({ error: 'グループのメンバーではありません' });
|
||||||
|
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
'SELECT id, title, url, full_content, summary, archive_status, shared_at FROM together_shares WHERE id=$1',
|
'SELECT id, title, url, full_content, summary, archive_status, shared_at FROM together_shares WHERE id=$1',
|
||||||
[req.params.shareId]
|
[req.params.shareId]
|
||||||
|
|
@ -2603,7 +2625,7 @@ ${excerpt}
|
||||||
r.post('/together/react', async (req, res) => {
|
r.post('/together/react', async (req, res) => {
|
||||||
const { share_id, username, type = 'like' } = req.body || {};
|
const { share_id, username, type = 'like' } = req.body || {};
|
||||||
if (!share_id || !username) return res.status(400).json({ error: 'share_id と username は必須です' });
|
if (!share_id || !username) return res.status(400).json({ error: 'share_id と username は必須です' });
|
||||||
if (!['like', 'star', 'fire'].includes(type)) return res.status(400).json({ error: 'type は like/star/fire のみ有効です' });
|
if (!['like', 'star', 'fire', 'read'].includes(type)) return res.status(400).json({ error: 'type は like/star/fire/read のみ有効です' });
|
||||||
try {
|
try {
|
||||||
// share の group に対してメンバーであることを確認
|
// share の group に対してメンバーであることを確認
|
||||||
const memberCheck = await pool.query(
|
const memberCheck = await pool.query(
|
||||||
|
|
@ -2635,9 +2657,17 @@ ${excerpt}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /together/comments/:shareId — コメント一覧
|
// GET /together/comments/:shareId — コメント一覧 ?u=username
|
||||||
r.get('/together/comments/:shareId', async (req, res) => {
|
r.get('/together/comments/:shareId', async (req, res) => {
|
||||||
|
const username = req.query.u;
|
||||||
|
if (!username) return res.status(400).json({ error: 'u (username) は必須です' });
|
||||||
try {
|
try {
|
||||||
|
const memberCheck = await pool.query(
|
||||||
|
'SELECT 1 FROM together_members m JOIN together_shares s ON s.group_id=m.group_id WHERE s.id=$1 AND m.username=$2',
|
||||||
|
[req.params.shareId, username]
|
||||||
|
);
|
||||||
|
if (memberCheck.rows.length === 0) return res.status(403).json({ error: 'グループのメンバーではありません' });
|
||||||
|
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
'SELECT * FROM together_comments WHERE share_id=$1 ORDER BY created_at',
|
'SELECT * FROM together_comments WHERE share_id=$1 ORDER BY created_at',
|
||||||
[req.params.shareId]
|
[req.params.shareId]
|
||||||
|
|
@ -2673,9 +2703,15 @@ ${excerpt}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /together/search/:groupId — キーワード / タグ検索
|
// GET /together/search/:groupId — キーワード / タグ検索 ?u=username
|
||||||
r.get('/together/search/:groupId', async (req, res) => {
|
r.get('/together/search/:groupId', async (req, res) => {
|
||||||
const { q = '', tag = '' } = req.query;
|
const { q = '', tag = '', u: username = '' } = req.query;
|
||||||
|
if (!username) return res.status(400).json({ error: 'u (username) は必須です' });
|
||||||
|
const memberOk = await pool.query(
|
||||||
|
'SELECT 1 FROM together_members WHERE group_id=$1 AND username=$2',
|
||||||
|
[req.params.groupId, username]
|
||||||
|
).then(r => r.rows.length > 0).catch(() => false);
|
||||||
|
if (!memberOk) return res.status(403).json({ error: 'グループのメンバーではありません' });
|
||||||
if (!q && !tag) return res.status(400).json({ error: 'q または tag が必要です' });
|
if (!q && !tag) return res.status(400).json({ error: 'q または tag が必要です' });
|
||||||
try {
|
try {
|
||||||
const keyword = q ? `%${q}%` : '';
|
const keyword = q ? `%${q}%` : '';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue