feat: implement authentication and core API endpoints - auth, podcasts, episodes

This commit is contained in:
Michael committed 2026-04-16 09:55:15 +02:00
1 parent 8df1161023
commit 8c00a1b0f5
13 files changed
+822 -1

No files matched your search

+35
View File
@@ -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;
+66
View File
@@ -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');
},
};
+74
View File
@@ -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;
},
};
+52
View File
@@ -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;
},
};