- Created backend structure with Go, including main application logic and API endpoints. - Added Docker support for both development and production environments. - Introduced frontend using Nuxt 3 with Tailwind CSS for styling. - Included configuration files for Docker and environment variables. - Established basic documentation for contributing, development, and deployment processes. - Set up .gitignore and .dockerignore files to manage ignored files in the repository.
223 lines
6.3 KiB
TypeScript
223 lines
6.3 KiB
TypeScript
export interface MeResponse {
|
|
username: string
|
|
auths: string[]
|
|
tokens?: string[]
|
|
prefix?: string
|
|
}
|
|
|
|
export interface MapInfoAdmin {
|
|
ID: number
|
|
Name: string
|
|
Hidden: boolean
|
|
Priority: boolean
|
|
}
|
|
|
|
export interface SettingsResponse {
|
|
prefix: string
|
|
defaultHide: boolean
|
|
title: string
|
|
}
|
|
|
|
// Singleton: shared by all useMapApi() callers so 401 triggers one global handler (e.g. app.vue)
|
|
const onApiErrorCallbacks: (() => void)[] = []
|
|
|
|
export function useMapApi() {
|
|
const config = useRuntimeConfig()
|
|
const apiBase = config.public.apiBase as string
|
|
|
|
function onApiError(cb: () => void) {
|
|
onApiErrorCallbacks.push(cb)
|
|
}
|
|
|
|
async function request<T>(path: string, opts?: RequestInit): Promise<T> {
|
|
const url = path.startsWith('http') ? path : `${apiBase}/${path.replace(/^\//, '')}`
|
|
const res = await fetch(url, { credentials: 'include', ...opts })
|
|
// Only redirect to login on 401 (session invalid); 403 = forbidden (no permission)
|
|
if (res.status === 401) {
|
|
onApiErrorCallbacks.forEach((cb) => cb())
|
|
throw new Error('Unauthorized')
|
|
}
|
|
if (res.status === 403) throw new Error('Forbidden')
|
|
if (!res.ok) throw new Error(`API ${res.status}`)
|
|
if (res.headers.get('content-type')?.includes('application/json')) {
|
|
return res.json() as Promise<T>
|
|
}
|
|
return undefined as T
|
|
}
|
|
|
|
async function getConfig() {
|
|
return request<{ title?: string; auths?: string[] }>('config')
|
|
}
|
|
|
|
async function getCharacters() {
|
|
return request<unknown[]>('v1/characters')
|
|
}
|
|
|
|
async function getMarkers() {
|
|
return request<unknown[]>('v1/markers')
|
|
}
|
|
|
|
async function getMaps() {
|
|
return request<Record<string, { ID: number; Name: string; size?: number }>>('maps')
|
|
}
|
|
|
|
// Auth
|
|
async function login(user: string, pass: string) {
|
|
const res = await fetch(`${apiBase}/login`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ user, pass }),
|
|
})
|
|
if (res.status === 401) throw new Error('Unauthorized')
|
|
if (!res.ok) throw new Error(`API ${res.status}`)
|
|
return res.json() as Promise<MeResponse>
|
|
}
|
|
|
|
async function logout() {
|
|
await fetch(`${apiBase}/logout`, { method: 'POST', credentials: 'include' })
|
|
}
|
|
|
|
async function me() {
|
|
return request<MeResponse>('me')
|
|
}
|
|
|
|
/** Public: whether first-time setup (no users) is required. */
|
|
async function setupRequired(): Promise<{ setupRequired: boolean }> {
|
|
const res = await fetch(`${apiBase}/setup`, { credentials: 'include' })
|
|
if (!res.ok) throw new Error(`API ${res.status}`)
|
|
return res.json() as Promise<{ setupRequired: boolean }>
|
|
}
|
|
|
|
// Profile
|
|
async function meTokens() {
|
|
const data = await request<{ tokens: string[] }>('me/tokens', { method: 'POST' })
|
|
return data!.tokens
|
|
}
|
|
|
|
async function mePassword(pass: string) {
|
|
await request('me/password', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ pass }),
|
|
})
|
|
}
|
|
|
|
// Admin
|
|
async function adminUsers() {
|
|
return request<string[]>('admin/users')
|
|
}
|
|
|
|
async function adminUserByName(name: string) {
|
|
return request<{ username: string; auths: string[] }>(`admin/users/${encodeURIComponent(name)}`)
|
|
}
|
|
|
|
async function adminUserPost(body: { user: string; pass?: string; auths: string[] }) {
|
|
await request('admin/users', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
})
|
|
}
|
|
|
|
async function adminUserDelete(name: string) {
|
|
await request(`admin/users/${encodeURIComponent(name)}`, { method: 'DELETE' })
|
|
}
|
|
|
|
async function adminSettings() {
|
|
return request<SettingsResponse>('admin/settings')
|
|
}
|
|
|
|
async function adminSettingsPost(body: { prefix?: string; defaultHide?: boolean; title?: string }) {
|
|
await request('admin/settings', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
})
|
|
}
|
|
|
|
async function adminMaps() {
|
|
return request<MapInfoAdmin[]>('admin/maps')
|
|
}
|
|
|
|
async function adminMapPost(id: number, body: { name: string; hidden: boolean; priority: boolean }) {
|
|
await request(`admin/maps/${id}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
})
|
|
}
|
|
|
|
async function adminMapToggleHidden(id: number) {
|
|
return request<MapInfoAdmin>(`admin/maps/${id}/toggle-hidden`, { method: 'POST' })
|
|
}
|
|
|
|
async function adminWipe() {
|
|
await request('admin/wipe', { method: 'POST' })
|
|
}
|
|
|
|
async function adminRebuildZooms() {
|
|
await request('admin/rebuildZooms', { method: 'POST' })
|
|
}
|
|
|
|
function adminExportUrl() {
|
|
return `${apiBase}/admin/export`
|
|
}
|
|
|
|
async function adminMerge(formData: FormData) {
|
|
const res = await fetch(`${apiBase}/admin/merge`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
body: formData,
|
|
})
|
|
if (res.status === 401 || res.status === 403) {
|
|
onApiErrorCallbacks.forEach((cb) => cb())
|
|
throw new Error('Unauthorized')
|
|
}
|
|
if (!res.ok) throw new Error(`API ${res.status}`)
|
|
}
|
|
|
|
async function adminWipeTile(params: { map: number; x: number; y: number }) {
|
|
return request(`admin/wipeTile?${new URLSearchParams(params as any)}`)
|
|
}
|
|
|
|
async function adminSetCoords(params: { map: number; fx: number; fy: number; tx: number; ty: number }) {
|
|
return request(`admin/setCoords?${new URLSearchParams(params as any)}`)
|
|
}
|
|
|
|
async function adminHideMarker(params: { id: number }) {
|
|
return request(`admin/hideMarker?${new URLSearchParams(params as any)}`)
|
|
}
|
|
|
|
return {
|
|
apiBase,
|
|
onApiError,
|
|
getConfig,
|
|
getCharacters,
|
|
getMarkers,
|
|
getMaps,
|
|
login,
|
|
logout,
|
|
me,
|
|
setupRequired,
|
|
meTokens,
|
|
mePassword,
|
|
adminUsers,
|
|
adminUserByName,
|
|
adminUserPost,
|
|
adminUserDelete,
|
|
adminSettings,
|
|
adminSettingsPost,
|
|
adminMaps,
|
|
adminMapPost,
|
|
adminMapToggleHidden,
|
|
adminWipe,
|
|
adminRebuildZooms,
|
|
adminExportUrl,
|
|
adminMerge,
|
|
adminWipeTile,
|
|
adminSetCoords,
|
|
adminHideMarker,
|
|
}
|
|
}
|