mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-12 01:38:16 +02:00
feat(epic-3): implémentation du Dashboard, mode lecture et notifications temps réel
This commit is contained in:
1 parent
1d31de95ec
commit
dc26f22f51
20 files changed
+1237
-107
No files matched your search
@@ -0,0 +1,109 @@
|
||||
import { API_BASE, handleResponse } from './client';
|
||||
|
||||
export class ApiError extends Error {
|
||||
public status: number;
|
||||
constructor(status: number, message: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
export interface Article {
|
||||
id: string;
|
||||
feed_id: string;
|
||||
guid: string;
|
||||
title: string;
|
||||
url?: string;
|
||||
content?: string;
|
||||
summary?: string;
|
||||
author?: string;
|
||||
image_url?: string;
|
||||
published_at?: string;
|
||||
is_read: boolean;
|
||||
is_favorite: boolean;
|
||||
read_at?: string;
|
||||
created_at: string;
|
||||
feed_title?: string;
|
||||
}
|
||||
|
||||
export interface ListArticlesOptions {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
unread?: boolean;
|
||||
favorite?: boolean;
|
||||
feed_id?: string;
|
||||
}
|
||||
|
||||
export const articlesApi = {
|
||||
async list(options: ListArticlesOptions = {}): Promise<Article[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (options.limit) params.append('limit', options.limit.toString());
|
||||
if (options.offset) params.append('offset', options.offset.toString());
|
||||
if (options.unread) params.append('unread', 'true');
|
||||
if (options.favorite) params.append('favorite', 'true');
|
||||
|
||||
let url = `${API_BASE}/articles`;
|
||||
if (options.favorite) {
|
||||
url = `${API_BASE}/articles/favorites`;
|
||||
} else if (options.feed_id) {
|
||||
url = `${API_BASE}/feeds/${options.feed_id}/articles`;
|
||||
}
|
||||
|
||||
if (params.toString()) {
|
||||
url += `?${params.toString()}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse<Article[]>(response);
|
||||
},
|
||||
|
||||
async get(id: string): Promise<Article> {
|
||||
const response = await fetch(`${API_BASE}/articles/${id}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse<Article>(response);
|
||||
},
|
||||
|
||||
async markRead(id: string): Promise<{ is_read: boolean }> {
|
||||
const response = await fetch(`${API_BASE}/articles/${id}/read`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse(response);
|
||||
},
|
||||
|
||||
async markUnread(id: string): Promise<{ is_read: boolean }> {
|
||||
const response = await fetch(`${API_BASE}/articles/${id}/read`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse(response);
|
||||
},
|
||||
|
||||
async toggleFavorite(id: string): Promise<{ is_favorite: boolean }> {
|
||||
const response = await fetch(`${API_BASE}/articles/${id}/favorite`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse(response);
|
||||
},
|
||||
|
||||
async markAllRead(feedId: string): Promise<{ message: string }> {
|
||||
const response = await fetch(`${API_BASE}/feeds/${feedId}/read-all`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse(response);
|
||||
},
|
||||
|
||||
async markAllReadGlobal(): Promise<{ message: string }> {
|
||||
const response = await fetch(`${API_BASE}/articles/read-all`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse(response);
|
||||
},
|
||||
};
|
||||
+14
-32
@@ -1,48 +1,33 @@
|
||||
const API_BASE = '/api/v1';
|
||||
import { API_BASE, handleResponse } from './client';
|
||||
|
||||
interface RegisterRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface User {
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
is_admin: boolean;
|
||||
}
|
||||
|
||||
interface LoginResponse {
|
||||
export interface RegisterRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
expires_at: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
interface RegisterResponse {
|
||||
export interface RegisterResponse {
|
||||
id: string;
|
||||
email: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(public status: number, message: string) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResponse<T>(response: Response): Promise<T> {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Request failed' }));
|
||||
throw new ApiError(response.status, error.error || 'Request failed');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export const authApi = {
|
||||
async register(data: RegisterRequest): Promise<RegisterResponse> {
|
||||
const response = await fetch(`${API_BASE}/auth/register`, {
|
||||
@@ -78,6 +63,3 @@ export const authApi = {
|
||||
return handleResponse<User>(response);
|
||||
},
|
||||
};
|
||||
|
||||
export { ApiError };
|
||||
export type { User, LoginResponse, RegisterResponse };
|
||||
@@ -0,0 +1,18 @@
|
||||
export const API_BASE = '/api/v1';
|
||||
|
||||
export class ApiError extends Error {
|
||||
public status: number;
|
||||
constructor(status: number, message: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleResponse<T>(response: Response): Promise<T> {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Request failed' }));
|
||||
throw new ApiError(response.status, error.error || 'Request failed');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { API_BASE, handleResponse } from './client';
|
||||
|
||||
export interface Feed {
|
||||
id: string;
|
||||
user_id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
site_url?: string;
|
||||
image_url?: string;
|
||||
last_fetched_at?: string;
|
||||
fetch_error?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
unread_count?: number;
|
||||
}
|
||||
|
||||
export interface AddFeedRequest {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const feedsApi = {
|
||||
async list(): Promise<Feed[]> {
|
||||
const response = await fetch(`${API_BASE}/feeds`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse<Feed[]>(response);
|
||||
},
|
||||
|
||||
async add(data: AddFeedRequest): Promise<Feed> {
|
||||
const response = await fetch(`${API_BASE}/feeds`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return handleResponse<Feed>(response);
|
||||
},
|
||||
|
||||
async get(id: string): Promise<Feed> {
|
||||
const response = await fetch(`${API_BASE}/feeds/${id}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
return handleResponse<Feed>(response);
|
||||
},
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/feeds/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
await handleResponse(response);
|
||||
},
|
||||
|
||||
async importOPML(file: File): Promise<{ imported: number; skipped: number; errors?: string[] }> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const response = await fetch(`${API_BASE}/feeds/import/opml`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: formData,
|
||||
});
|
||||
return handleResponse(response);
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user