Initial commit: Coffee Map PWA

Vue 3 + Vite PWA backed by ATProto PDS (coffee.apoena.dev).
Stores coffee spots as dev.apoena.coffeespot records with name,
geolocation, note, and status. Map via MapLibre + OpenFreeMap,
auth via ATProto OAuth, deploy via Docker + Nginx on Coolify.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Calixte
2026-03-28 23:01:17 +01:00
commit 645f93069c
26 changed files with 7241 additions and 0 deletions

35
src/lib/atproto.ts Normal file
View File

@@ -0,0 +1,35 @@
import { BrowserOAuthClient } from '@atproto/oauth-client-browser'
import { Agent } from '@atproto/api'
// The client_id must equal the public URL of client-metadata.json.
// Update VITE_APP_URL in your environment or set it here directly.
const APP_URL = import.meta.env.VITE_APP_URL ?? 'https://coffee.apoena.dev'
let _client: BrowserOAuthClient | null = null
export function getOAuthClient(): BrowserOAuthClient {
if (!_client) {
_client = new BrowserOAuthClient({
clientMetadata: {
client_id: `${APP_URL}/client-metadata.json`,
client_name: 'Coffee Map',
client_uri: APP_URL,
redirect_uris: [`${APP_URL}/oauth/callback`],
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
scope: 'atproto transition:generic',
dpop_bound_access_tokens: true,
token_endpoint_auth_method: 'none',
application_type: 'web',
},
// Use the public ATProto resolver — for full privacy use your own PDS
handleResolver: 'https://bsky.social',
})
}
return _client
}
export function createAgent(session: Awaited<ReturnType<BrowserOAuthClient['restore']>>): Agent {
if (!session) throw new Error('No session')
return new Agent(session)
}