Initial Commit
This commit is contained in:
145
client/src/api.js
Normal file
145
client/src/api.js
Normal file
@@ -0,0 +1,145 @@
|
||||
const ADS_BASE = '/api/ads';
|
||||
const APPS_BASE = '/api/apps';
|
||||
|
||||
function authHeaders(extra = {}) {
|
||||
const token = localStorage.getItem('showcase_token');
|
||||
return token ? { Authorization: `Bearer ${token}`, ...extra } : { ...extra };
|
||||
}
|
||||
|
||||
async function apiFetch(url, options = {}) {
|
||||
const res = await fetch(url, {
|
||||
...options,
|
||||
headers: { ...authHeaders(), ...options.headers },
|
||||
});
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem('showcase_token');
|
||||
window.location.href = '/login';
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// ── auth ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function login(password) {
|
||||
const res = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
if (!res.ok) throw new Error('Invalid password');
|
||||
const { token } = await res.json();
|
||||
localStorage.setItem('showcase_token', token);
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
localStorage.removeItem('showcase_token');
|
||||
}
|
||||
|
||||
export function isAuthenticated() {
|
||||
return !!localStorage.getItem('showcase_token');
|
||||
}
|
||||
|
||||
// ── ads ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function listAds(appId) {
|
||||
const url = appId ? `${ADS_BASE}?appId=${encodeURIComponent(appId)}` : ADS_BASE;
|
||||
const res = await apiFetch(url);
|
||||
if (!res.ok) throw new Error('Failed to fetch ads');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function getAd(id) {
|
||||
const res = await apiFetch(`${ADS_BASE}/${id}`);
|
||||
if (!res.ok) throw new Error('Ad not found');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function uploadAd(file) {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
const res = await apiFetch(ADS_BASE, { method: 'POST', body: form });
|
||||
if (!res.ok) throw new Error('Upload failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function updateAd(id, data) {
|
||||
const res = await apiFetch(`${ADS_BASE}/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) throw new Error('Update failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function updateAdFile(id, file) {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
const res = await apiFetch(`${ADS_BASE}/${id}/file`, { method: 'POST', body: form });
|
||||
if (!res.ok) throw new Error('File update failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function deleteAd(id) {
|
||||
const res = await apiFetch(`${ADS_BASE}/${id}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw new Error('Delete failed');
|
||||
}
|
||||
|
||||
export async function requestScreenshot(id) {
|
||||
const res = await apiFetch(`${ADS_BASE}/${id}/screenshot`, { method: 'POST' });
|
||||
if (!res.ok) throw new Error('Screenshot failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function saveThumbnail(id, dataUrl) {
|
||||
const res = await apiFetch(`${ADS_BASE}/${id}/thumbnail`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ dataUrl }),
|
||||
});
|
||||
if (!res.ok) throw new Error('Thumbnail save failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// ── apps ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function lookupApp(url) {
|
||||
const res = await apiFetch(`${APPS_BASE}/lookup?url=${encodeURIComponent(url)}`);
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.error || 'Lookup failed');
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function listApps() {
|
||||
const res = await apiFetch(APPS_BASE);
|
||||
if (!res.ok) throw new Error('Failed to fetch apps');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function createApp(data) {
|
||||
const res = await apiFetch(APPS_BASE, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) throw new Error('Create app failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function updateApp(id, data) {
|
||||
const res = await apiFetch(`${APPS_BASE}/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) throw new Error('Update app failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function deleteApp(id) {
|
||||
const res = await apiFetch(`${APPS_BASE}/${id}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw new Error('Delete app failed');
|
||||
}
|
||||
Reference in New Issue
Block a user