fix env
Some checks failed
Deploy ailog / build-and-deploy (push) Failing after 11m20s

This commit is contained in:
2025-06-11 17:31:21 +09:00
parent 4775fa7034
commit ad45b151b1
4 changed files with 151 additions and 29 deletions

View File

@ -1,4 +1,9 @@
# Development environment variables # Development environment variables
VITE_APP_HOST=http://localhost:4173 VITE_APP_HOST=http://localhost:4173
VITE_OAUTH_CLIENT_ID=http://localhost:4173/client-metadata.json VITE_OAUTH_CLIENT_ID=http://localhost:4173/client-metadata.json
VITE_OAUTH_REDIRECT_URI=http://localhost:4173/oauth/callback 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

View File

@ -1,4 +1,9 @@
# Production environment variables # Production environment variables
VITE_APP_HOST=https://log.syui.ai VITE_APP_HOST=https://log.syui.ai
VITE_OAUTH_CLIENT_ID=https://log.syui.ai/client-metadata.json VITE_OAUTH_CLIENT_ID=https://log.syui.ai/client-metadata.json
VITE_OAUTH_REDIRECT_URI=https://log.syui.ai/oauth/callback 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

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { OAuthCallback } from './components/OAuthCallback'; import { OAuthCallback } from './components/OAuthCallback';
import { authService, User } from './services/auth'; import { authService, User } from './services/auth';
import { atprotoOAuthService } from './services/atproto-oauth'; import { atprotoOAuthService } from './services/atproto-oauth';
import { appConfig } from './config/app';
import './App.css'; import './App.css';
function App() { function App() {
@ -54,14 +55,14 @@ function App() {
ws.onopen = () => { ws.onopen = () => {
console.log('Jetstream connected'); console.log('Jetstream connected');
ws.send(JSON.stringify({ ws.send(JSON.stringify({
wantedCollections: ['ai.syui.log'] wantedCollections: [appConfig.collections.comment]
})); }));
}; };
ws.onmessage = (event) => { ws.onmessage = (event) => {
try { try {
const data = JSON.parse(event.data); 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); console.log('New comment detected via Jetstream:', data);
// Optionally reload comments // Optionally reload comments
// loadAllComments(window.location.href); // loadAllComments(window.location.href);
@ -146,7 +147,7 @@ function App() {
loadAllComments(); loadAllComments();
// Load user list records if admin // Load user list records if admin
if (userProfile.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') { if (userProfile.did === appConfig.adminDid) {
loadUserListRecords(); loadUserListRecords();
} }
@ -166,7 +167,7 @@ function App() {
loadAllComments(); loadAllComments();
// Load user list records if admin // Load user list records if admin
if (verifiedUser.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') { if (verifiedUser.did === appConfig.adminDid) {
loadUserListRecords(); loadUserListRecords();
} }
} }
@ -225,7 +226,7 @@ function App() {
// Get comments from current user // Get comments from current user
const response = await agent.api.com.atproto.repo.listRecords({ const response = await agent.api.com.atproto.repo.listRecords({
repo: did, repo: did,
collection: 'ai.syui.log', collection: appConfig.collections.comment,
limit: 100, limit: 100,
}); });
@ -268,10 +269,10 @@ function App() {
// JSONからユーザーリストを取得 // JSONからユーザーリストを取得
const loadUsersFromRecord = async () => { const loadUsersFromRecord = async () => {
try { 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); 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) { if (!response.ok) {
console.warn('Failed to fetch user list from admin, using default users. Status:', response.status); console.warn('Failed to fetch user list from admin, using default users. Status:', response.status);
@ -331,8 +332,8 @@ function App() {
const loadUserListRecords = async () => { const loadUserListRecords = async () => {
try { try {
console.log('Loading user list records...'); console.log('Loading user list records...');
const adminDid = 'did:plc:uqzpqmrjnptsxezjx4xuh2mn'; // syui.ai const adminDid = appConfig.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) { if (!response.ok) {
console.warn('Failed to fetch user list records'); console.warn('Failed to fetch user list records');
@ -358,8 +359,8 @@ function App() {
const getDefaultUsers = () => { const getDefaultUsers = () => {
const defaultUsers = [ const defaultUsers = [
// bsky.social - 実際のDIDを使用 // Default admin user
{ did: 'did:plc:uqzpqmrjnptsxezjx4xuh2mn', handle: 'syui.ai', pds: 'https://bsky.social' }, { 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}`); console.log(`Fetching comments from user: ${user.handle} (${user.did}) at ${user.pds}`);
// Public API使用認証不要 // 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) { if (!response.ok) {
console.warn(`Failed to fetch from ${user.handle} (${response.status}): ${response.statusText}`); console.warn(`Failed to fetch from ${user.handle} (${response.status}): ${response.statusText}`);
@ -490,12 +491,13 @@ function App() {
throw new Error('No agent available'); 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 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 = { const record = {
$type: 'ai.syui.log', $type: appConfig.collections.comment,
text: commentText, text: commentText,
url: window.location.href, url: window.location.href,
createdAt: now.toISOString(), createdAt: now.toISOString(),
@ -510,7 +512,7 @@ function App() {
// Post to ATProto with rkey // Post to ATProto with rkey
const response = await agent.api.com.atproto.repo.putRecord({ const response = await agent.api.com.atproto.repo.putRecord({
repo: user.did, repo: user.did,
collection: 'ai.syui.log', collection: appConfig.collections.comment,
rkey: rkey, rkey: rkey,
record: record, record: record,
}); });
@ -553,7 +555,7 @@ function App() {
// Delete the record // Delete the record
await agent.api.com.atproto.repo.deleteRecord({ await agent.api.com.atproto.repo.deleteRecord({
repo: user.did, repo: user.did,
collection: 'ai.syui.log', collection: appConfig.collections.comment,
rkey: rkey, rkey: rkey,
}); });
@ -578,7 +580,7 @@ function App() {
// 管理者チェック // 管理者チェック
const isAdmin = (user: User | null): boolean => { 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 rkey = now.toISOString().replace(/[:.]/g, '-');
const record = { const record = {
$type: 'ai.syui.log.user', $type: appConfig.collections.user,
users: users, users: users,
createdAt: now.toISOString(), createdAt: now.toISOString(),
updatedBy: { updatedBy: {
@ -652,7 +654,7 @@ function App() {
// Post to ATProto with rkey // Post to ATProto with rkey
const response = await agent.api.com.atproto.repo.putRecord({ const response = await agent.api.com.atproto.repo.putRecord({
repo: user.did, repo: user.did,
collection: 'ai.syui.log.user', collection: appConfig.collections.user,
rkey: rkey, rkey: rkey,
record: record, record: record,
}); });
@ -697,7 +699,7 @@ function App() {
// Delete the record // Delete the record
await agent.api.com.atproto.repo.deleteRecord({ await agent.api.com.atproto.repo.deleteRecord({
repo: user.did, repo: user.did,
collection: 'ai.syui.log.user', collection: appConfig.collections.user,
rkey: rkey, 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 // OAuth callback is now handled by React Router in main.tsx
console.log('=== APP.TSX URL CHECK ==='); console.log('=== APP.TSX URL CHECK ===');
console.log('Full URL:', window.location.href); console.log('Full URL:', window.location.href);
@ -896,10 +914,12 @@ function App() {
<div className="comments-header"> <div className="comments-header">
<h3>Comments</h3> <h3>Comments</h3>
</div> </div>
{comments.length === 0 ? ( {comments.filter(shouldShowComment).length === 0 ? (
<p className="no-comments">No comments yet</p> <p className="no-comments">
{appConfig.rkey ? `No comments for this post yet` : `No comments yet`}
</p>
) : ( ) : (
comments.map((record, index) => ( comments.filter(shouldShowComment).map((record, index) => (
<div key={index} className="comment-item"> <div key={index} className="comment-item">
<div className="comment-header"> <div className="comment-header">
<img <img
@ -945,8 +965,8 @@ function App() {
)} )}
</div> </div>
{/* Comment Form - Outside user section, after comments list */} {/* Comment Form - Only show on post pages */}
{user && ( {user && appConfig.rkey && (
<div className="comment-form"> <div className="comment-form">
<h3>Post a Comment</h3> <h3>Post a Comment</h3>
<textarea <textarea
@ -969,6 +989,14 @@ function App() {
{error && <p className="error">{error}</p>} {error && <p className="error">{error}</p>}
</div> </div>
)} )}
{/* Show authentication status on non-post pages */}
{user && !appConfig.rkey && (
<div className="auth-status">
<p> Authenticated as @{user.handle}</p>
<p><small>Visit a post page to comment</small></p>
</div>
)}
</section> </section>
</main> </main>

View File

@ -0,0 +1,84 @@
// Application configuration
export interface AppConfig {
adminDid: string;
collections: {
comment: string;
user: string;
};
host: string;
rkey?: string; // Current post rkey if on post page
}
// Generate collection names from host
// Format: ${reg}.${name}.${sub}
// Example: log.syui.ai -> ai.syui.log
function generateCollectionNames(host: string): { comment: string; user: string } {
try {
// Remove protocol if present
const cleanHost = host.replace(/^https?:\/\//, '');
// Split host into parts
const parts = cleanHost.split('.');
if (parts.length < 2) {
throw new Error('Invalid host format');
}
// Reverse the parts for collection naming
// log.syui.ai -> ai.syui.log
const reversedParts = parts.reverse();
const collectionBase = reversedParts.join('.');
return {
comment: collectionBase,
user: `${collectionBase}.user`
};
} catch (error) {
console.warn('Failed to generate collection names from host:', host, error);
// Fallback to default collections
return {
comment: 'ai.syui.log',
user: 'ai.syui.log.user'
};
}
}
// Extract rkey from current URL
// /posts/xxx.html -> xxx
function extractRkeyFromUrl(): string | undefined {
const pathname = window.location.pathname;
const match = pathname.match(/\/posts\/([^/]+)\.html$/);
return match ? match[1] : undefined;
}
// Get application configuration from environment variables
export function getAppConfig(): AppConfig {
const host = import.meta.env.VITE_APP_HOST || 'https://log.syui.ai';
const adminDid = import.meta.env.VITE_ADMIN_DID || 'did:plc:uqzpqmrjnptsxezjx4xuh2mn';
// Priority: Environment variables > Auto-generated from host
const autoGeneratedCollections = generateCollectionNames(host);
const collections = {
comment: import.meta.env.VITE_COLLECTION_COMMENT || autoGeneratedCollections.comment,
user: import.meta.env.VITE_COLLECTION_USER || autoGeneratedCollections.user,
};
const rkey = extractRkeyFromUrl();
console.log('App configuration:', {
host,
adminDid,
collections,
rkey: rkey || 'none (not on post page)'
});
return {
adminDid,
collections,
host,
rkey
};
}
// Export singleton instance
export const appConfig = getAppConfig();