diff --git a/backend/package-lock.json b/backend/package-lock.json index 440cd9d..7250187 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,17 +1,21 @@ { - "name": "backend", + "name": "iptv-player-backend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "backend", + "name": "iptv-player-backend", "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/axios": "^0.9.36", "@types/express": "^5.0.0", "@types/node": "^22.10.2", + "axios": "^1.7.9", "bcryptjs": "^2.4.3", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", @@ -131,6 +135,12 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "license": "MIT" }, + "node_modules/@types/axios": { + "version": "0.9.36", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.9.36.tgz", + "integrity": "sha512-NLOpedx9o+rxo/X5ChbdiX6mS1atE4WHmEEIcR9NLenRVa5HoVjAvjafwU3FPTqnZEstpoqCaW7fagqSoTDNeg==", + "license": "MIT" + }, "node_modules/@types/bcryptjs": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", @@ -260,6 +270,12 @@ "@types/send": "*" } }, + "node_modules/@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -365,6 +381,23 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -574,6 +607,23 @@ "fsevents": "~2.3.2" } }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/cli-highlight": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", @@ -781,6 +831,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -872,6 +934,15 @@ "ms": "2.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1155,6 +1226,26 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -1171,6 +1262,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1537,6 +1642,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.17", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.17.tgz", + "integrity": "sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2090,6 +2201,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -2936,6 +3053,15 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index 713ec47..50adf87 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,13 +10,21 @@ "typeorm": "typeorm-ts-node-commonjs", "test": "echo \"Error: no test specified\" && exit 1" }, - "keywords": ["iptv", "streaming", "video"], + "keywords": [ + "iptv", + "streaming", + "video" + ], "author": "", "license": "ISC", "dependencies": { + "@types/axios": "^0.9.36", "@types/express": "^5.0.0", "@types/node": "^22.10.2", + "axios": "^1.7.9", "bcryptjs": "^2.4.3", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", diff --git a/backend/src/controllers/ContentController.ts b/backend/src/controllers/ContentController.ts index 22003c8..35299b5 100644 --- a/backend/src/controllers/ContentController.ts +++ b/backend/src/controllers/ContentController.ts @@ -8,62 +8,64 @@ export class ContentController { private favoriteRepository = AppDataSource.getRepository(Favorite); private watchHistoryRepository = AppDataSource.getRepository(WatchHistory); - async getLiveStreams(req: Request, res: Response) { + async getLiveStreams(req: Request, res: Response): Promise { try { const xtreamService = new XtreamService(req.user!); - const streams = await xtreamService.getLiveStreams(); + const response = await xtreamService.getLiveStreams(); + const streams = response as any[]; - if (!req.user!.adult_content_enabled) { - // Filter out adult content - streams = streams.filter((stream: any) => - !stream.category_name?.toLowerCase().includes('adult') && - !stream.name?.toLowerCase().includes('adult') - ); - } - - res.json(streams); + const filteredStreams = !req.user!.adult_content_enabled + ? streams.filter(stream => + !stream.category_name?.toLowerCase().includes('adult') && + !stream.name?.toLowerCase().includes('adult') + ) + : streams; + + res.json(filteredStreams); } catch (error) { res.status(500).json({ error: 'Error fetching live streams' }); } } - async getVodStreams(req: Request, res: Response) { + async getVodStreams(req: Request, res: Response): Promise { try { const xtreamService = new XtreamService(req.user!); - const streams = await xtreamService.getVodStreams(); + const response = await xtreamService.getVodStreams(); + const streams = response as any[]; - if (!req.user!.adult_content_enabled) { - streams = streams.filter((stream: any) => - !stream.category_name?.toLowerCase().includes('adult') && - !stream.name?.toLowerCase().includes('adult') - ); - } - - res.json(streams); + const filteredStreams = !req.user!.adult_content_enabled + ? streams.filter(stream => + !stream.category_name?.toLowerCase().includes('adult') && + !stream.name?.toLowerCase().includes('adult') + ) + : streams; + + res.json(filteredStreams); } catch (error) { res.status(500).json({ error: 'Error fetching VOD streams' }); } } - async getSeriesStreams(req: Request, res: Response) { + async getSeriesStreams(req: Request, res: Response): Promise { try { const xtreamService = new XtreamService(req.user!); - const series = await xtreamService.getSeriesStreams(); + const response = await xtreamService.getSeriesStreams(); + const series = response as any[]; - if (!req.user!.adult_content_enabled) { - series = series.filter((show: any) => - !show.category_name?.toLowerCase().includes('adult') && - !show.name?.toLowerCase().includes('adult') - ); - } - - res.json(series); + const filteredSeries = !req.user!.adult_content_enabled + ? series.filter(show => + !show.category_name?.toLowerCase().includes('adult') && + !show.name?.toLowerCase().includes('adult') + ) + : series; + + res.json(filteredSeries); } catch (error) { res.status(500).json({ error: 'Error fetching series' }); } } - async getSeriesInfo(req: Request, res: Response) { + async getSeriesInfo(req: Request, res: Response): Promise { try { const { seriesId } = req.params; const xtreamService = new XtreamService(req.user!); @@ -74,7 +76,7 @@ export class ContentController { } } - async getEPG(req: Request, res: Response) { + async getEPG(req: Request, res: Response): Promise { try { const { streamId } = req.params; const xtreamService = new XtreamService(req.user!); @@ -85,7 +87,7 @@ export class ContentController { } } - async addToFavorites(req: Request, res: Response) { + async addToFavorites(req: Request, res: Response): Promise { try { const { content_id, content_type, title, poster_url } = req.body; @@ -98,7 +100,8 @@ export class ContentController { }); if (existingFavorite) { - return res.status(400).json({ error: 'Already in favorites' }); + res.status(400).json({ error: 'Already in favorites' }); + return; } const favorite = this.favoriteRepository.create({ @@ -116,7 +119,7 @@ export class ContentController { } } - async getFavorites(req: Request, res: Response) { + async getFavorites(req: Request, res: Response): Promise { try { const favorites = await this.favoriteRepository.find({ where: { user: { id: req.user!.id } }, @@ -128,7 +131,7 @@ export class ContentController { } } - async removeFromFavorites(req: Request, res: Response) { + async removeFromFavorites(req: Request, res: Response): Promise { try { const { id } = req.params; await this.favoriteRepository.delete({ @@ -141,7 +144,7 @@ export class ContentController { } } - async updateWatchHistory(req: Request, res: Response) { + async updateWatchHistory(req: Request, res: Response): Promise { try { const { content_id, @@ -186,7 +189,7 @@ export class ContentController { } } - async getWatchHistory(req: Request, res: Response) { + async getWatchHistory(req: Request, res: Response): Promise { try { const watchHistory = await this.watchHistoryRepository.find({ where: { user: { id: req.user!.id } }, diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index f364bda..750798e 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -7,13 +7,14 @@ import jwt from 'jsonwebtoken'; export class UserController { private userRepository = AppDataSource.getRepository(User); - async register(req: Request, res: Response) { + async register(req: Request, res: Response): Promise { try { const { email, password, username } = req.body; const existingUser = await this.userRepository.findOne({ where: { email } }); if (existingUser) { - return res.status(400).json({ error: 'Email already in use' }); + res.status(400).json({ error: 'Email already in use' }); + return; } const hashedPassword = await bcrypt.hash(password, 10); @@ -35,18 +36,20 @@ export class UserController { } } - async login(req: Request, res: Response) { + async login(req: Request, res: Response): Promise { try { const { email, password } = req.body; const user = await this.userRepository.findOne({ where: { email } }); if (!user) { - return res.status(401).json({ error: 'Invalid credentials' }); + res.status(401).json({ error: 'Invalid credentials' }); + return; } const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { - return res.status(401).json({ error: 'Invalid credentials' }); + res.status(401).json({ error: 'Invalid credentials' }); + return; } const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET!, { diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts index 25f1c82..445b37b 100644 --- a/backend/src/middleware/auth.ts +++ b/backend/src/middleware/auth.ts @@ -38,11 +38,12 @@ export const auth = async (req: Request, res: Response, next: NextFunction) => { } }; -export const adminAuth = async (req: Request, res: Response, next: NextFunction) => { +export const adminAuth = async (req: Request, res: Response, next: NextFunction): Promise => { try { await auth(req, res, () => { if (!req.user?.is_admin) { - return res.status(403).json({ error: 'Admin access required.' }); + res.status(403).json({ error: 'Admin access required.' }); + return; } next(); }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d9c5ad5..3c04a18 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,13 +1,12 @@ { - "name": "frontend", + "name": "iptv-player-frontend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "frontend", + "name": "iptv-player-frontend", "version": "1.0.0", - "license": "ISC", "dependencies": { "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", @@ -18,6 +17,7 @@ "@types/react-dom": "^18.2.0", "@types/video.js": "^7.3.52", "axios": "^1.4.0", + "date-fns": "^4.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.1", @@ -1582,6 +1582,16 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5623b40..f24ce93 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,6 @@ "preview": "vite preview", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0" }, - "dependencies": { "dependencies": { "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", @@ -20,6 +19,7 @@ "@types/react-dom": "^18.2.0", "@types/video.js": "^7.3.52", "axios": "^1.4.0", + "date-fns": "^4.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.1", diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1fa14f1..5c62ea7 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -10,10 +10,10 @@ export default defineConfig({ }, }, server: { - port: 3000, + port: 5173, proxy: { '/api': { - target: 'http://localhost:4000', + target: 'http://localhost:5000', changeOrigin: true, }, },