mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-19 13:08:03 +02:00
feat: implement authentication and core API endpoints - auth, podcasts, episodes
This commit is contained in:
1 parent
8df1161023
commit
8c00a1b0f5
13 files changed
+822
-1
No files matched your search
@@ -0,0 +1,35 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Add auth token to requests
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Handle token refresh on 401
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token expired - could implement refresh token logic here
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default api;
|
||||
@@ -0,0 +1,66 @@
|
||||
import api from './api';
|
||||
|
||||
export interface User {
|
||||
_id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
avatar?: string;
|
||||
preferences: {
|
||||
defaultPlaybackSpeed: number;
|
||||
theme: 'light' | 'dark';
|
||||
language: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
message: string;
|
||||
user: User;
|
||||
tokens: {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const authService = {
|
||||
register: async (email: string, password: string, username: string): Promise<AuthResponse> => {
|
||||
const response = await api.post('/auth/register', {
|
||||
email,
|
||||
password,
|
||||
username,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
login: async (email: string, password: string): Promise<AuthResponse> => {
|
||||
const response = await api.post('/auth/login', {
|
||||
email,
|
||||
password,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getMe: async (): Promise<{ user: User }> => {
|
||||
const response = await api.get('/auth/me');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
logout: async (): Promise<{ message: string }> => {
|
||||
const response = await api.post('/auth/logout');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
setTokens: (accessToken: string, refreshToken: string) => {
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
localStorage.setItem('refreshToken', refreshToken);
|
||||
},
|
||||
|
||||
getTokens: () => ({
|
||||
accessToken: localStorage.getItem('accessToken'),
|
||||
refreshToken: localStorage.getItem('refreshToken'),
|
||||
}),
|
||||
|
||||
clearTokens: () => {
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import api from './api';
|
||||
|
||||
export interface Episode {
|
||||
_id: string;
|
||||
podcastId: string | { _id: string; title: string; author: string; imageUrl?: string };
|
||||
title: string;
|
||||
description: string;
|
||||
audioUrl: string;
|
||||
pubDate: string;
|
||||
duration: number;
|
||||
guid: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export interface EpisodeResponse {
|
||||
episodes: Episode[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface UserProgress {
|
||||
_id: string;
|
||||
userId: string;
|
||||
episodeId: string;
|
||||
position: number;
|
||||
isCompleted: boolean;
|
||||
isSaved: boolean;
|
||||
lastListenedAt: string;
|
||||
}
|
||||
|
||||
export const episodeService = {
|
||||
getLatestEpisodes: async (
|
||||
limit: number = 30,
|
||||
skip: number = 0
|
||||
): Promise<EpisodeResponse> => {
|
||||
const response = await api.get('/episodes/latest', {
|
||||
params: { limit, skip },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getPodcastEpisodes: async (
|
||||
podcastId: string,
|
||||
limit: number = 50,
|
||||
skip: number = 0
|
||||
): Promise<EpisodeResponse> => {
|
||||
const response = await api.get(`/episodes/podcast/${podcastId}`, {
|
||||
params: { limit, skip },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getEpisode: async (episodeId: string): Promise<{ episode: Episode }> => {
|
||||
const response = await api.get(`/episodes/${episodeId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
saveProgress: async (
|
||||
episodeId: string,
|
||||
position: number,
|
||||
isCompleted?: boolean
|
||||
): Promise<{ message: string; progress: UserProgress }> => {
|
||||
const response = await api.post('/episodes/progress', {
|
||||
episodeId,
|
||||
position,
|
||||
isCompleted,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getProgress: async (episodeId: string): Promise<{ progress: UserProgress | null }> => {
|
||||
const response = await api.get(`/episodes/progress/${episodeId}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import api from './api';
|
||||
|
||||
export interface Podcast {
|
||||
_id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
rssUrl: string;
|
||||
imageUrl?: string;
|
||||
author: string;
|
||||
category: string[];
|
||||
language: string;
|
||||
episodeCount: number;
|
||||
lastFetched: string;
|
||||
}
|
||||
|
||||
export interface PodcastResponse {
|
||||
podcasts: Podcast[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export const podcastService = {
|
||||
getUserSubscriptions: async (): Promise<PodcastResponse> => {
|
||||
const response = await api.get('/podcasts/subscriptions');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
subscribe: async (rssUrl: string): Promise<{ message: string; podcast: Podcast }> => {
|
||||
const response = await api.post('/podcasts/subscribe', { rssUrl });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
unsubscribe: async (podcastId: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete(`/podcasts/${podcastId}/unsubscribe`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
searchPodcasts: async (
|
||||
query: string,
|
||||
limit: number = 20,
|
||||
skip: number = 0
|
||||
): Promise<PodcastResponse> => {
|
||||
const response = await api.get('/podcasts/search', {
|
||||
params: { q: query, limit, skip },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getPodcast: async (podcastId: string): Promise<{ podcast: Podcast }> => {
|
||||
const response = await api.get(`/podcasts/${podcastId}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user