diff --git a/aicard-web-oauth/.env.development b/aicard-web-oauth/.env.development index 97c77d4..23e281d 100644 --- a/aicard-web-oauth/.env.development +++ b/aicard-web-oauth/.env.development @@ -1,4 +1,9 @@ # Development environment variables VITE_APP_HOST=http://localhost:4173 VITE_OAUTH_CLIENT_ID=http://localhost:4173/client-metadata.json -VITE_OAUTH_REDIRECT_URI=http://localhost:4173/oauth/callback \ No newline at end of file +VITE_OAUTH_REDIRECT_URI=http://localhost:4173/oauth/callback +VITE_ADMIN_DID=did:plc:uqzpqmrjnptsxezjx4xuh2mn + +# Optional: Override collection names (if not set, auto-generated from host) +# VITE_COLLECTION_COMMENT=ai.syui.log +# VITE_COLLECTION_USER=ai.syui.log.user \ No newline at end of file diff --git a/aicard-web-oauth/.env.production b/aicard-web-oauth/.env.production index 15692f4..cdee24b 100644 --- a/aicard-web-oauth/.env.production +++ b/aicard-web-oauth/.env.production @@ -1,4 +1,9 @@ # Production environment variables VITE_APP_HOST=https://log.syui.ai VITE_OAUTH_CLIENT_ID=https://log.syui.ai/client-metadata.json -VITE_OAUTH_REDIRECT_URI=https://log.syui.ai/oauth/callback \ No newline at end of file +VITE_OAUTH_REDIRECT_URI=https://log.syui.ai/oauth/callback +VITE_ADMIN_DID=did:plc:uqzpqmrjnptsxezjx4xuh2mn + +# Optional: Override collection names (if not set, auto-generated from host) +# VITE_COLLECTION_COMMENT=ai.syui.log +# VITE_COLLECTION_USER=ai.syui.log.user \ No newline at end of file diff --git a/aicard-web-oauth/src/App.tsx b/aicard-web-oauth/src/App.tsx index 03232a0..98c6404 100644 --- a/aicard-web-oauth/src/App.tsx +++ b/aicard-web-oauth/src/App.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { OAuthCallback } from './components/OAuthCallback'; import { authService, User } from './services/auth'; import { atprotoOAuthService } from './services/atproto-oauth'; +import { appConfig } from './config/app'; import './App.css'; function App() { @@ -54,14 +55,14 @@ function App() { ws.onopen = () => { console.log('Jetstream connected'); ws.send(JSON.stringify({ - wantedCollections: ['ai.syui.log'] + wantedCollections: [appConfig.collections.comment] })); }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); - if (data.collection === 'ai.syui.log' && data.commit?.operation === 'create') { + if (data.collection === appConfig.collections.comment && data.commit?.operation === 'create') { console.log('New comment detected via Jetstream:', data); // Optionally reload comments // loadAllComments(window.location.href); @@ -146,7 +147,7 @@ function App() { loadAllComments(); // Load user list records if admin - if (userProfile.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') { + if (userProfile.did === appConfig.adminDid) { loadUserListRecords(); } @@ -166,7 +167,7 @@ function App() { loadAllComments(); // Load user list records if admin - if (verifiedUser.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') { + if (verifiedUser.did === appConfig.adminDid) { loadUserListRecords(); } } @@ -225,7 +226,7 @@ function App() { // Get comments from current user const response = await agent.api.com.atproto.repo.listRecords({ repo: did, - collection: 'ai.syui.log', + collection: appConfig.collections.comment, limit: 100, }); @@ -268,10 +269,10 @@ function App() { // JSONからユーザーリストを取得 const loadUsersFromRecord = async () => { try { - // 管理者のユーザーリストを取得 (ai.syui.log.user collection) - const adminDid = 'did:plc:uqzpqmrjnptsxezjx4xuh2mn'; // syui.ai + // 管理者のユーザーリストを取得 + const adminDid = appConfig.adminDid; console.log('Fetching user list from admin DID:', adminDid); - const response = await fetch(`https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(adminDid)}&collection=ai.syui.log.user&limit=100`); + const response = await fetch(`https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(adminDid)}&collection=${encodeURIComponent(appConfig.collections.user)}&limit=100`); if (!response.ok) { console.warn('Failed to fetch user list from admin, using default users. Status:', response.status); @@ -331,8 +332,8 @@ function App() { const loadUserListRecords = async () => { try { console.log('Loading user list records...'); - const adminDid = 'did:plc:uqzpqmrjnptsxezjx4xuh2mn'; // syui.ai - const response = await fetch(`https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(adminDid)}&collection=ai.syui.log.user&limit=100`); + const adminDid = appConfig.adminDid; + const response = await fetch(`https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(adminDid)}&collection=${encodeURIComponent(appConfig.collections.user)}&limit=100`); if (!response.ok) { console.warn('Failed to fetch user list records'); @@ -358,8 +359,8 @@ function App() { const getDefaultUsers = () => { const defaultUsers = [ - // bsky.social - 実際のDIDを使用 - { did: 'did:plc:uqzpqmrjnptsxezjx4xuh2mn', handle: 'syui.ai', pds: 'https://bsky.social' }, + // Default admin user + { did: appConfig.adminDid, handle: 'syui.ai', pds: 'https://bsky.social' }, ]; // 現在ログインしているユーザーも追加(重複チェック) @@ -393,7 +394,7 @@ function App() { console.log(`Fetching comments from user: ${user.handle} (${user.did}) at ${user.pds}`); // Public API使用(認証不要) - const response = await fetch(`${user.pds}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(user.did)}&collection=ai.syui.log&limit=100`); + const response = await fetch(`${user.pds}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(user.did)}&collection=${encodeURIComponent(appConfig.collections.comment)}&limit=100`); if (!response.ok) { console.warn(`Failed to fetch from ${user.handle} (${response.status}): ${response.statusText}`); @@ -490,12 +491,13 @@ function App() { throw new Error('No agent available'); } - // Create comment record with ISO datetime rkey + // Create comment record with post-specific rkey const now = new Date(); - const rkey = now.toISOString().replace(/[:.]/g, '-'); // Replace : and . with - for valid rkey + // Use post rkey if on post page, otherwise use timestamp-based rkey + const rkey = appConfig.rkey || now.toISOString().replace(/[:.]/g, '-'); const record = { - $type: 'ai.syui.log', + $type: appConfig.collections.comment, text: commentText, url: window.location.href, createdAt: now.toISOString(), @@ -510,7 +512,7 @@ function App() { // Post to ATProto with rkey const response = await agent.api.com.atproto.repo.putRecord({ repo: user.did, - collection: 'ai.syui.log', + collection: appConfig.collections.comment, rkey: rkey, record: record, }); @@ -553,7 +555,7 @@ function App() { // Delete the record await agent.api.com.atproto.repo.deleteRecord({ repo: user.did, - collection: 'ai.syui.log', + collection: appConfig.collections.comment, rkey: rkey, }); @@ -578,7 +580,7 @@ function App() { // 管理者チェック const isAdmin = (user: User | null): boolean => { - return user?.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn'; // syui.ai + return user?.did === appConfig.adminDid; }; // ユーザーリスト投稿 @@ -640,7 +642,7 @@ function App() { const rkey = now.toISOString().replace(/[:.]/g, '-'); const record = { - $type: 'ai.syui.log.user', + $type: appConfig.collections.user, users: users, createdAt: now.toISOString(), updatedBy: { @@ -652,7 +654,7 @@ function App() { // Post to ATProto with rkey const response = await agent.api.com.atproto.repo.putRecord({ repo: user.did, - collection: 'ai.syui.log.user', + collection: appConfig.collections.user, rkey: rkey, record: record, }); @@ -697,7 +699,7 @@ function App() { // Delete the record await agent.api.com.atproto.repo.deleteRecord({ repo: user.did, - collection: 'ai.syui.log.user', + collection: appConfig.collections.user, rkey: rkey, }); @@ -743,6 +745,22 @@ function App() { } }; + // Rkey-based comment filtering + // If on post page (/posts/xxx.html), only show comments with rkey=xxx + const shouldShowComment = (record: any): boolean => { + // If not on a post page, show all comments + if (!appConfig.rkey) { + return true; + } + + // Extract rkey from comment URI: at://did:plc:xxx/collection/rkey + const uriParts = record.uri.split('/'); + const commentRkey = uriParts[uriParts.length - 1]; + + // Show comment only if rkey matches current post + return commentRkey === appConfig.rkey; + }; + // OAuth callback is now handled by React Router in main.tsx console.log('=== APP.TSX URL CHECK ==='); console.log('Full URL:', window.location.href); @@ -896,10 +914,12 @@ function App() {

Comments

- {comments.length === 0 ? ( -

No comments yet

+ {comments.filter(shouldShowComment).length === 0 ? ( +

+ {appConfig.rkey ? `No comments for this post yet` : `No comments yet`} +

) : ( - comments.map((record, index) => ( + comments.filter(shouldShowComment).map((record, index) => (
- {/* Comment Form - Outside user section, after comments list */} - {user && ( + {/* Comment Form - Only show on post pages */} + {user && appConfig.rkey && (

Post a Comment