Import source: app/src/lib/engagement-api
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,242 @@
|
|||||||
|
import { api } from './api.js';
|
||||||
|
|
||||||
|
function toQuery(params = {}) {
|
||||||
|
const query = new URLSearchParams();
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== null && value !== '') query.set(key, String(value));
|
||||||
|
});
|
||||||
|
const s = query.toString();
|
||||||
|
return s ? `?${s}` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const engagementApi = {
|
||||||
|
listComments(slug, params) {
|
||||||
|
return api.get(`/api/articles/${encodeURIComponent(slug)}/comments${toQuery(params)}`);
|
||||||
|
},
|
||||||
|
postComment(slug, body, replyToId) {
|
||||||
|
return api.post(`/api/articles/${encodeURIComponent(slug)}/comments`, { body, replyToId });
|
||||||
|
},
|
||||||
|
flagComment(id) {
|
||||||
|
return api.post(`/api/comments/${encodeURIComponent(id)}/flag`, {});
|
||||||
|
},
|
||||||
|
likeComment(id) {
|
||||||
|
return api.post(`/api/comments/${encodeURIComponent(id)}/like`, {});
|
||||||
|
},
|
||||||
|
upvoteComment(id) {
|
||||||
|
return api.post(`/api/comments/${encodeURIComponent(id)}/upvote`, {});
|
||||||
|
},
|
||||||
|
recordHistory(articleId, progress = 0) {
|
||||||
|
return api.post('/api/auth/history', { articleId, progress });
|
||||||
|
},
|
||||||
|
getStreaks() {
|
||||||
|
return api.get('/api/auth/streaks');
|
||||||
|
},
|
||||||
|
listHistory(params) {
|
||||||
|
return api.get(`/api/auth/history${toQuery(params)}`);
|
||||||
|
},
|
||||||
|
clearHistory() {
|
||||||
|
return api.del('/api/auth/history');
|
||||||
|
},
|
||||||
|
listNotifications(params) {
|
||||||
|
return api.get(`/api/auth/notifications${toQuery(params)}`);
|
||||||
|
},
|
||||||
|
markNotificationsRead() {
|
||||||
|
return api.post('/api/auth/notifications/read-all', {});
|
||||||
|
},
|
||||||
|
markNotificationRead(id) {
|
||||||
|
return api.patch(`/api/auth/notifications/${encodeURIComponent(id)}/read`, {});
|
||||||
|
},
|
||||||
|
deleteNotification(id) {
|
||||||
|
return api.del(`/api/auth/notifications/${encodeURIComponent(id)}`);
|
||||||
|
},
|
||||||
|
followWriter(username) {
|
||||||
|
return api.post(`/api/writers/${encodeURIComponent(username.replace(/^@/, ''))}/follow`, {});
|
||||||
|
},
|
||||||
|
unfollowWriter(username) {
|
||||||
|
return api.del(`/api/writers/${encodeURIComponent(username.replace(/^@/, ''))}/follow`);
|
||||||
|
},
|
||||||
|
writerFollowers(username, params) {
|
||||||
|
return api.get(`/api/writers/${encodeURIComponent(username.replace(/^@/, ''))}/followers${toQuery(params)}`);
|
||||||
|
},
|
||||||
|
followTopic(slug) {
|
||||||
|
return api.post(`/api/topics/${encodeURIComponent(slug)}/follow`, {});
|
||||||
|
},
|
||||||
|
unfollowTopic(slug) {
|
||||||
|
return api.del(`/api/topics/${encodeURIComponent(slug)}/follow`);
|
||||||
|
},
|
||||||
|
topicFollowers(slug, params) {
|
||||||
|
return api.get(`/api/topics/${encodeURIComponent(slug)}/followers${toQuery(params)}`);
|
||||||
|
},
|
||||||
|
following() {
|
||||||
|
return api.get('/api/auth/following');
|
||||||
|
},
|
||||||
|
listReadingLists() {
|
||||||
|
return api.get('/api/auth/lists');
|
||||||
|
},
|
||||||
|
createReadingList(name) {
|
||||||
|
return api.post('/api/auth/lists', { name });
|
||||||
|
},
|
||||||
|
deleteReadingList(id) {
|
||||||
|
return api.del(`/api/auth/lists/${encodeURIComponent(id)}`);
|
||||||
|
},
|
||||||
|
addToReadingList(listId, articleId) {
|
||||||
|
return api.post(`/api/auth/lists/${encodeURIComponent(listId)}/items`, { articleId });
|
||||||
|
},
|
||||||
|
removeFromReadingList(listId, itemId) {
|
||||||
|
return api.del(`/api/auth/lists/${encodeURIComponent(listId)}/items/${encodeURIComponent(itemId)}`);
|
||||||
|
},
|
||||||
|
reorderReadingList(listId, itemIds) {
|
||||||
|
return api.put(`/api/auth/lists/${encodeURIComponent(listId)}/reorder`, { itemIds });
|
||||||
|
},
|
||||||
|
listQa(slug) {
|
||||||
|
return api.get(`/api/articles/${encodeURIComponent(slug)}/qa`);
|
||||||
|
},
|
||||||
|
postQuestion(slug, body) {
|
||||||
|
return api.post(`/api/articles/${encodeURIComponent(slug)}/qa`, { body });
|
||||||
|
},
|
||||||
|
postAnswer(questionId, body) {
|
||||||
|
return api.post(`/api/qa/questions/${encodeURIComponent(questionId)}/answers`, { body });
|
||||||
|
},
|
||||||
|
writerAnalyticsOverview() {
|
||||||
|
return api.get('/api/auth/analytics/overview');
|
||||||
|
},
|
||||||
|
writerAnalyticsArticles() {
|
||||||
|
return api.get('/api/auth/analytics/articles');
|
||||||
|
},
|
||||||
|
writerAnalyticsArticle(slug) {
|
||||||
|
return api.get(`/api/auth/analytics/article/${encodeURIComponent(slug)}`);
|
||||||
|
},
|
||||||
|
listBadges() {
|
||||||
|
return api.get('/api/auth/badges');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function urlBase64ToUint8Array(base64String) {
|
||||||
|
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
||||||
|
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
const rawData = atob(base64);
|
||||||
|
return Uint8Array.from([...rawData].map((c) => c.charCodeAt(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCsrfToken() {
|
||||||
|
return document.cookie.match(/tek_csrf=([^;]+)/)?.[1] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notificationPreferencesApi = {
|
||||||
|
get() {
|
||||||
|
return api.get('/api/auth/notifications/preferences');
|
||||||
|
},
|
||||||
|
update(preferences) {
|
||||||
|
return api.put('/api/auth/notifications/preferences', preferences);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const revisionsApi = {
|
||||||
|
list(articleId, page = 1) {
|
||||||
|
return api.get(`/api/admin/articles/${encodeURIComponent(articleId)}/revisions?page=${page}`);
|
||||||
|
},
|
||||||
|
get(articleId, revisionId) {
|
||||||
|
return api.get(`/api/admin/articles/${encodeURIComponent(articleId)}/revisions/${encodeURIComponent(revisionId)}`);
|
||||||
|
},
|
||||||
|
restore(articleId, revisionId) {
|
||||||
|
return api.post(`/api/admin/articles/${encodeURIComponent(articleId)}/revisions/${encodeURIComponent(revisionId)}/restore`, {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const presenceApi = {
|
||||||
|
heartbeat(articleId) {
|
||||||
|
return api.post(`/api/admin/articles/${encodeURIComponent(articleId)}/heartbeat`, {});
|
||||||
|
},
|
||||||
|
getEditors(articleId) {
|
||||||
|
return api.get(`/api/admin/articles/${encodeURIComponent(articleId)}/presence`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pushApi = {
|
||||||
|
async getVapidKey() {
|
||||||
|
const r = await fetch('/api/v1/auth/push/vapid-key', { credentials: 'include' });
|
||||||
|
if (!r.ok) throw new Error('push_not_configured');
|
||||||
|
const { publicKey } = await r.json();
|
||||||
|
return publicKey;
|
||||||
|
},
|
||||||
|
async isSubscribed() {
|
||||||
|
if (!('PushManager' in window) || !('serviceWorker' in navigator)) return false;
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const sub = await reg.pushManager.getSubscription();
|
||||||
|
return !!sub;
|
||||||
|
},
|
||||||
|
async subscribe() {
|
||||||
|
const publicKey = await this.getVapidKey();
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const sub = await reg.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: urlBase64ToUint8Array(publicKey),
|
||||||
|
});
|
||||||
|
const json = sub.toJSON();
|
||||||
|
await fetch('/api/v1/auth/push/subscribe', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken() },
|
||||||
|
body: JSON.stringify({
|
||||||
|
endpoint: json.endpoint,
|
||||||
|
auth: json.keys.auth,
|
||||||
|
p256dh: json.keys.p256dh,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return sub;
|
||||||
|
},
|
||||||
|
async unsubscribe() {
|
||||||
|
if (!('serviceWorker' in navigator)) return;
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const sub = await reg.pushManager.getSubscription();
|
||||||
|
if (!sub) return;
|
||||||
|
await fetch('/api/v1/auth/push/unsubscribe', {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken() },
|
||||||
|
body: JSON.stringify({ endpoint: sub.endpoint }),
|
||||||
|
});
|
||||||
|
await sub.unsubscribe();
|
||||||
|
},
|
||||||
|
async getGuestVapidKey() {
|
||||||
|
const r = await fetch('/api/v1/push/vapid-key');
|
||||||
|
if (!r.ok) throw new Error('push_not_configured');
|
||||||
|
const { publicKey } = await r.json();
|
||||||
|
return publicKey;
|
||||||
|
},
|
||||||
|
async subscribeAsGuest(topics) {
|
||||||
|
const publicKey = await this.getGuestVapidKey();
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const sub = await reg.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: urlBase64ToUint8Array(publicKey),
|
||||||
|
});
|
||||||
|
const json = sub.toJSON();
|
||||||
|
const r = await fetch('/api/v1/push/guest-subscribe', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
endpoint: json.endpoint,
|
||||||
|
auth: json.keys.auth,
|
||||||
|
p256dh: json.keys.p256dh,
|
||||||
|
topics,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!r.ok) throw new Error('subscribe_failed');
|
||||||
|
return sub;
|
||||||
|
},
|
||||||
|
async unsubscribeGuest() {
|
||||||
|
if (!('serviceWorker' in navigator)) return;
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const sub = await reg.pushManager.getSubscription();
|
||||||
|
if (!sub) return;
|
||||||
|
await fetch('/api/v1/push/guest-unsubscribe', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ endpoint: sub.endpoint }),
|
||||||
|
});
|
||||||
|
await sub.unsubscribe();
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user