PodStreamμ μ¬μ©μλ€μ΄ μ€λμ€μ λΉλμ€ νμΊμ€νΈλ₯Ό μ½κ² 곡μ νκ³ μμ²/μ²μ·¨ν μ μλ μ€νΈλ¦¬λ° νλ«νΌμ λλ€. Reactμ Node.jsλ₯Ό κΈ°λ°μΌλ‘ κ°λ°λμμΌλ©°, μ§κ΄μ μΈ UI/UXμ μμ μ μΈ μ€νΈλ¦¬λ° μλΉμ€λ₯Ό μ 곡ν©λλ€.
- JWT κΈ°λ° λ‘κ·ΈμΈ/νμκ°μ
- μΉ΄μΉ΄μ€ OAuth μμ λ‘κ·ΈμΈ
- μ΄λ©μΌ μΈμ¦ λ° λΉλ°λ²νΈ μ¬μ€μ
- 컀μ€ν μ€λμ€/λΉλμ€ νλ μ΄μ΄
- μ¬μ μ§νλ₯ μ€μκ° μΆμ
- λ³Όλ₯¨ μ‘°μ λ° μ¬μ μ μ΄
- μ΄μ /λ€μ μνΌμλ μ ν
- νμΊμ€νΈ μ λ‘λ λ° κ΄λ¦¬
- μνΌμλ μΆκ° λ° μμ
- μΉ΄ν κ³ λ¦¬λ³ μ½ν μΈ λΆλ₯
- μ¦κ²¨μ°ΎκΈ° κΈ°λ₯
- νκ΅μ΄/μμ΄ μ§μ
- μ¬μ©μ μμΉ κΈ°λ° μλ μΈμ΄ μ€μ
- React
- Redux Toolkit (μν κ΄λ¦¬)
- Styled-Components (μ€νμΌλ§)
- Material-UI (UI μ»΄ν¬λνΈ)
- i18next (λ€κ΅μ΄ μ²λ¦¬)
- Node.js
- Express.js
- MongoDB (λ°μ΄ν°λ² μ΄μ€)
- JWT (μΈμ¦)
- AWS S3 (νμΌ μ€ν 리μ§)
# μ μ₯μ ν΄λ‘
git clone [μ μ₯μ URL]
# λλ ν 리 μ΄λ
cd podstream/frontend
# μμ‘΄μ± μ€μΉ
npm install
# κ°λ° μλ² μ€ν
npm start# λλ ν 리 μ΄λ
cd podstream/backend
# μμ‘΄μ± μ€μΉ
npm install
# μλ² μ€ν
npm start# Backend .env
MONGODB_URL=your_mongodb_url
JWT_SECRET=your_jwt_secret
AWS_S3_BUCKET=your_bucket_name
AWS_ACCESS_KEY=your_access_key
AWS_SECRET_KEY=your_secret_key
EMAIL_SERVICE=your_email_service
EMAIL_USERNAME=your_email_username
EMAIL_PASSWORD=your_email_password
KAKAO_CLIENT_ID=your_kakao_client_id
# Frontend .env
REACT_APP_API_URL=your_api_url
REACT_APP_KAKAO_KEY=your_kakao_keypodstream/
βββ frontend/
β βββ src/
β β βββ components/
β β βββ pages/
β β βββ redux/
β β βββ utils/
β β βββ App.js
β βββ package.json
β
βββ backend/
βββ controllers/
βββ models/
βββ routes/
βββ middleware/
βββ server.js
POST /api/auth/signup- νμκ°μPOST /api/auth/signin- λ‘κ·ΈμΈPOST /api/auth/kakao- μΉ΄μΉ΄μ€ λ‘κ·ΈμΈGET /api/auth/generateotp- OTP μμ±GET /api/auth/verifyotp- OTP κ²μ¦
POST /api/podcasts- νμΊμ€νΈ μμ±GET /api/podcasts- νμΊμ€νΈ λͺ©λ‘ μ‘°νPOST /api/podcasts/episode- μνΌμλ μΆκ°POST /api/podcasts/favorit/:id- μ¦κ²¨μ°ΎκΈ° ν κΈGET /api/podcasts/search- νμΊμ€νΈ κ²μ
- JWT κΈ°λ° μΈμ¦
- λΉλ°λ²νΈ μνΈν
- μ΄λ©μΌ μΈμ¦
- OAuth 보μ
const AudioPlayer = ({ episode, currenttime }) => {
// μ¬μ μν κ΄λ¦¬
// μ§νλ₯ μΆμ
// λ³Όλ₯¨ μ μ΄
};const audioPlayerSlice = createSlice({
name: 'audioplayer',
initialState,
reducers: {
// νλ μ΄μ΄ μν κ΄λ¦¬
// μ¬μ μ μ΄
}
});// μΉ΄μΉ΄μ€ OAuth λ° μ΄λ©μΌ μΈμ¦ ꡬν
export const kakaoAuthSignIn = async (req, res, next) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user) {
// μ μ¬μ©μ μμ± λ‘μ§
const user = new User({ ...req.body, kakaoSignIn: true });
await user.save();
const token = jwt.sign({ id: user._id }, process.env.JWT);
res.status(200).json({ token, user: user });
}
// ... κΈ°μ‘΄ μ¬μ©μ μ²λ¦¬ λ‘μ§
} catch (err) {
next(err);
}
};// 컀μ€ν
μ€λμ€ νλ μ΄μ΄ ꡬν
const AudioPlayer = ({ episode, podid, currenttime, index }) => {
const [isPlaying, setIsPlaying] = useState(false);
const [volume, setVolume] = useState(1);
const audioRef = useRef(null);
const handleTimeUpdate = () => {
dispatch(
setCurrentTime({
currenttime: audioRef.current.currentTime,
})
);
};
// μ΄μ /λ€μ μνΌμλ μ²λ¦¬
const goToNextPodcast = () => {
if (podid.episodes.length === index + 1) {
dispatch(
openSnackbar({
message: 'This is the last episode',
severity: 'info',
})
);
return;
}
// ... λ€μ μνΌμλ μ¬μ λ‘μ§
};
};// μ΅μ νλ MongoDB μ€ν€λ§
const PodcastsSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
desc: String,
thumbnail: String,
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
tags: [String],
type: String,
category: String,
views: {
type: Number,
default: 0,
},
episodes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Episodes',
}]
});// Reduxλ₯Ό νμ©ν μν κ΄λ¦¬
const audioplayer = createSlice({
name: 'audioplayer',
initialState: {
openplayer: false,
type: 'audio',
episode: null,
podid: null,
currenttime: 0,
index: 0,
},
reducers: {
openPlayer: (state, action) => {
state.openplayer = true;
state.type = action.payload.type;
state.episode = action.payload.episode;
// ... κΈ°ν μν μ
λ°μ΄νΈ
},
},
});useEffect(() => {
fetch('https://ipapi.co/json/')
.then((response) => response.json())
.then((data) => {
const userCountry = data.country_code;
if (userCountry === 'KR') {
i18n.changeLanguage('ko');
} else {
i18n.changeLanguage('en');
}
})
.catch(() => i18n.changeLanguage('en'));
}, []);- μ¬μ©μ μΈμ¦ λ° κΆν κ΄λ¦¬ μμ€ν ꡬμΆ
- μμ μ μΈ λ―Έλμ΄ μ€νΈλ¦¬λ° μλΉμ€ ꡬν
- λ€κ΅μ΄ μ§μμΌλ‘ μλΉμ€ μ κ·Όμ± ν₯μ
- μ§κ΄μ μΈ μ¬μ©μ μΈν°νμ΄μ€ μ€κ³
- Reactμ Node.jsλ₯Ό νμ©ν νμ€ν κ°λ° κ²½ν
- JWTλ₯Ό νμ©ν μΈμ¦ μμ€ν ꡬν λ°©λ²
- λ―Έλμ΄ μ€νΈλ¦¬λ° μλΉμ€ κ°λ° λ Ένμ°
- MongoDBλ₯Ό νμ©ν λ°μ΄ν° λͺ¨λΈλ§
- μν κ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬ νμ© λ°©λ²
- κ²μ κΈ°λ₯ κ°ν
- μ¬μ©μ μνΈμμ© κΈ°λ₯ μΆκ°
- μ±λ₯ μ΅μ ν
- λͺ¨λ°μΌ λ°μμ± κ°μ
- Frontend: Vercel
- Backend: Render
- Database: MongoDB Atlas
- Storage: AWS S3
-
κΈ°μ μ κ°μ
- GraphQL λμ κ²ν
- μ€μκ° μ²λ¦¬ μμ€ν κ°ν
- λ¨Έμ λ¬λ κΈ°λ° μΆμ² μμ€ν ꡬν
-
μλ‘μ΄ κΈ°λ₯
- μ€μκ° μ±ν μμ€ν
- κ³ κΈ κ²μ κΈ°λ₯
- μμ κΈ°λ₯ νμ₯
μ΄ νλ‘μ νΈλ₯Ό ν΅ν΄ νλμ μΈ μΉ μ ν리μΌμ΄μ κ°λ°μ μ 체 λΌμ΄νμ¬μ΄ν΄μ κ²½ννλ©°, νΉν μ±λ₯ μ΅μ νμ μ¬μ©μ κ²½ν κ°μ μ μ€μ μ λμ΄ μ€μ μλΉμ€ μμ€μ μ ν리μΌμ΄μ μ ꡬννμ΅λλ€. μ§μμ μΈ κ°μ κ³Ό μλ‘μ΄ κΈ°μ λμ μ ν΅ν΄ λ λμ μλΉμ€λ₯Ό μ 곡νκΈ° μν΄ λ Έλ ₯νκ³ μμ΅λλ€.
- νλ‘μ νΈ ν¬ν¬
- κΈ°λ₯ κ°λ°μ© λΈλμΉ μμ±
- λ³κ²½ μ¬ν 컀λ°
- ν 리νμ€νΈ μμ²
MIT License
- Email: whd793@gmail.com
- GitHub: https://github.com/whd793
PodStream is a full-stack podcast streaming platform that allows users to discover, listen to, and manage their favorite podcasts. Built with modern web technologies, it offers a seamless experience for both audio and video podcast content.
- π Secure authentication (Email/Password & Kakao OAuth)
- π± Responsive design for all devices
- π§ Audio & Video podcast streaming
- β€οΈ Favorite podcasts functionality
- π Advanced search and filtering
- π·οΈ Category-based browsing
- π View count tracking
- π Multi-language support (English & Korean)
- π€ Upload audio/video podcasts
- π Episode management
- πΈ Thumbnail uploads
- π·οΈ Add tags and categories
- π View statistics
- π Real-time progress tracking
- ποΈ Volume control
- β¨οΈ Keyboard controls for playback
- π Toast notifications
- π JWT authentication
- π§ Email verification system
- π Dark/Light theme support
- React.js - UI Library
- Redux Toolkit - State Management
- Styled Components - Styling
- Material UI - UI Components
- i18next - Internationalization
- React Router - Navigation
- Axios - HTTP Client
- Node.js & Express.js - Server Framework
- MongoDB & Mongoose - Database
- JWT - Authentication
- Bcrypt - Password Hashing
- Nodemailer - Email Service
- AWS S3 - File Storage
{
name: String,
email: String,
password: String,
img: String,
kakaoSignIn: Boolean,
podcasts: [PodcastId],
favorits: [PodcastId]
}{
name: String,
desc: String,
thumbnail: String,
creator: UserId,
tags: [String],
type: String,
category: String,
views: Number,
episodes: [EpisodeId]
}{
name: String,
desc: String,
thumbnail: String,
creator: UserId,
type: String,
duration: String,
file: String
}POST /auth/signup- User registrationPOST /auth/signin- User loginPOST /auth/kakao- Kakao OAuthGET /auth/generateotp- Generate OTP for verificationGET /auth/verifyotp- Verify OTP
GET /podcasts- Get all podcastsGET /podcasts/get/:id- Get specific podcastPOST /podcasts- Create podcastPOST /podcasts/episode- Add episodePOST /podcasts/favorit/:id- Toggle favoriteGET /podcasts/search- Search podcastsGET /podcasts/category- Get by category
const AudioPlayer = ({ episode, podid, currenttime, index }) => {
// Custom player with progress tracking and volume control
// Keyboard shortcuts for playback control
// Automatic progress saving
}const signin = async (req, res, next) => {
// Email/Password validation
// JWT token generation
// Session management
}const uploadFile = async (file) => {
// AWS S3 integration
// Progress tracking
// File validation
}- Clone the repository
git clone https://github.com/yourusername/podstream.git- Install dependencies
cd podstream
npm install- Environment Variables
MONGODB_URL=your_mongodb_url
JWT_SECRET=your_jwt_secret
AWS_S3_BUCKET=your_s3_bucket
EMAIL_SERVICE_CREDENTIALS=your_email_credentials- Run the application
npm run dev # Development
npm start # ProductionContributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ by Won Lee






