fix xxxcard log
Some checks failed
Deploy ailog / build-and-deploy (push) Failing after 12m39s

This commit is contained in:
2025-06-11 13:20:01 +09:00
parent 4fe0582c6b
commit bebd6a61eb
11 changed files with 133 additions and 81 deletions

View File

@ -16,8 +16,8 @@ AI-powered static blog generator with ATProto integration, part of the ai.ai eco
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `./run.zsh c` | Enable Cloudflare tunnel (xxxcard.syui.ai) for OAuth | | `./run.zsh c` | Enable Cloudflare tunnel (log.syui.ai) for OAuth |
| `./run.zsh o` | Start OAuth web server (port:4173 = xxxcard.syui.ai) | | `./run.zsh o` | Start OAuth web server (port:4173 = log.syui.ai) |
| `./run.zsh co` | Start comment system (ATProto stream monitor) | | `./run.zsh co` | Start comment system (ATProto stream monitor) |
## 🏗️ Architecture (Pure Rust + HTML + JS) ## 🏗️ Architecture (Pure Rust + HTML + JS)

4
aicard-web-oauth/.env Normal file
View File

@ -0,0 +1,4 @@
# Default environment variables (fallback)
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

View File

@ -0,0 +1,4 @@
# 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

View File

@ -0,0 +1,4 @@
# 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

View File

@ -6,6 +6,7 @@
"dev": "vite --mode development", "dev": "vite --mode development",
"build": "vite build --mode production", "build": "vite build --mode production",
"build:dev": "vite build --mode development", "build:dev": "vite build --mode development",
"build:local": "VITE_APP_HOST=http://localhost:4173 vite build --mode development",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {

View File

@ -1,13 +1,13 @@
{ {
"client_id": "https://xxxcard.syui.ai/client-metadata.json", "client_id": "https://log.syui.ai/client-metadata.json",
"client_name": "ai.card", "client_name": "ai.card",
"client_uri": "https://xxxcard.syui.ai", "client_uri": "https://log.syui.ai",
"logo_uri": "https://xxxcard.syui.ai/favicon.ico", "logo_uri": "https://log.syui.ai/favicon.ico",
"tos_uri": "https://xxxcard.syui.ai/terms", "tos_uri": "https://log.syui.ai/terms",
"policy_uri": "https://xxxcard.syui.ai/privacy", "policy_uri": "https://log.syui.ai/privacy",
"redirect_uris": [ "redirect_uris": [
"https://xxxcard.syui.ai/oauth/callback", "https://log.syui.ai/oauth/callback",
"https://xxxcard.syui.ai/" "https://log.syui.ai/"
], ],
"response_types": [ "response_types": [
"code" "code"

View File

@ -99,9 +99,9 @@ function App() {
return false; return false;
}; };
// キャッシュがなければ、ATProtoから取得 // キャッシュがなければ、ATProtoから取得(認証状態に関係なく)
if (!loadCachedComments()) { if (!loadCachedComments()) {
loadAllComments(window.location.href); loadAllComments(); // URLフィルタリングを無効にして全コメント表示
} }
// Handle popstate events for mock OAuth flow // Handle popstate events for mock OAuth flow
@ -142,7 +142,8 @@ function App() {
setUser(userProfile); setUser(userProfile);
// Load all comments for display (this will be the default view) // Load all comments for display (this will be the default view)
loadAllComments(window.location.href); // Temporarily disable URL filtering to see all comments
loadAllComments();
// Load user list records if admin // Load user list records if admin
if (userProfile.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') { if (userProfile.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') {
@ -161,7 +162,8 @@ function App() {
setUser(verifiedUser); setUser(verifiedUser);
// Load all comments for display (this will be the default view) // Load all comments for display (this will be the default view)
loadAllComments(window.location.href); // Temporarily disable URL filtering to see all comments
loadAllComments();
// Load user list records if admin // Load user list records if admin
if (verifiedUser.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') { if (verifiedUser.did === 'did:plc:uqzpqmrjnptsxezjx4xuh2mn') {
@ -169,6 +171,9 @@ function App() {
} }
} }
setIsLoading(false); setIsLoading(false);
// 認証状態に関係なく、コメントを読み込む
loadAllComments();
}; };
checkAuth(); checkAuth();
@ -265,17 +270,20 @@ function App() {
try { try {
// 管理者のユーザーリストを取得 (ai.syui.log.user collection) // 管理者のユーザーリストを取得 (ai.syui.log.user collection)
const adminDid = 'did:plc:uqzpqmrjnptsxezjx4xuh2mn'; // syui.ai const adminDid = 'did:plc:uqzpqmrjnptsxezjx4xuh2mn'; // syui.ai
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=ai.syui.log.user&limit=100`);
if (!response.ok) { if (!response.ok) {
console.warn('Failed to fetch user list from admin, using default users'); console.warn('Failed to fetch user list from admin, using default users. Status:', response.status);
return getDefaultUsers(); return getDefaultUsers();
} }
const data = await response.json(); const data = await response.json();
const userRecords = data.records || []; const userRecords = data.records || [];
console.log('User records found:', userRecords.length);
if (userRecords.length === 0) { if (userRecords.length === 0) {
console.log('No user records found, using default users');
return getDefaultUsers(); return getDefaultUsers();
} }
@ -349,20 +357,33 @@ function App() {
}; };
const getDefaultUsers = () => { const getDefaultUsers = () => {
return [ const defaultUsers = [
// bsky.social - 実際のDIDを使用 // bsky.social - 実際のDIDを使用
{ did: 'did:plc:uqzpqmrjnptsxezjx4xuh2mn', handle: 'syui.ai', pds: 'https://bsky.social' }, { did: 'did:plc:uqzpqmrjnptsxezjx4xuh2mn', handle: 'syui.ai', pds: 'https://bsky.social' },
// 他のユーザーは実際のDIDが不明なので、実在するユーザーのみ含める
]; ];
// 現在ログインしているユーザーも追加(重複チェック)
if (user && user.did && user.handle && !defaultUsers.find(u => u.did === user.did)) {
defaultUsers.push({
did: user.did,
handle: user.handle,
pds: user.handle.endsWith('.syu.is') ? 'https://syu.is' : 'https://bsky.social'
});
}
console.log('Default users list (including current user):', defaultUsers);
return defaultUsers;
}; };
// 新しい関数: 全ユーザーからコメントを収集 // 新しい関数: 全ユーザーからコメントを収集
const loadAllComments = async (pageUrl?: string) => { const loadAllComments = async (pageUrl?: string) => {
try { try {
console.log('Loading comments from all users...'); console.log('Loading comments from all users...');
console.log('Page URL filter:', pageUrl);
// ユーザーリストを動的に取得 // ユーザーリストを動的に取得
const knownUsers = await loadUsersFromRecord(); const knownUsers = await loadUsersFromRecord();
console.log('Known users for comment fetching:', knownUsers);
const allComments = []; const allComments = [];
@ -388,7 +409,8 @@ function App() {
? userComments.filter(record => record.value.url === pageUrl) ? userComments.filter(record => record.value.url === pageUrl)
: userComments; : userComments;
console.log(`After URL filtering: ${filteredComments.length} comments from ${user.handle}`); console.log(`After URL filtering (${pageUrl}): ${filteredComments.length} comments from ${user.handle}`);
console.log('All comments from this user:', userComments.map(r => ({ url: r.value.url, text: r.value.text })));
allComments.push(...filteredComments); allComments.push(...filteredComments);
} catch (err) { } catch (err) {
console.warn(`Failed to load comments from ${user.handle}:`, err); console.warn(`Failed to load comments from ${user.handle}:`, err);
@ -859,14 +881,16 @@ function App() {
<button <button
onClick={() => user && loadUserComments(user.did)} onClick={() => user && loadUserComments(user.did)}
className="comments-toggle-button" className="comments-toggle-button"
disabled={!user}
title={!user ? "Login required to view your comments" : ""}
> >
My Comments My Comments {!user && "(Login Required)"}
</button> </button>
<button <button
onClick={() => loadAllComments(window.location.href)} onClick={() => loadAllComments()}
className="comments-toggle-button" className="comments-toggle-button"
> >
All Comments All Comments (No Filter)
</button> </button>
</div> </div>
</div> </div>

View File

@ -188,13 +188,15 @@ class AtprotoOAuthService {
} }
private getClientId(): string { private getClientId(): string {
const origin = window.location.origin; // Use environment variable if available
const envClientId = import.meta.env.VITE_OAUTH_CLIENT_ID;
// For production (xxxcard.syui.ai), use the actual URL if (envClientId) {
if (origin.includes('xxxcard.syui.ai')) { console.log('Using client ID from environment:', envClientId);
return `${origin}/client-metadata.json`; return envClientId;
} }
const origin = window.location.origin;
// For localhost development, use undefined for loopback client // For localhost development, use undefined for loopback client
// The BrowserOAuthClient will handle this automatically // The BrowserOAuthClient will handle this automatically
if (origin.includes('localhost') || origin.includes('127.0.0.1')) { if (origin.includes('localhost') || origin.includes('127.0.0.1')) {

View File

@ -157,40 +157,19 @@ export class OAuthKeyManager {
* Generate dynamic client metadata based on current URL * Generate dynamic client metadata based on current URL
*/ */
export function generateClientMetadata(): any { export function generateClientMetadata(): any {
const origin = window.location.origin; // Use environment variables if available, fallback to current origin
const clientId = `${origin}/client-metadata.json`; const host = import.meta.env.VITE_APP_HOST || window.location.origin;
const clientId = import.meta.env.VITE_OAUTH_CLIENT_ID || `${host}/client-metadata.json`;
const redirectUri = import.meta.env.VITE_OAUTH_REDIRECT_URI || `${host}/oauth/callback`;
// Use static production metadata for xxxcard.syui.ai
if (origin === 'https://xxxcard.syui.ai') {
return {
client_id: 'https://xxxcard.syui.ai/client-metadata.json',
client_name: 'ai.card',
client_uri: 'https://xxxcard.syui.ai',
logo_uri: 'https://xxxcard.syui.ai/favicon.ico',
tos_uri: 'https://xxxcard.syui.ai/terms',
policy_uri: 'https://xxxcard.syui.ai/privacy',
redirect_uris: ['https://xxxcard.syui.ai/oauth/callback'],
response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'],
token_endpoint_auth_method: 'private_key_jwt',
token_endpoint_auth_signing_alg: 'ES256',
scope: 'atproto transition:generic',
subject_type: 'public',
application_type: 'web',
dpop_bound_access_tokens: true,
jwks_uri: 'https://xxxcard.syui.ai/.well-known/jwks.json'
};
}
// Dynamic metadata for development
return { return {
client_id: clientId, client_id: clientId,
client_name: 'ai.card', client_name: 'ai.card',
client_uri: origin, client_uri: host,
logo_uri: `${origin}/favicon.ico`, logo_uri: `${host}/favicon.ico`,
tos_uri: `${origin}/terms`, tos_uri: `${host}/terms`,
policy_uri: `${origin}/privacy`, policy_uri: `${host}/privacy`,
redirect_uris: [`${origin}/oauth/callback`], redirect_uris: [redirectUri, host],
response_types: ['code'], response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'], grant_types: ['authorization_code', 'refresh_token'],
token_endpoint_auth_method: 'private_key_jwt', token_endpoint_auth_method: 'private_key_jwt',
@ -199,6 +178,6 @@ export function generateClientMetadata(): any {
subject_type: 'public', subject_type: 'public',
application_type: 'web', application_type: 'web',
dpop_bound_access_tokens: true, dpop_bound_access_tokens: true,
jwks_uri: `${origin}/.well-known/jwks.json` jwks_uri: `${host}/.well-known/jwks.json`
}; };
} }

View File

@ -1,31 +1,58 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import fs from 'fs'
import path from 'path'
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [react()], // Load env file based on `mode` in the current working directory.
build: { const env = loadEnv(mode, process.cwd(), '')
// Keep console.log in production for debugging
minify: 'esbuild', return {
}, plugins: [
esbuild: { react(),
drop: [], // Don't drop console.log // Custom plugin to replace variables in public files during build
}, {
server: { name: 'replace-env-vars',
port: 5173, writeBundle() {
host: '127.0.0.1', const host = env.VITE_APP_HOST || 'https://log.syui.ai'
allowedHosts: ['localhost', '127.0.0.1', 'xxxcard.syui.ai'], const clientId = env.VITE_OAUTH_CLIENT_ID || `${host}/client-metadata.json`
proxy: { const redirectUri = env.VITE_OAUTH_REDIRECT_URI || `${host}/oauth/callback`
'/api': {
target: 'http://127.0.0.1:8000', // Replace variables in client-metadata.json
changeOrigin: true, const clientMetadataPath = path.resolve(__dirname, 'dist/client-metadata.json')
secure: false, if (fs.existsSync(clientMetadataPath)) {
let content = fs.readFileSync(clientMetadataPath, 'utf-8')
content = content.replace(/https:\/\/log\.syui\.ai/g, host)
fs.writeFileSync(clientMetadataPath, content)
console.log(`Updated client-metadata.json with host: ${host}`)
}
}
} }
],
build: {
// Keep console.log in production for debugging
minify: 'esbuild',
}, },
// Handle OAuth callback routing esbuild: {
historyApiFallback: { drop: [], // Don't drop console.log
rewrites: [ },
{ from: /^\/oauth\/callback/, to: '/index.html' } server: {
] port: 5173,
host: '127.0.0.1',
allowedHosts: ['localhost', '127.0.0.1', 'log.syui.ai'],
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
secure: false,
}
},
// Handle OAuth callback routing
historyApiFallback: {
rewrites: [
{ from: /^\/oauth\/callback/, to: '/index.html' }
]
}
} }
} }
}) })

View File

@ -18,7 +18,8 @@ function _server() {
function _server_public() { function _server_public() {
_env _env
cloudflared tunnel --config $d/aicard-web-oauth/cloudflared-config.yml run #cloudflared tunnel --config $d/aicard-web-oauth/cloudflared-config.yml run
cloudflared tunnel --config $d/cloudflared-config.yml run
} }
function _oauth_build() { function _oauth_build() {
@ -29,6 +30,12 @@ function _oauth_build() {
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion [ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion
nvm use 21 nvm use 21
npm i npm i
# Build with production environment variables
export VITE_APP_HOST="https://log.syui.ai"
export VITE_OAUTH_CLIENT_ID="https://log.syui.ai/client-metadata.json"
export VITE_OAUTH_REDIRECT_URI="https://log.syui.ai/oauth/callback"
npm run build npm run build
npm run preview npm run preview
} }