Саске Учиха 2 years ago
parent f6c27f0da4
commit d185fd38cd

@ -22,3 +22,6 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Sentry Config File
.env.sentry-build-plugin

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title> <title>808</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

@ -13,9 +13,12 @@
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.7.1/react-icons-all-files-4.7.1.tgz", "@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.10.1/react-icons-all-files-4.10.1.tgz",
"@sentry/react": "^7.80.1",
"@sentry/vite-plugin": "^2.10.0",
"@tanstack/react-query": "^5.7.2", "@tanstack/react-query": "^5.7.2",
"@tanstack/react-query-devtools": "^5.7.2", "@tanstack/react-query-devtools": "^5.7.2",
"@tanstack/react-table": "^8.10.7", "@tanstack/react-table": "^8.10.7",
@ -1398,6 +1401,30 @@
} }
} }
}, },
"node_modules/@radix-ui/react-progress": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.0.3.tgz",
"integrity": "sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": { "node_modules/@radix-ui/react-slot": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
@ -1588,9 +1615,9 @@
} }
}, },
"node_modules/@react-icons/all-files": { "node_modules/@react-icons/all-files": {
"version": "4.7.1", "version": "4.10.1",
"resolved": "https://github.com/react-icons/react-icons/releases/download/v4.7.1/react-icons-all-files-4.7.1.tgz", "resolved": "https://github.com/react-icons/react-icons/releases/download/v4.10.1/react-icons-all-files-4.10.1.tgz",
"integrity": "sha512-YnHGdOz7tWUIgDHDkNBU1rz7JfoH5RDySHY1KMQj2JHKRAVlj2vtgOKuWPV79kAb4I9Nyb+A6s17yrUr2/NNDw==", "integrity": "sha512-jm/9akpfUQRYke8YArGwnoTTTcsCNFjzXfC9rUwrtnEDrV3EOXYOF+R5sFrIgHPn+DMp5nEWLlPV4fmnLCEpMQ==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"react": "*" "react": "*"
@ -1604,6 +1631,199 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@sentry-internal/tracing": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.80.1.tgz",
"integrity": "sha512-5gZ4LPIj2vpQl2/dHBM4uXMi9OI5E0VlOhJQt0foiuN6JJeiOjdpJFcfVqJk69wrc0deVENTtgKKktxqMwVeWQ==",
"dependencies": {
"@sentry/core": "7.80.1",
"@sentry/types": "7.80.1",
"@sentry/utils": "7.80.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.80.1.tgz",
"integrity": "sha512-1dPR6vPJ9vOTzgXff9HGheb178XeEv5hyjBNhCO1f6rjCgnVj99XGNZIgO1Ee1ALJbqlfPWaeV+uSWbbcmgJMA==",
"dependencies": {
"@sentry-internal/tracing": "7.80.1",
"@sentry/core": "7.80.1",
"@sentry/replay": "7.80.1",
"@sentry/types": "7.80.1",
"@sentry/utils": "7.80.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/bundler-plugin-core": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.10.0.tgz",
"integrity": "sha512-bjcYvMrCQgqTiRRkWGNpeZ7EJOu0JQFYj+rrcQpwaVO+ll1eWNGW1nNveU+8vpnXrlaIAvieBxCrcCj5V9y1+Q==",
"dependencies": {
"@sentry/cli": "^2.21.2",
"@sentry/node": "^7.60.0",
"@sentry/utils": "^7.60.0",
"dotenv": "^16.3.1",
"find-up": "5.0.0",
"glob": "9.3.2",
"magic-string": "0.27.0",
"unplugin": "1.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/glob": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
"integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
"dependencies": {
"fs.realpath": "^1.0.0",
"minimatch": "^7.4.1",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/minimatch": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
"integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@sentry/cli": {
"version": "2.21.4",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.21.4.tgz",
"integrity": "sha512-KIgvgl1DB/i41GmXfJkv96TdtKeJIhiV6l5OLRmxtnvA2JTqAQaeH+YMHE+vpZ/0FqtLK5clIkt5ReQNpmigPg==",
"hasInstallScript": true,
"dependencies": {
"https-proxy-agent": "^5.0.0",
"node-fetch": "^2.6.7",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0",
"which": "^2.0.2"
},
"bin": {
"sentry-cli": "bin/sentry-cli"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/@sentry/core": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.80.1.tgz",
"integrity": "sha512-3Yh+O9Q86MxwIuJFYtuSSoUCpdx99P1xDAqL0FIPTJ+ekaVMiUJq9NmyaNh9uN2myPSmxvEXW6q3z37zta9ZHg==",
"dependencies": {
"@sentry/types": "7.80.1",
"@sentry/utils": "7.80.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.80.1.tgz",
"integrity": "sha512-0NWfcZMlyQphKWsvyzfhGm2dCBk5DUPqOGW/vGx18G4tCCYtFcAIj/mCp/4XOEcZRPQgb9vkm+sidGD6DnwWlA==",
"dependencies": {
"@sentry-internal/tracing": "7.80.1",
"@sentry/core": "7.80.1",
"@sentry/types": "7.80.1",
"@sentry/utils": "7.80.1",
"https-proxy-agent": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/react": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.80.1.tgz",
"integrity": "sha512-AZjROgfJsYmI/Htb+giRQuVTCNofsLKGz6nYmJS2cYDZYKP4KU1l1SapF5F8r5Pu7c/6ZvULNj7MeHOXq2SEYA==",
"dependencies": {
"@sentry/browser": "7.80.1",
"@sentry/types": "7.80.1",
"@sentry/utils": "7.80.1",
"hoist-non-react-statics": "^3.3.2"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": "15.x || 16.x || 17.x || 18.x"
}
},
"node_modules/@sentry/replay": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.80.1.tgz",
"integrity": "sha512-yjpftIyybQeWD2i0Nd7C96tZwjNbSMRW515EL9jwlNxYbQtGtMs0HavP9Y7uQvQrzwSHY0Wp+ooe9PMuvzqbHw==",
"dependencies": {
"@sentry-internal/tracing": "7.80.1",
"@sentry/core": "7.80.1",
"@sentry/types": "7.80.1",
"@sentry/utils": "7.80.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry/types": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.80.1.tgz",
"integrity": "sha512-CVu4uPVTOI3U9kYiOdA085R7jX5H1oVODbs9y+A8opJ0dtJTMueCXgZyE8oXQ0NjGVs6HEeaLkOuiV0mj8X3yw==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.80.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.80.1.tgz",
"integrity": "sha512-bfFm2e/nEn+b9++QwjNEYCbS7EqmteT8uf0XUs7PljusSimIqqxDtK1pfD9zjynPgC8kW/fVBKv0pe2LufomeA==",
"dependencies": {
"@sentry/types": "7.80.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/vite-plugin": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-2.10.0.tgz",
"integrity": "sha512-jOJQnu3ox+HcD1HCul5LBy6YEhS0oVFjrFshoFD2PtHI5sMldbwNWrkkTXcknNy/0o4d5cAtdsEuVzEnbdd+Gg==",
"dependencies": {
"@sentry/bundler-plugin-core": "2.10.0",
"unplugin": "1.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@tanstack/query-core": { "node_modules/@tanstack/query-core": {
"version": "5.7.2", "version": "5.7.2",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.7.2.tgz", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.7.2.tgz",
@ -2022,7 +2242,6 @@
"version": "8.11.2", "version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -2039,6 +2258,17 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@ -2694,7 +2924,6 @@
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@ -2768,6 +2997,17 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dotenv": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.576", "version": "1.4.576",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz",
@ -3168,7 +3408,6 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"dependencies": { "dependencies": {
"locate-path": "^6.0.0", "locate-path": "^6.0.0",
"path-exists": "^4.0.0" "path-exists": "^4.0.0"
@ -3374,6 +3613,26 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.2.4", "version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@ -3496,8 +3755,7 @@
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
"dev": true
}, },
"node_modules/jiti": { "node_modules/jiti": {
"version": "1.21.0", "version": "1.21.0",
@ -3626,7 +3884,6 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"dependencies": { "dependencies": {
"p-locate": "^5.0.0" "p-locate": "^5.0.0"
}, },
@ -3676,6 +3933,17 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/magic-string": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
"integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.13"
},
"engines": {
"node": ">=12"
}
},
"node_modules/match-sorter": { "node_modules/match-sorter": {
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
@ -3740,11 +4008,18 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minipass": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
"integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"dev": true
}, },
"node_modules/mz": { "node_modules/mz": {
"version": "2.7.0", "version": "2.7.0",
@ -3779,6 +4054,25 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.13", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
@ -3847,7 +4141,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"dependencies": { "dependencies": {
"yocto-queue": "^0.1.0" "yocto-queue": "^0.1.0"
}, },
@ -3862,7 +4155,6 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"dependencies": { "dependencies": {
"p-limit": "^3.0.2" "p-limit": "^3.0.2"
}, },
@ -3889,7 +4181,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -3916,6 +4207,40 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
}, },
"node_modules/path-scurry": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.2.tgz",
"integrity": "sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/path-scurry/node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/path-type": { "node_modules/path-type": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@ -4090,6 +4415,14 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/prop-types": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@ -4430,7 +4763,6 @@
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
}, },
@ -4445,7 +4777,6 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": { "dependencies": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
}, },
@ -4456,8 +4787,7 @@
"node_modules/semver/node_modules/yallist": { "node_modules/semver/node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
"dev": true
}, },
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
@ -4690,6 +5020,11 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@ -4755,6 +5090,17 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true "dev": true
}, },
"node_modules/unplugin": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.0.1.tgz",
"integrity": "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==",
"dependencies": {
"acorn": "^8.8.1",
"chokidar": "^3.5.3",
"webpack-sources": "^3.2.3",
"webpack-virtual-modules": "^0.5.0"
}
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@ -4895,11 +5241,37 @@
} }
} }
}, },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack-virtual-modules": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz",
"integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
}, },
@ -4933,7 +5305,6 @@
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },

@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview" "preview": "vite preview"
}, },
@ -15,9 +15,12 @@
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.7.1/react-icons-all-files-4.7.1.tgz", "@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.10.1/react-icons-all-files-4.10.1.tgz",
"@sentry/react": "^7.80.1",
"@sentry/vite-plugin": "^2.10.0",
"@tanstack/react-query": "^5.7.2", "@tanstack/react-query": "^5.7.2",
"@tanstack/react-query-devtools": "^5.7.2", "@tanstack/react-query-devtools": "^5.7.2",
"@tanstack/react-table": "^8.10.7", "@tanstack/react-table": "^8.10.7",

@ -1,6 +1,7 @@
import { useRef, useState, useEffect } from 'react' import { useRef, useState } from 'react'
import ReactPlayer from 'react-player' import ReactPlayer from 'react-player'
import { Disc3 } from 'lucide-react' import { Disc3 } from 'lucide-react'
import { IoPlayCircleOutline } from "@react-icons/all-files/io5/IoPlayCircleOutline";
import {ImSoundcloud} from '@react-icons/all-files/im/ImSoundcloud' import {ImSoundcloud} from '@react-icons/all-files/im/ImSoundcloud'
import {BsSpotify} from '@react-icons/all-files/bs/BsSpotify' import {BsSpotify} from '@react-icons/all-files/bs/BsSpotify'
import {BsFacebook} from '@react-icons/all-files/bs/BsFacebook' import {BsFacebook} from '@react-icons/all-files/bs/BsFacebook'
@ -24,6 +25,13 @@ function App() {
const [playing, setPlaying] = useState<boolean>(false); const [playing, setPlaying] = useState<boolean>(false);
const { data, refetch } = useQuery({ queryKey: ['video '], queryFn: () => VideoService.GetOne()}) const { data, refetch } = useQuery({ queryKey: ['video '], queryFn: () => VideoService.GetOne()})
const [start, setStart] = useState<boolean>(true); const [start, setStart] = useState<boolean>(true);
const [videoQuality, setVideoQuality] = useState<boolean>(false);
const changeVideoQuality = () => {
const time = progress;
setVideoQuality(!videoQuality);
player.current?.seekTo(time);
}
return ( return (
<div className='bg-black flex justify-center h-screen shadow-inner'> <div className='bg-black flex justify-center h-screen shadow-inner'>
@ -31,9 +39,9 @@ function App() {
<h1 className='absolute text-8xl top-4 left-4 drop-shadow-md font-bold text-white text-opacity-50'>808</h1> <h1 className='absolute text-8xl top-4 left-4 drop-shadow-md font-bold text-white text-opacity-50'>808</h1>
<div className="w-full h-full absolute shadow-video"></div> <div className="w-full h-full absolute shadow-video"></div>
{start && {start &&
<button onClick={()=> {setStart(false); setPlaying(true)}} className="absolute z-10 bg-yellow-500 shadow-xl p-4 px-8 text-3xl rounded-lg text-black font-bold left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">Начать</button> <button onClick={()=> {setStart(false); setPlaying(true)}} className="absolute text-white z-10 p-4 px-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 drop-shadow-2xl "><IoPlayCircleOutline size={200}/></button>
} }
<ReactPlayer onEnded={()=> {refetch(); player.current?.seekTo(0) }} volume={volume ? 1 : 0} playing={playing} ref={player} onProgress={(e) => {setProgress(e.played); setProgressSec(e.playedSeconds)}} className='h-screen w-full m-auto' width='auto' height='100%' url={data?.data.video.videoHQ}/> <ReactPlayer onStart={()=> player.current?.seekTo(progress)} onEnded={()=> {refetch(); setProgress(0); player.current?.seekTo(0) }} volume={volume ? 1 : 0} playing={playing} ref={player} onProgress={(e) => {setProgress(e.played); setProgressSec(e.playedSeconds)}} className='h-screen w-full m-auto' width='auto' height='100%' url={videoQuality ? data?.data.video.videoLQ : data?.data.video.videoHQ}/>
<div className="absolute bottom-0 flex flex-col w-full overflow-hidden py-2"> <div className="absolute bottom-0 flex flex-col w-full overflow-hidden py-2">
<div ref={progressBar} onClick={(e)=> {player.current.seekTo(progressBar.current ? e.clientX / progressBar.current?.clientWidth : 0); setProgress(progressBar.current ? e.clientX / progressBar.current?.clientWidth : 0)}} className="bg-slate-500 h-2"> <div ref={progressBar} onClick={(e)=> {player.current.seekTo(progressBar.current ? e.clientX / progressBar.current?.clientWidth : 0); setProgress(progressBar.current ? e.clientX / progressBar.current?.clientWidth : 0)}} className="bg-slate-500 h-2">
<div className="bg-yellow-500 h-2 relative" style={{width: progress*100 + '%'}}> <div className="bg-yellow-500 h-2 relative" style={{width: progress*100 + '%'}}>
@ -44,28 +52,27 @@ function App() {
<div className="flex items-center"> <div className="flex items-center">
<div className="border-2 border-white p-2 rounded-full mr-5"><Disc3 strokeWidth={1.5} className='text-purple-500 w-8 h-auto'/></div> <div className="border-2 border-white p-2 rounded-full mr-5"><Disc3 strokeWidth={1.5} className='text-purple-500 w-8 h-auto'/></div>
<div className="flex flex-col mr-10"> <div className="flex flex-col mr-10">
<p className='text text-white'>Исполнитель</p> <p className='text text-white'>{data?.data.video.artist[0]?.name}</p>
<p className='text-lg font-medium text-white'>Название трека</p> <p className='text-lg font-medium text-white'>{data?.data.video.playlist[0]?.name}</p>
</div> </div>
<div className="flex"> <div className="flex">
{data?.data.video.artist[0]?.soundcloud && {data?.data.video.artist[0]?.soundcloud &&
<a className='border-2 border-slate-300 text-white hover:border-yellow-500 transition-all hover:text-yellow-500 mr-3 rounded-full text-2xl p-2' href={data?.data.video.artist[0]?.soundcloud} target='_blank'><ImSoundcloud /></a> <a className='border-2 border-slate-300 text-white hover:border-yellow-500 transition-all hover:text-yellow-500 mr-3 rounded-full text-2xl p-2' href={data?.data.video.artist[0]?.soundcloud} target='_blank'><ImSoundcloud /></a>
} }
{data?.data.video.artist[0]?.spotify && {data?.data.video.artist[0]?.spotify &&
<a className='border-2 border-slate-300 text-white hover:border-yellow-500 transition-all hover:text-yellow-500 mr-3 rounded-full text-2xl p-2' href={data?.data.video.artist[0]?.spotify} target='_blank'><BsSpotify /></a> <a className='border-2 border-slate-300 text-white hover:border-yellow-500 transition-all hover:text-yellow-500 mr-3 rounded-full text-2xl p-2' href={data?.data.video.artist[0]?.spotify} target='_blank'><BsSpotify /></a>
} }
{data?.data.video.artist[0]?.facebook && {data?.data.video.artist[0]?.facebook &&
<a className='border-2 border-slate-300 text-white hover:border-yellow-500 transition-all hover:text-yellow-500 rounded-full text-2xl p-2' href={data?.data.video.artist[0]?.facebook} target='_blank'><BsFacebook /></a> <a className='border-2 border-slate-300 text-white hover:border-yellow-500 transition-all hover:text-yellow-500 rounded-full text-2xl p-2' href={data?.data.video.artist[0]?.facebook} target='_blank'><BsFacebook /></a>
} }
</div> </div>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<button className="text-4xl mr-8 text-white"><MdHighQuality /></button> <button onClick={()=> changeVideoQuality()} className={["text-4xl mr-8", videoQuality ? 'text-white' : 'text-yellow-400'].join(' ')}><MdHighQuality /></button>
<div className="flex items-center mr-8"> <div className="flex items-center mr-8">
<button onClick={()=> player.current?.seekTo(progressSec - 10, 'seconds')} className='text-white text-2xl'><FaBackward className='fill-white'/></button> <button onClick={()=> player.current?.seekTo(progressSec - 10, 'seconds')} className='text-white text-2xl'><FaBackward className='fill-white'/></button>
<button onClick={()=> setPlaying(playing ? false : true)} className='text-white text-2xl mx-8'>{playing ? <FaPause className='fill-white'/> : <FaPlay className='fill-white'/>}</button> <button onClick={()=> setPlaying(!playing)} className='text-white text-2xl mx-8'>{playing ? <FaPause className='fill-white'/> : <FaPlay className='fill-white'/>}</button>
<button onClick={()=> player.current?.seekTo(progressSec + 10, 'seconds')} className='text-white text-2xl'><FaForward className='fill-white'/></button> <button onClick={()=> player.current?.seekTo(progressSec + 10, 'seconds')} className='text-white text-2xl'><FaForward className='fill-white'/></button>
</div> </div>
<button onClick={()=> setVolume(volume ? false : true)} className='text-3xl text-white'>{volume ? <FaVolumeDown/> : <FaVolumeMute/>}</button> <button onClick={()=> setVolume(volume ? false : true)} className='text-3xl text-white'>{volume ? <FaVolumeDown/> : <FaVolumeMute/>}</button>

@ -28,7 +28,7 @@ const LoginForm: FC = () => {
localStorage.setItem('token', res.data.accessToken); localStorage.setItem('token', res.data.accessToken);
localStorage.setItem('user', JSON.stringify(res.data.user)); localStorage.setItem('user', JSON.stringify(res.data.user));
setIsAuth(true); setIsAuth(true);
navigate('/admin/artists') navigate('/admin')
} }
) )
} }

@ -1,11 +1,12 @@
import { FC } from 'react' import { FC } from 'react'
import { Link } from 'react-router-dom';
import Menu from './Menu'; import Menu from './Menu';
import Profile from './Profile'; import Profile from './Profile';
const Sidebar: FC = () => { const Sidebar: FC = () => {
return ( return (
<aside className='flex flex-col bg-slate-100 p-5 py-10 h-screen basis-[330px]'> <aside className='flex flex-col bg-slate-100 p-5 py-10 h-screen basis-[330px] max-w-[280px] w-full'>
<h1 className='text-slate-900 font-extrabold text-5xl mb-14 px-5'>808</h1> <Link to='/' className='text-slate-900 font-extrabold text-5xl mb-14 px-5'>808</Link>
<Menu/> <Menu/>
<Profile/> <Profile/>
</aside> </aside>

@ -37,7 +37,7 @@ export const ArtistColumns: ColumnDef<IArtist>[] = [
accessorKey: "date", accessorKey: "date",
header: "Date", header: "Date",
cell: ({ row }) => ( cell: ({ row }) => (
<div className="lowercase">{format(new Date(row.getValue("date")), 'dd.mm.yyyy')}</div> <div className="lowercase">{format(new Date(row.getValue("date")), 'dd.MM.yyyy')}</div>
), ),
}, },
{ {

@ -16,7 +16,7 @@ export const PlaylistColumns: ColumnDef<IPlaylist>[] = [
accessorKey: "date", accessorKey: "date",
header: "Date", header: "Date",
cell: ({ row }) => ( cell: ({ row }) => (
<div className="lowercase">{format(new Date(row.getValue("date")), 'dd.mm.yyyy')}</div> <div className="lowercase">{format(new Date(row.getValue("date")), 'dd.MM.yyyy')}</div>
), ),
}, },
{ {

@ -1,4 +1,4 @@
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState, useMemo } from 'react'
import { useForm, SubmitHandler } from "react-hook-form"; import { useForm, SubmitHandler } from "react-hook-form";
import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DialogClose} from "@/components/ui/Dialog" import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DialogClose} from "@/components/ui/Dialog"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/Form" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/Form"
@ -14,6 +14,7 @@ import PlaylistService from '@/services/PlaylistService';
import ArtistService from '@/services/ArtistService'; import ArtistService from '@/services/ArtistService';
import { useQuery, keepPreviousData } from '@tanstack/react-query'; import { useQuery, keepPreviousData } from '@tanstack/react-query';
import ComboBox from '@/components/ui/ComboBox'; import ComboBox from '@/components/ui/ComboBox';
import UploadProgress from './UploadProgress';
interface AddModalProps { interface AddModalProps {
edit?: boolean; edit?: boolean;
@ -26,26 +27,32 @@ export type AddForm = {
videoLQ: string; videoLQ: string;
playlist: string; playlist: string;
artist: string; artist: string;
} };
type IFieldExtended<T> = IField<T> & {type: string} type IFieldExtended<T> = IField<T> & {type: string}
const fields: IFieldExtended<AddForm>[] = [ const AddModal: FC<AddModalProps> = ({edit, video}) => {
{id: 0, label: 'Название видео:', placeholder: 'Название...', name: 'name', required: true, type: 'text'},
// {id: 1, label: 'Исполнитель:', placeholder: 'Исполнитель...', name: 'artist', required: true, type: 'text'}, // @ts-ignore
// {id: 2, label: 'Плейлист:', placeholder: 'Плейлист...', name: 'playlist', required: true, type: 'text'}, const fields: IFieldExtended<AddForm>[] = useMemo(() => [
{id: 3, label: 'Видео HQ:', placeholder: 'Видео HQ...', name: 'videoHQ', required: true, type: 'file'}, {id: 0, label: 'Название видео:', placeholder: 'Название...', name: 'name', required: true, type: 'text'},
{id: 4, label: 'Видео LQ:', placeholder: 'Видео LQ...', name: 'videoLQ', required: true, type: 'file'}, // {id: 1, label: 'Исполнитель:', placeholder: 'Исполнитель...', name: 'artist', required: true, type: 'text'},
] // {id: 2, label: 'Плейлист:', placeholder: 'Плейлист...', name: 'playlist', required: true, type: 'text'},
...(!edit ? [{id: 3, label: 'Видео HQ:', placeholder: 'Видео HQ...', name: 'videoHQ', required: true, type: 'file'}] : []),
...(!edit ? [{id: 4, label: 'Видео LQ:', placeholder: 'Видео LQ...', name: 'videoLQ', required: true, type: 'file'}] : []),
], []);
const AddModal: FC<AddModalProps> = ({edit, video}) => {
const [finds, setFinds] = useState({ const [finds, setFinds] = useState({
find1: '', find1: '',
find2: '' find2: ''
}) })
const { data: playlists } = useQuery({ queryKey: ['playlists', {finds}], queryFn: () => PlaylistService.GetAll({search: finds.find2}), enabled: finds.find2.length >= 3, placeholderData: keepPreviousData}); const { data: playlists } = useQuery({ queryKey: ['playlists', {finds}], queryFn: () => PlaylistService.GetAll({search: finds.find2}), enabled: finds.find2.length >= 3, placeholderData: keepPreviousData});
const { data: artists } = useQuery({ queryKey: ['artists', {finds}], queryFn: () => ArtistService.GetAll({search: finds.find1}), enabled: finds.find1.length >= 3, placeholderData: keepPreviousData}) const { data: artists } = useQuery({ queryKey: ['artists', {finds}], queryFn: () => ArtistService.GetAll({search: finds.find1}), enabled: finds.find1.length >= 3, placeholderData: keepPreviousData})
const [startedUpload, setStartedUpload] = useState<boolean>(false);
const [uploadProgress, setUploadProgress] = useState<number>(0);
const CreateForm = useForm<AddForm>({ const CreateForm = useForm<AddForm>({
defaultValues: { defaultValues: {
name: '', name: '',
@ -71,6 +78,7 @@ const AddModal: FC<AddModalProps> = ({edit, video}) => {
const { toast } = useToast() const { toast } = useToast()
const {createMutation, editMutation} = useVideo(() => { const {createMutation, editMutation} = useVideo(() => {
setOpen(false); setOpen(false);
setStartedUpload(false);
toast({ toast({
title: 'Успешно!', title: 'Успешно!',
description: edit ? 'Видео успешно отредактировано.' : 'Новое видео успешно добавлено.' description: edit ? 'Видео успешно отредактировано.' : 'Новое видео успешно добавлено.'
@ -79,7 +87,7 @@ const AddModal: FC<AddModalProps> = ({edit, video}) => {
const onSubmit: SubmitHandler<AddForm> = async(data) => { const onSubmit: SubmitHandler<AddForm> = async(data) => {
edit ? editMutation.mutate(data) : createMutation.mutate(data); edit ? (editMutation.mutate(data), setStartedUpload(true)) : (createMutation.mutate({...data, setUploadProgress}), setStartedUpload(true));
} }
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, name: keyof AddForm) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>, name: keyof AddForm) => {
@ -101,62 +109,68 @@ const AddModal: FC<AddModalProps> = ({edit, video}) => {
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>{edit ? 'Редактировать' : 'Добавить'} видео</DialogTitle> <DialogTitle>{startedUpload ? 'Загрузка' : edit ? 'Редактировать' : 'Добавить'} видео</DialogTitle>
</DialogHeader> </DialogHeader>
<Form {...CreateForm}> {startedUpload
<form onSubmit={CreateForm.handleSubmit(onSubmit)}> ?
{fields.map(item => <UploadProgress uploadProgress={uploadProgress}/>
:
<Form {...CreateForm}>
<form onSubmit={CreateForm.handleSubmit(onSubmit)}>
{fields.map(item =>
<FormField
key={item.id}
control={CreateForm.control}
name={item.name}
rules={item.required && !edit ? {required: 'Поле обязательно к заполнению'} : {}}
render={({ field }) => (
<FormItem className="mb-5">
<FormLabel>{item.label}</FormLabel>
<FormControl>
<Input type={item.type} placeholder={item.placeholder}
{...(item.type !== 'file' && { ...field})}
{...(item.type === 'file' && { onChange: (e) => handleChange(e, item.name)})}
{...(item.type === 'file' && {accept: "video/*"})}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormField <FormField
key={item.id}
control={CreateForm.control} control={CreateForm.control}
name={item.name} name='artist'
rules={item.required && !edit ? {required: 'Поле обязательно к заполнению'} : {}} rules={{required: 'Поле обязательно к заполнению'}}
render={({ field }) => ( render={({ field }) => (
<FormItem className="mb-5"> <FormItem className="mb-5">
<FormLabel>{item.label}</FormLabel> <FormLabel>Артист:</FormLabel>
<FormControl> <ComboBox field={field.value} onSelect={(e: string)=> CreateForm.setValue("artist", e)} onValueChange={(e: string)=> setFinds({...finds, find1: e})} data={artists?.data.artists}/>
<Input type={item.type} placeholder={item.placeholder}
{...(item.type !== 'file' && { ...field})}
{...(item.type === 'file' && { onChange: (e) => handleChange(e, item.name)})} />
</FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
)} <FormField
<FormField control={CreateForm.control}
control={CreateForm.control} name='playlist'
name='artist' rules={{required: 'Поле обязательно к заполнению'}}
rules={{required: 'Поле обязательно к заполнению'}} render={({ field }) => (
render={({ field }) => ( <FormItem className="mb-5 w-full">
<FormItem className="mb-5"> <FormLabel>Плейлист:</FormLabel>
<FormLabel>Артист:</FormLabel> <ComboBox field={field.value} onSelect={(e: string)=> CreateForm.setValue("playlist", e)} onValueChange={(e: string)=> setFinds({...finds, find2: e})} data={playlists?.data.playlists}/>
<ComboBox field={field.value} onSelect={(e: string)=> CreateForm.setValue("artist", e)} onValueChange={(e: string)=> setFinds({...finds, find1: e})} data={artists?.data.artists}/> <FormMessage />
<FormMessage /> </FormItem>
</FormItem> )}
)} />
/> <DialogFooter>
<FormField <Button type='submit' className='mr-2' variant='default'>{edit ? 'Редактировать' : 'Добавить'}</Button>
control={CreateForm.control} <DialogClose asChild>
name='playlist' <Button type='button' variant='secondary'>Отмена</Button>
rules={{required: 'Поле обязательно к заполнению'}} </DialogClose>
render={({ field }) => ( </DialogFooter>
<FormItem className="mb-5 w-full"> </form>
<FormLabel>Плейлист:</FormLabel> </Form>
<ComboBox field={field.value} onSelect={(e: string)=> CreateForm.setValue("playlist", e)} onValueChange={(e: string)=> setFinds({...finds, find2: e})} data={playlists?.data.playlists}/> }
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type='submit' className='mr-2' variant='default'>{edit ? 'Редактировать' : 'Добавить'}</Button>
<DialogClose asChild>
<Button type='button' variant='secondary'>Отмена</Button>
</DialogClose>
</DialogFooter>
</form>
</Form>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) )

@ -0,0 +1,21 @@
import { FC } from "react";
import { Progress } from "@/components/ui/Progress";
interface UploadProgressProps {
uploadProgress: number;
}
const UploadProgress: FC<UploadProgressProps> = ({uploadProgress}) => {
return (
<div>
{uploadProgress < 100
?
<Progress value={uploadProgress} />
:
<p className="text-black text-lg">Идет обработка...</p>
}
</div>
)
}
export default UploadProgress;

@ -30,7 +30,7 @@ export const VideoColumns: ColumnDef<IVideo>[] = [
accessorKey: "date", accessorKey: "date",
header: "Date", header: "Date",
cell: ({ row }) => ( cell: ({ row }) => (
<div className="lowercase">{format(new Date(row.getValue("date")), 'dd.mm.yyyy')}</div> <div className="lowercase">{format(new Date(row.getValue("date")), 'dd.MM.yyyy')}</div>
), ),
}, },
{ {

@ -2,6 +2,7 @@ import {FC, PropsWithChildren, useEffect, useState, createContext} from 'react';
import axios from "axios"; import axios from "axios";
import { AuthResponse } from '@/models/response/AuthResponse'; import { AuthResponse } from '@/models/response/AuthResponse';
import { API_URL } from '@/http'; import { API_URL } from '@/http';
import { useVisits } from "@/hooks/useVisits.ts";
type AuthContextType = { type AuthContextType = {
isAuth: boolean; isAuth: boolean;
@ -28,14 +29,15 @@ const checkAuth = async (setIsLoading: (bool: boolean) => void, setIsAuth: (bool
} else { } else {
setIsLoading(false); setIsLoading(false);
} }
} }
const AuthProvider: FC<PropsWithChildren> = ({children}) => { const AuthProvider: FC<PropsWithChildren> = ({children}) => {
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const [isAuth, setIsAuth] = useState<boolean>(false); const [isAuth, setIsAuth] = useState<boolean>(false);
const { setVisits } = useVisits();
useEffect(() => { useEffect(() => {
checkAuth(setIsLoading, setIsAuth); void checkAuth(setIsLoading, setIsAuth);
void setVisits();
}, []) }, [])
return ( return (
<AuthContext.Provider value={{isAuth, setIsAuth}}> <AuthContext.Provider value={{isAuth, setIsAuth}}>

@ -0,0 +1,79 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border border-slate-200 bg-white text-slate-950 shadow-sm dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-slate-500 dark:text-slate-400", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

@ -0,0 +1,26 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-slate-100 dark:bg-slate-800",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-slate-900 transition-all dark:bg-slate-50"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

@ -4,10 +4,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
export const useVideo = (callback: () => void, _id: string) => { export const useVideo = (callback: () => void, _id: string) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const createMutation = useMutation({ const createMutation = useMutation({
mutationFn: (newVideo: IVideoReq) => { mutationFn: ({setUploadProgress, ...newVideo}: IVideoReq & {setUploadProgress: (value: number) => void}) => {
return VideoService.Create(newVideo); return VideoService.Create(newVideo, setUploadProgress);
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({queryKey: ['videos']}); queryClient.invalidateQueries({queryKey: ['videos']});

@ -0,0 +1,14 @@
import VisitService from "@/services/VisitService.ts";
export const useVisits = () => {
const setVisits = async () => {
const itemStr = localStorage.getItem('visit')
if(!itemStr || new Date().getTime() > JSON.parse(itemStr).time ) {
await VisitService.Update().then(
()=> localStorage.setItem('visit', JSON.stringify({time: new Date().getTime() + 1000*60*60*24}))
);
}
}
return { setVisits };
}

@ -1,9 +1,9 @@
import axios from "axios"; import axios from "axios";
import { AuthResponse } from "@/models/response/AuthResponse"; import { AuthResponse } from "@/models/response/AuthResponse";
//export const API_URL = `https://api.808.dance/api`
export const API_URL = `http://localhost:8080/api` export const API_URL = `http://localhost:8080/api`
const $api = axios.create({ const $api = axios.create({
withCredentials: true, withCredentials: true,
baseURL: API_URL baseURL: API_URL

@ -1,6 +1,6 @@
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import * as Sentry from "@sentry/react";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { RouterProvider } from "react-router-dom"; import { RouterProvider } from "react-router-dom";
import AuthProvider from './components/Auth/AuthProvider.tsx'; import AuthProvider from './components/Auth/AuthProvider.tsx';
import adminRoutes from './routers/admin.routes.tsx' import adminRoutes from './routers/admin.routes.tsx'
@ -10,6 +10,22 @@ import '@fontsource/inter/700.css';
import '@fontsource/inter/800.css'; import '@fontsource/inter/800.css';
import './index.css' import './index.css'
Sentry.init({
dsn: "https://e8779b77eb666fc83de815be909c1ead@o4506227519651840.ingest.sentry.io/4506227522666496",
integrations: [
new Sentry.BrowserTracing({
// Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
tracePropagationTargets: ["localhost", /^https:\/\/yourserver\.io\/api/],
}),
new Sentry.Replay(),
],
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of the transactions
// Session Replay
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
});
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
@ -23,6 +39,5 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<AuthProvider> <AuthProvider>
<RouterProvider router={adminRoutes} /> <RouterProvider router={adminRoutes} />
</AuthProvider> </AuthProvider>
</QueryClientProvider> </QueryClientProvider>
) )

@ -0,0 +1,5 @@
export interface IVisit {
_id: string;
name: string;
count: number;
}

@ -0,0 +1,8 @@
import {IVisit} from "@/models/IVisit.ts";
export interface VisitsResponse {
allTime: IVisit;
month: IVisit;
day: IVisit;
lastday: IVisit;
}

@ -0,0 +1,33 @@
import {Card, CardContent, CardHeader, CardTitle,} from "@/components/ui/Card"
import {keepPreviousData, useQuery} from "@tanstack/react-query";
import VisitService from "@/services/VisitService.ts";
const DashboardPage = () => {
const { data } = useQuery({ queryKey: ['visits'], queryFn: () => VisitService.GetAll(), placeholderData: keepPreviousData})
const dataFraction = (data?.data.day.count && data?.data.lastday.count)
&& data?.data.day.count > data?.data.lastday.count
? Math.floor((data?.data.day.count/data?.data.lastday.count-1)*100) + ' %'
: 0 + ' %'
const dataObj: {id: number, name: string, value?: number | string}[] = [
{id: 1, name: 'За всё время', value: data?.data.allTime.count},
{id: 2, name: 'За месяц', value: data?.data.month.count},
{id: 3, name: 'За сегодня', value: data?.data.day.count},
{id: 4, name: 'Доля вернувшихся', value: dataFraction}
];
return (
<div className='flex items-start p-10 justify-between w-full'>
{dataObj.map(item=>
<Card key={item.id} className='text-gray-800 w-full mr-10'>
<CardHeader className='pb-4'>
<CardTitle className='text-xl'>{item.name}</CardTitle>
</CardHeader>
<CardContent>
<p className='text-2xl font-bold'>{item.value}</p>
</CardContent>
</Card>
)}
</div>
);
};
export default DashboardPage;

@ -4,18 +4,23 @@ import UnauthorizeRequired from "@/components/Auth/UnauthorizeRequired";
import AdminLayout from "@/components/Layouts/AdminLayout"; import AdminLayout from "@/components/Layouts/AdminLayout";
import DashboardPage from "@/pages/Admin/DashboardPage.tsx";
import LoginPage from "@/pages/Admin/LoginPage"; import LoginPage from "@/pages/Admin/LoginPage";
import ArtistsPage from "@/pages/Admin/ArtistsPage"; import ArtistsPage from "@/pages/Admin/ArtistsPage";
import UsersPage from "@/pages/Admin/UsersPage"; import UsersPage from "@/pages/Admin/UsersPage";
import VideosPage from "@/pages/Admin/VideosPage"; import VideosPage from "@/pages/Admin/VideosPage";
import PlatlistsPage from "@/pages/Admin/PlatlistsPage"; import PlatlistsPage from "@/pages/Admin/PlatlistsPage";
import App from "@/App"; import App from "@/App";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
element: <AuthRequired><AdminLayout/></AuthRequired>, element: <AuthRequired><AdminLayout/></AuthRequired>,
children: [ children: [
{
path: "/admin",
element: <DashboardPage/>
},
{ {
path: "/admin/artists", path: "/admin/artists",
element: <ArtistsPage/> element: <ArtistsPage/>

@ -5,7 +5,7 @@ import { IVideoReq } from '@/models/IVideo';
export default class VideoService { export default class VideoService {
static async Create({ name, videoHQ, videoLQ, playlist, artist }: IVideoReq): Promise<AxiosResponse<VideoResponse>> { static async Create({ name, videoHQ, videoLQ, playlist, artist }: IVideoReq, setUploadProgress: (value: number) => void): Promise<AxiosResponse<VideoResponse>> {
const formData = new FormData(); const formData = new FormData();
formData.append("name", name); formData.append("name", name);
@ -14,7 +14,15 @@ export default class VideoService {
formData.append("playlist", playlist ?? ''); formData.append("playlist", playlist ?? '');
formData.append("artist", artist); formData.append("artist", artist);
return $api.post<VideoResponse>('/video', formData, {withCredentials: true}) return $api.post<VideoResponse>('/video', formData, {
withCredentials: true,
onUploadProgress: function(progressEvent) {
if(progressEvent.total) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
setUploadProgress(percentCompleted);
}
}
})
} }
static async GetAll({ page, search }: {page?: number, search?: string}): Promise<AxiosResponse<VideosResponse>> { static async GetAll({ page, search }: {page?: number, search?: string}): Promise<AxiosResponse<VideosResponse>> {

@ -0,0 +1,13 @@
import $api from '@/http';
import { AxiosResponse } from 'axios';
import {VisitsResponse} from "@/models/response/VisitResponse.ts";
export default class VisitService {
static async Update(): Promise<AxiosResponse<string>> {
return $api.put<string>('/visit', {}, {withCredentials: true})
}
static async GetAll(): Promise<AxiosResponse<VisitsResponse>> {
return $api.get<VisitsResponse>('/visit', {withCredentials: true})
}
}

@ -1,19 +1,30 @@
import { sentryVitePlugin } from "@sentry/vite-plugin";
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import path from "path" import path from "path"
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react(), sentryVitePlugin({
org: "none-fut",
project: "javascript-react"
})],
server: { server: {
port: 3000, port: 3000,
}, },
preview: { preview: {
port: 3000, port: 3000,
}, },
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
}, },
}, },
})
build: {
sourcemap: true
}
})

@ -0,0 +1,14 @@
import AWS from 'aws-sdk';
const storage = new AWS.S3({
endpoint: 'https://storage.yandexcloud.net',
accessKeyId: 'YCAJETluKbNj6pNd9GLMvFToy',
secretAccessKey: 'YCPRu1qIZ_p4OGLMdAYb_zjv3-lSAh3HnvYy-J-I',
region: 'ru-central1',
httpOptions: {
timeout: 10000,
connectTimeout: 10000
},
});
export default storage;

@ -9,9 +9,10 @@ class VideoController {
if(!errors.isEmpty()) { if(!errors.isEmpty()) {
return next(ApiError.BadRequest('Ошибка при валидации', errors.array())); return next(ApiError.BadRequest('Ошибка при валидации', errors.array()));
} }
console.log(req.files); const { name, playlist, artist } = req.body;
const { name, videoHQ, videoLQ, playlist, artist } = req.body; const { videoHQ, videoLQ } = req.files;
const video = await VideoService.create({name, videoHQ, videoLQ, playlist, artist});
const video = await VideoService.create({name, playlist, artist, videoHQ, videoLQ});
return res.json(video); return res.json(video);
} catch (e) { } catch (e) {
next(e); next(e);
@ -44,8 +45,8 @@ class VideoController {
async edit(req, res, next) { async edit(req, res, next) {
try { try {
const { id } = req.params; const { id } = req.params;
const { name, videoHQ, videoLQ, playlist, artist } = req.body; const { name, playlist, artist } = req.body;
const video = await VideoService.edit({id, name, videoHQ, videoLQ, playlist, artist}); const video = await VideoService.edit({id, name, playlist, artist});
return res.json(video); return res.json(video);
} catch (e) { } catch (e) {
next(e); next(e);

@ -0,0 +1,22 @@
import VisitService from '../services/VisitService.js';
class VisitController {
async getAll(req, res, next) {
try {
const visits = await VisitService.getAll();
return res.json(visits);
} catch (e) {
next(e);
}
}
async update(req, res, next) {
try {
await VisitService.update();
return res.json('ok');
} catch (e) {
next(e);
}
}
}
export default new VisitController();

@ -2,6 +2,7 @@ import express from "express";
import mongoose from "mongoose"; import mongoose from "mongoose";
import config from "config"; import config from "config";
import cors from "cors"; import cors from "cors";
import visitsReset from "./services/VisitsReset.js";
import errorMiddleware from "./middlewares/errorMiddleware.js"; import errorMiddleware from "./middlewares/errorMiddleware.js";
import fileUpload from "express-fileupload"; import fileUpload from "express-fileupload";
@ -12,6 +13,7 @@ import userRouter from "./routers/user.router.js"
import artistRouter from "./routers/artist.router.js" import artistRouter from "./routers/artist.router.js"
import playlistRouter from "./routers/playlist.router.js" import playlistRouter from "./routers/playlist.router.js"
import videoRouter from "./routers/video.router.js" import videoRouter from "./routers/video.router.js"
import visitRouter from "./routers/visits.router.js"
const PORT = config.get('serverPort'); const PORT = config.get('serverPort');
const app = express(); const app = express();
@ -27,10 +29,10 @@ app.use('/api/user', userRouter);
app.use('/api/artist', artistRouter); app.use('/api/artist', artistRouter);
app.use('/api/playlist', playlistRouter); app.use('/api/playlist', playlistRouter);
app.use('/api/video', videoRouter); app.use('/api/video', videoRouter);
app.use('/api/visit', visitRouter);
app.use(errorMiddleware); app.use(errorMiddleware);
const start = async() => { const start = async() => {
try { try {
mongoose.set('strictQuery', true); mongoose.set('strictQuery', true);
@ -40,6 +42,7 @@ const start = async() => {
})); }));
app.listen(PORT, ()=> { app.listen(PORT, ()=> {
console.log(`Сервер успешно запущен на порту: ${PORT}`) console.log(`Сервер успешно запущен на порту: ${PORT}`)
visitsReset();
}) })
} catch (e) { } catch (e) {
console.log(e) console.log(e)

@ -0,0 +1,8 @@
import mongoose, { Schema } from "mongoose";
const Visits = new Schema({
name: {type: String},
count: {type: Number}
});
export default mongoose.model('Visits', Visits);

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"aws-sdk": "^2.1495.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"config": "^3.3.9", "config": "^3.3.9",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
@ -18,7 +19,8 @@
"express-validator": "^7.0.1", "express-validator": "^7.0.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mongodb": "^6.2.0", "mongodb": "^6.2.0",
"mongoose": "^8.0.0" "mongoose": "^8.0.0",
"node-schedule": "^2.1.1"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.0.1" "nodemon": "^3.0.1"
@ -165,11 +167,61 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
}, },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/aws-sdk": {
"version": "2.1495.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1495.0.tgz",
"integrity": "sha512-JbefhY9G3WooJJjTtSUegyuNiYhY0vFd0q1KtpY8W+z1U6aKovkIyLJsR2de6u8KXZQkcwT+7N46BYT1SbZ5sQ==",
"dependencies": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"util": "^0.12.4",
"uuid": "8.0.0",
"xml2js": "0.5.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
}, },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bcrypt": { "node_modules/bcrypt": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
@ -244,6 +296,16 @@
"node": ">=16.20.1" "node": ">=16.20.1"
} }
}, },
"node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"node_modules/buffer-equal-constant-time": { "node_modules/buffer-equal-constant-time": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@ -401,6 +463,17 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/cron-parser": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
"dependencies": {
"luxon": "^3.2.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -491,6 +564,14 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/express": { "node_modules/express": {
"version": "4.18.2", "version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@ -592,6 +673,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
"dependencies": {
"is-callable": "^1.1.3"
}
},
"node_modules/forwarded": { "node_modules/forwarded": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -774,6 +863,20 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dependencies": {
"has-symbols": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": { "node_modules/has-unicode": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
@ -849,6 +952,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"node_modules/ignore-by-default": { "node_modules/ignore-by-default": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
@ -877,6 +985,21 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -889,6 +1012,17 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": { "node_modules/is-extglob": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -906,6 +1040,20 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/is-generator-function": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
"integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-glob": { "node_modules/is-glob": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@ -927,6 +1075,33 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/is-typed-array": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
"integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
"dependencies": {
"which-typed-array": "^1.1.11"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/json5": { "node_modules/json5": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@ -1031,6 +1206,11 @@
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
}, },
"node_modules/long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
"integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w=="
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -1042,6 +1222,14 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/luxon": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
"engines": {
"node": ">=12"
}
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@ -1349,6 +1537,19 @@
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"
} }
}, },
"node_modules/node-schedule": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz",
"integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==",
"dependencies": {
"cron-parser": "^4.2.0",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.3.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/nodemon": { "node_modules/nodemon": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz",
@ -1534,6 +1735,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/range-parser": { "node_modules/range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -1619,6 +1829,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
@ -1734,6 +1949,11 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/sorted-array-functions": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
"integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
},
"node_modules/sparse-bitfield": { "node_modules/sparse-bitfield": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@ -1907,6 +2127,32 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
"integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==",
"dependencies": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"node_modules/url/node_modules/punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -1920,6 +2166,14 @@
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
}, },
"node_modules/uuid": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/validator": { "node_modules/validator": {
"version": "13.11.0", "version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
@ -1956,6 +2210,24 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/which-typed-array": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
"integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
"dependencies": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.4",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wide-align": { "node_modules/wide-align": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@ -1969,6 +2241,26 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"engines": {
"node": ">=4.0"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",

@ -10,6 +10,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"aws-sdk": "^2.1495.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"config": "^3.3.9", "config": "^3.3.9",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
@ -19,7 +20,8 @@
"express-validator": "^7.0.1", "express-validator": "^7.0.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mongodb": "^6.2.0", "mongodb": "^6.2.0",
"mongoose": "^8.0.0" "mongoose": "^8.0.0",
"node-schedule": "^2.1.1"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.0.1" "nodemon": "^3.0.1"

@ -0,0 +1,10 @@
import { Router } from "express";
import VisitController from '../controllers/VisitController.js';
import authMiddleware from "../middlewares/authMiddleware.js";
const router = new Router();
router.put('/', VisitController.update);
router.get('/', authMiddleware, VisitController.getAll);
export default router;

@ -2,14 +2,22 @@ import Video from '../models/Video.js';
import Playlist from '../models/Playlist.js'; import Playlist from '../models/Playlist.js';
import Artist from '../models/Artist.js'; import Artist from '../models/Artist.js';
import ApiError from '../controllers/ErrorController.js'; import ApiError from '../controllers/ErrorController.js';
import storage from '../config/storage.js';
import uploadVideo from "../utils/uploadVideo.js";
class VideoService { class VideoService {
async create({name, videoHQ, videoLQ, playlist, artist}) { async create({name, videoHQ, videoLQ, playlist, artist}) {
if(!videoHQ && !videoLQ) {
throw ApiError.BadRequest('Видео не загружены');
}
if(videoHQ?.mimetype.indexOf('video') === -1 || videoLQ?.mimetype.indexOf('video') === -1) {
throw ApiError.BadRequest('К загрузке принимаются только видео.');
}
const getArtist = await Artist.findById(artist); const getArtist = await Artist.findById(artist);
if(!getArtist) { if(!getArtist) {
throw ApiError.BadRequest('Такого исполнителя не существует'); throw ApiError.BadRequest('Такого исполнителя не существует');
} }
if(playlist) { if(playlist) {
const getPlaylist = await Playlist.findById(playlist); const getPlaylist = await Playlist.findById(playlist);
if(!getPlaylist) { if(!getPlaylist) {
@ -17,7 +25,10 @@ class VideoService {
} }
} }
const video = await Video.create({name, videoHQ, videoLQ, playlist, artist, date: new Date()}); const videoHQLink = await uploadVideo(videoHQ);
const videoLQLink = await uploadVideo(videoLQ);
const video = await Video.create({name, videoHQ: videoHQLink.Location, videoLQ: videoLQLink.Location, playlist, artist, date: new Date()});
return {video}; return {video};
} }
@ -77,7 +88,7 @@ class VideoService {
} }
} }
const video = await Video.findByIdAndUpdate(id, {name, videoHQ, videoLQ, playlist, artist}, {returnOriginal: false}); const video = await Video.findByIdAndUpdate(id, {name, playlist, artist}, {returnOriginal: false});
if(!video) { if(!video) {
throw ApiError.BadRequest('Видео не существует'); throw ApiError.BadRequest('Видео не существует');
} }

@ -0,0 +1,21 @@
import Visits from "../models/Visits.js";
import ApiError from '../controllers/ErrorController.js';
class VisitService {
async getAll() {
const visits = await Visits.find();
const allTime = visits.find(item => item.name === 'alltime');
const month = visits.find(item => item.name === 'month');
const day = visits.find(item => item.name === 'day');
const lastday = visits.find(item => item.name === 'lastday');
return { allTime, month, day, lastday};
}
async update() {
await Visits.updateMany({name: {$ne: 'lastday'}}, { $inc: { count: 1 } });
return 'ok';
}
}
export default new VisitService();

@ -0,0 +1,17 @@
import Visits from "../models/Visits.js";
import schedule from 'node-schedule'
const visitsReset = () => {
schedule.scheduleJob({hour: 0}, async function() {
const today = await Visits.findOne({name: 'day'});
await Visits.findOneAndUpdate({name: 'lastday'}, {count: today.count});
await Visits.findOneAndUpdate({name: 'day'}, { count: 0 });
});
schedule.scheduleJob({date :1}, async function() {
await Visits.findOneAndUpdate({name: 'month'}, { count: 0 });
});
}
export default visitsReset;

@ -0,0 +1,24 @@
import storage from "../config/storage.js";
const uploadVideo = async (file) => {
let fileExt = file.name.split('.').pop();
let fileName = Date.now() + Math.random() + '.' + fileExt;
const params = {
Bucket: '808',
Key: `${fileName}`,
Body: file.data,
}
const uplfile = await new Promise(function(resolve, reject) {
storage.upload(params, function(err, data) {
if (err) return reject(err);
return resolve(data);
});
});
return uplfile;
}
export default uploadVideo;
Loading…
Cancel
Save