chore: init commit from GitHub
This commit is contained in:
parent
11a3f40596
commit
aebe654c38
19
.github/workflows/docker-deploy.yml
vendored
Normal file
19
.github/workflows/docker-deploy.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
jobs:
|
||||||
|
update:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Update Git Repository
|
||||||
|
working-directory: /home/swapdude/MainStack
|
||||||
|
run: |
|
||||||
|
pwd
|
||||||
|
git fetch --all
|
||||||
|
git reset --hard origin/main
|
||||||
|
- name: Build & Deploy on docker-compose
|
||||||
|
working-directory: /home/swapdude/MainStack
|
||||||
|
run: |
|
||||||
|
docker compose up -d --build
|
26
.github/workflows/dotnet.yml
vendored
Normal file
26
.github/workflows/dotnet.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# This workflow will build a .NET project
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||||
|
|
||||||
|
name: Build Project .NET
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 6.0.x
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build --no-restore
|
415
.gitignore
vendored
415
.gitignore
vendored
@ -1,402 +1,13 @@
|
|||||||
# ---> VisualStudio
|
################################################################################
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
# Данный GITIGNORE-файл был автоматически создан Microsoft(R) Visual Studio.
|
||||||
## files generated by popular Visual Studio add-ons.
|
################################################################################
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
/SWAD.API/appsettings.local.json
|
||||||
|
/SWAD.API/appsettings.Development.json
|
||||||
# User-specific files
|
.vs
|
||||||
*.rsuser
|
.idea
|
||||||
*.suo
|
|
||||||
*.user
|
#Rider sucks
|
||||||
*.userosscache
|
.idea/.idea.SWAD.API/.idea/workspace.xml
|
||||||
*.sln.docstates
|
SWAD.Front/node_modules
|
||||||
|
TelegramBot/obj
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
|
||||||
ScaffoldingReadMe.txt
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
|
||||||
!Directory.Build.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.tlog
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
|
||||||
coverage*.json
|
|
||||||
coverage*.xml
|
|
||||||
coverage*.info
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
|
||||||
*.vbp
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
|
||||||
*.dsw
|
|
||||||
*.dsp
|
|
||||||
|
|
||||||
# Visual Studio 6 technical files
|
|
||||||
*.ncb
|
|
||||||
*.aps
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# Visual Studio History (VSHistory) files
|
|
||||||
.vshistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
|
||||||
FodyWeavers.xsd
|
|
||||||
|
|
||||||
# VS Code files for those working on multiple tools
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
|
||||||
.history/
|
|
||||||
|
|
||||||
# Windows Installer files from build outputs
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
*.sln.iml
|
|
||||||
|
|
365
ApiTest/.gitignore
vendored
Normal file
365
ApiTest/.gitignore
vendored
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# DocFx
|
||||||
|
/.vscode/
|
||||||
|
log/
|
||||||
|
obj/
|
||||||
|
_site/
|
||||||
|
.optemp/
|
||||||
|
_themes/
|
||||||
|
_themes.MSDN.Modern/
|
||||||
|
_themes.VS.Modern/
|
||||||
|
|
||||||
|
.openpublishing.buildcore.ps1
|
||||||
|
.openpublishing.redirection.sorted.json
|
||||||
|
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Folder configuration on Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Custom added by ghogen
|
||||||
|
settings.json
|
38
ApiTest/ApiTest.csproj
Normal file
38
ApiTest/ApiTest.csproj
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bogus" Version="35.5.0" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||||
|
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||||
|
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="NUnit.Framework"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SWAD.API\SWAD.API.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="appsettings.json">
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
26
ApiTest/MusicApi/Spotify.cs
Normal file
26
ApiTest/MusicApi/Spotify.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using SWAD.API.Models.Config.ApiServices;
|
||||||
|
using SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
namespace ApiTest.MusicApi;
|
||||||
|
|
||||||
|
public class Spotify
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task SpotifyAuthTest()
|
||||||
|
{
|
||||||
|
var confbuilder = new ConfigurationBuilder().SetBasePath(Environment.CurrentDirectory)
|
||||||
|
.AddJsonFile("appsettings.json")
|
||||||
|
.Build().GetSection(ApiServicesConfig.ConfigName);
|
||||||
|
//throw new Exception(Environment.CurrentDirectory);
|
||||||
|
IOptions<ApiServicesConfig> config = new OptionsWrapper<ApiServicesConfig>(confbuilder.Get<ApiServicesConfig>());
|
||||||
|
var authService = new SpotifyAuthService(config);
|
||||||
|
var authResponse = await authService.GetToken();
|
||||||
|
authResponse.Should()
|
||||||
|
.NotBeNull();
|
||||||
|
authResponse.Token.Should()
|
||||||
|
.NotBeNull();
|
||||||
|
}
|
||||||
|
}
|
40
README.md
40
README.md
@ -1,3 +1,41 @@
|
|||||||
# SwapDude
|
# SwapDude
|
||||||
|
Platform for sharing music links from one music service to another for free!
|
||||||
|
|
||||||
SwapDude - Platform for sharing music links from one music service to another for free!
|

|
||||||
|
## Supported Services*:
|
||||||
|
|
||||||
|
- [X] Spotify
|
||||||
|
- [ ] Deezer
|
||||||
|
- [X] Tidal
|
||||||
|
- [ ] Apple Music
|
||||||
|
- [X] Yandex.Music
|
||||||
|
- [ ] VK/BOOM
|
||||||
|
- ~~SoundCloud~~
|
||||||
|
|
||||||
|
_*Where_ ☑ _is released and_ ☐ _in development or ~~not planned~~_
|
||||||
|
## Features*:
|
||||||
|
|
||||||
|
- [ ] Creating link for multiplie services
|
||||||
|
- [X] Finding links with track on another services
|
||||||
|
- [ ] Proxy links to service based on your SWAD account
|
||||||
|
|
||||||
|
_*Where_ ☑ _is released and_ ☐ _in development._
|
||||||
|
|
||||||
|
## Source code:
|
||||||
|
### Stack:
|
||||||
|
#### Angular (Front-end SPA)
|
||||||
|
##### Projects:
|
||||||
|
SWAD.Front
|
||||||
|
#### C# Asp.NET (Back-end REST API)
|
||||||
|
##### Projects:
|
||||||
|
SWAD.API
|
||||||
|
### Build:
|
||||||
|
|
||||||
|
```docker compose up```
|
||||||
|
|
||||||
|
So i think you are not so stupid to guess it yourself. Enjoy!
|
||||||
|
|
||||||
|
## Problem songs:
|
||||||
|
https://open.spotify.com/track/0IF9nrOlmE3iVKRkJ1QkuQ - (Possible latin symbols instead cyrilic)
|
||||||
|
|
||||||
|
response: not found
|
||||||
|
42
SWAD.API.sln
Normal file
42
SWAD.API.sln
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.8.34330.188
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SWAD.API", ".\SWAD.API\SWAD.API.csproj", "{36313BFE-46A8-44E7-BD08-2652A90C03D1}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{30A2E1AB-5617-47D9-9FB5-F441645C4513}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.editorconfig = .editorconfig
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiTest", "ApiTest\ApiTest.csproj", "{DF986307-4329-49A6-926E-C23D34C65173}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TelegramBot", "TelegramBot\TelegramBot.csproj", "{E4B6CD72-213C-404C-824F-3A74C1722EAD}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{36313BFE-46A8-44E7-BD08-2652A90C03D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{36313BFE-46A8-44E7-BD08-2652A90C03D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{36313BFE-46A8-44E7-BD08-2652A90C03D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{36313BFE-46A8-44E7-BD08-2652A90C03D1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DF986307-4329-49A6-926E-C23D34C65173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DF986307-4329-49A6-926E-C23D34C65173}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DF986307-4329-49A6-926E-C23D34C65173}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DF986307-4329-49A6-926E-C23D34C65173}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E4B6CD72-213C-404C-824F-3A74C1722EAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E4B6CD72-213C-404C-824F-3A74C1722EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E4B6CD72-213C-404C-824F-3A74C1722EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E4B6CD72-213C-404C-824F-3A74C1722EAD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {31ECAE7B-5C42-4C80-A7F8-81DADD6DAD8E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
3
SWAD.API.sln.DotSettings
Normal file
3
SWAD.API.sln.DotSettings
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=SWAD/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
4
SWAD.API/.editorconfig
Normal file
4
SWAD.API/.editorconfig
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# CS1591: Отсутствует комментарий XML для открытого видимого типа или члена
|
||||||
|
dotnet_diagnostic.CS1591.severity = none
|
364
SWAD.API/.gitignore
vendored
Normal file
364
SWAD.API/.gitignore
vendored
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# DocFx
|
||||||
|
/.vscode/
|
||||||
|
log/
|
||||||
|
obj/
|
||||||
|
_site/
|
||||||
|
.optemp/
|
||||||
|
_themes/
|
||||||
|
_themes.MSDN.Modern/
|
||||||
|
_themes.VS.Modern/
|
||||||
|
|
||||||
|
.openpublishing.buildcore.ps1
|
||||||
|
.openpublishing.redirection.sorted.json
|
||||||
|
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Folder configuration on Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
appsettings.Development.json
|
17
SWAD.API/Consts/Enums/MusicService.cs
Normal file
17
SWAD.API/Consts/Enums/MusicService.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace SWAD.API.Consts.Enums;
|
||||||
|
|
||||||
|
public enum MusicService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Spotify Service
|
||||||
|
/// </summary>
|
||||||
|
Spotify,
|
||||||
|
/// <summary>
|
||||||
|
/// Tidal Service
|
||||||
|
/// </summary>
|
||||||
|
Tidal,
|
||||||
|
/// <summary>
|
||||||
|
/// Yandex Service
|
||||||
|
/// </summary>
|
||||||
|
Yandex
|
||||||
|
}
|
31
SWAD.API/Consts/Enums/ServiceResult.cs
Normal file
31
SWAD.API/Consts/Enums/ServiceResult.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
namespace SWAD.API.Consts.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service errors for controller
|
||||||
|
/// </summary>
|
||||||
|
public enum ServiceResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns on the successful executed state
|
||||||
|
/// </summary>
|
||||||
|
Success,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns on the unsuccessful executed state
|
||||||
|
/// </summary>
|
||||||
|
Failure,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns on the unsuccessful executed state not by code
|
||||||
|
/// </summary>
|
||||||
|
NoResponse,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns on the unsuccessful executed state by user
|
||||||
|
/// </summary>
|
||||||
|
BadRequest,
|
||||||
|
/// <summary>
|
||||||
|
/// Returns on the unsuccessful executed state by service
|
||||||
|
/// </summary>
|
||||||
|
NotFound
|
||||||
|
}
|
14
SWAD.API/Consts/ErrorResources.cs
Normal file
14
SWAD.API/Consts/ErrorResources.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace SWAD.API.Consts;
|
||||||
|
|
||||||
|
public static class ErrorResources
|
||||||
|
{
|
||||||
|
public const string SomethingWentWrong = "I don't know what but something went wrong";
|
||||||
|
public const string ApiNotRespond = "Service provider not respond, please try later";
|
||||||
|
public const string BadRequest = "Oops, request is bad, dude!";
|
||||||
|
public const string Unknown = "Problem really unknown... I don't even know what else to say";
|
||||||
|
public const string NotFound = "That service is really dummy, man... Not found that track";
|
||||||
|
|
||||||
|
//For exceptions
|
||||||
|
|
||||||
|
public const string Unsuccessful = "Request unsuccessful";
|
||||||
|
}
|
39
SWAD.API/Controllers/DTOs/LinkDtos.cs
Normal file
39
SWAD.API/Controllers/DTOs/LinkDtos.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
|
||||||
|
namespace SWAD.API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Track directly from query
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="Name">Name of track</param>
|
||||||
|
/// <param Name="Artist">Artist of track</param>
|
||||||
|
/// <param Name="AlbumObject">AlbumObject of track</param>
|
||||||
|
/// <param Name="Service">Service provider</param>
|
||||||
|
public record TrackDto([Required] string Name, [Required] string Artist, string Album, MusicService Service);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Track from service link
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="Link">
|
||||||
|
/// Spotify example: https://open.spotify.com/track/2K7xn816oNHJZ0aVqdQsha
|
||||||
|
/// Tidal example: https://tidal.com/track/294942856
|
||||||
|
/// Yandex.Music example: https://music.yandex.by/Albums/25851387/track/113810002 P.S.: Будда какая параша, даже тут
|
||||||
|
/// яндекс отличился
|
||||||
|
/// </param>
|
||||||
|
/// <param Name="Service">
|
||||||
|
/// </param>
|
||||||
|
public record TrackLinkDto([Required] string Link, [Required] MusicService Service);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service Name
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="Service">Enum MusicService</param>
|
||||||
|
/// <param Name="Name">MusicService.ToString</param>
|
||||||
|
public record ServiceDto(MusicService Service, string Name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of GetLink method
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="Link"></param>
|
||||||
|
public record LinkResultDto(string? Link);
|
111
SWAD.API/Controllers/LinkController.cs
Normal file
111
SWAD.API/Controllers/LinkController.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SWAD.API.Consts;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Controllers.DTOs;
|
||||||
|
using SWAD.API.Services.Links;
|
||||||
|
|
||||||
|
namespace SWAD.API.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controller for get links from music providers
|
||||||
|
/// </summary>
|
||||||
|
[Tags($"{nameof(LinkController)}: Controller for get links from music providers")]
|
||||||
|
[Route("[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class LinkController(LinksService linksService) : SwadController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get link from other link
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="track">Track query such as Name, artist, Albums</param>
|
||||||
|
/// <returns>Link for track</returns>
|
||||||
|
[HttpPost("FromLink")]
|
||||||
|
[ProducesResponseType(typeof(LinkResultDto), 200)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), 400)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), 404)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), 502)]
|
||||||
|
public async Task<IActionResult> GetLink(TrackLinkDto track)
|
||||||
|
{
|
||||||
|
var serviceResult = await linksService.MapLinks(track);
|
||||||
|
|
||||||
|
switch (serviceResult.result)
|
||||||
|
{
|
||||||
|
case ServiceResult.Success:
|
||||||
|
return Ok(new LinkResultDto(serviceResult.link));
|
||||||
|
|
||||||
|
case ServiceResult.Failure:
|
||||||
|
return BadRequest($"{track.Link} is not recognized. Try other link");
|
||||||
|
|
||||||
|
case ServiceResult.NoResponse:
|
||||||
|
return BadGateway("Bad gateway with one of services");
|
||||||
|
|
||||||
|
case ServiceResult.BadRequest:
|
||||||
|
return BadRequest($"Unknown service {track.Service} or parameters not valid");
|
||||||
|
|
||||||
|
case ServiceResult.NotFound:
|
||||||
|
return NotFound(ErrorResources.NotFound);
|
||||||
|
default:
|
||||||
|
throw new ApplicationException("Unknown response from service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get link from search query such as artist and song Name
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="track">Track query such as Name, artist, Albums</param>
|
||||||
|
/// <returns>Link for track</returns>
|
||||||
|
[HttpPost("FromQuery")]
|
||||||
|
[ProducesResponseType(typeof(LinkResultDto), 200)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), 400)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), 404)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), 502)]
|
||||||
|
public async Task<IActionResult> GetLink(TrackDto track)
|
||||||
|
{
|
||||||
|
var serviceResult = await linksService.GetLinkByQuery(track);
|
||||||
|
|
||||||
|
switch (serviceResult.result)
|
||||||
|
{
|
||||||
|
case ServiceResult.Success:
|
||||||
|
return Ok(new LinkResultDto(serviceResult.link));
|
||||||
|
|
||||||
|
case ServiceResult.Failure:
|
||||||
|
return NotFound($"Track {track.Name} - {track.Artist} not found!");
|
||||||
|
|
||||||
|
case ServiceResult.NoResponse:
|
||||||
|
return BadGateway($"Bad gateway with service {track.Service}");
|
||||||
|
|
||||||
|
case ServiceResult.BadRequest:
|
||||||
|
return BadRequest($"Unknown service {track.Service} or parameters not valid");
|
||||||
|
|
||||||
|
case ServiceResult.NotFound:
|
||||||
|
return BadRequest(ErrorResources.NotFound);
|
||||||
|
default:
|
||||||
|
throw new ApplicationException($"Unknown response from service {linksService}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get service what uses that link
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="link"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ApplicationException"></exception>
|
||||||
|
[HttpGet("GetService")]
|
||||||
|
[ProducesResponseType(typeof(ServiceDto), 200)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), 400)]
|
||||||
|
public async Task<IActionResult> GetServiceOfLink([Required] string link)
|
||||||
|
{
|
||||||
|
var serviceResult = await linksService.GetServiceByLink(link);
|
||||||
|
switch (serviceResult.result)
|
||||||
|
{
|
||||||
|
case ServiceResult.Success:
|
||||||
|
return Ok(new ServiceDto(serviceResult.service!.Value,
|
||||||
|
serviceResult.service.Value.ToString()));
|
||||||
|
case ServiceResult.Failure:
|
||||||
|
return BadRequest($"{link} is not recognized. Try other link");
|
||||||
|
default:
|
||||||
|
throw new ApplicationException("WTF are you doing?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
SWAD.API/Controllers/ProblemsController.cs
Normal file
22
SWAD.API/Controllers/ProblemsController.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace SWAD.API.Controllers;
|
||||||
|
|
||||||
|
public abstract class ProblemsController : ControllerBase
|
||||||
|
{
|
||||||
|
protected ObjectResult BadRequest(string? detail)
|
||||||
|
{
|
||||||
|
return Problem(statusCode: StatusCodes.Status400BadRequest, title: "Bad request!", detail: detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ObjectResult NotFound(string? detail)
|
||||||
|
{
|
||||||
|
return Problem(statusCode: StatusCodes.Status404NotFound, title: "Not found :c", detail: detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ObjectResult BadGateway(string? detail)
|
||||||
|
{
|
||||||
|
return Problem(statusCode: StatusCodes.Status502BadGateway, title: "Some problems in other side, dude!",
|
||||||
|
detail: detail);
|
||||||
|
}
|
||||||
|
}
|
11
SWAD.API/Controllers/SwadController.cs
Normal file
11
SWAD.API/Controllers/SwadController.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace SWAD.API.Controllers;
|
||||||
|
|
||||||
|
public class SwadController : ProblemsController
|
||||||
|
{
|
||||||
|
public static string GetTitleForSwagger(string controllerName, string description)
|
||||||
|
{
|
||||||
|
return $"{controllerName}: {description}";
|
||||||
|
}
|
||||||
|
}
|
15
SWAD.API/Dockerfile
Normal file
15
SWAD.API/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# https://hub.docker.com/_/microsoft-dotnet
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
WORKDIR /source
|
||||||
|
|
||||||
|
# copy csproj and restore as distinct layers
|
||||||
|
COPY ./ ./
|
||||||
|
RUN dotnet restore
|
||||||
|
WORKDIR /source/SWAD.API
|
||||||
|
RUN dotnet publish -c release -o /app --no-restore
|
||||||
|
|
||||||
|
# final stage/image
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app ./
|
||||||
|
ENTRYPOINT ["dotnet", "/app/SWAD.API.dll"]
|
3
SWAD.API/Exceptions/TrackNotFoundException.cs
Normal file
3
SWAD.API/Exceptions/TrackNotFoundException.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace SWAD.API.Exceptions;
|
||||||
|
|
||||||
|
public class TrackNotFoundException(string? message) : Exception(message);
|
90
SWAD.API/Middlewares/ExceptionMiddleware.cs
Normal file
90
SWAD.API/Middlewares/ExceptionMiddleware.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace SWAD.API.Middlewares;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DONT TOUCH THAT!!! Generate good messages for responses and handle to logger exceptions
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="logger"></param>
|
||||||
|
/// <param Name="env"></param>
|
||||||
|
/// <param Name="next"></param>
|
||||||
|
public class ExceptionMiddleware(ILogger<ExceptionMiddleware> logger, IWebHostEnvironment env, RequestDelegate next)
|
||||||
|
{
|
||||||
|
private const string DefaultMessage = "An unexpected error has occurred, dude.";
|
||||||
|
|
||||||
|
private const string ResponseStartedMessage =
|
||||||
|
"The response has already started, the http status code middleware will not be executed.";
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await next(httpContext);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (httpContext.Response.HasStarted)
|
||||||
|
{
|
||||||
|
logger.LogWarning(ResponseStartedMessage);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = string.IsNullOrEmpty(httpContext?.TraceIdentifier)
|
||||||
|
? Guid.NewGuid().ToString()
|
||||||
|
: httpContext.TraceIdentifier;
|
||||||
|
logger.LogError($"An exception was thrown during the request, dude. " +
|
||||||
|
$"{id} \n{(env.IsDevelopment() ? "strcstart:\n" + e : e.GetType().Name) + e.StackTrace + e.Source + e.InnerException}");
|
||||||
|
// logger.LogError($"An exception was thrown during the request, dude. " +
|
||||||
|
// $"{id} \n{(env.IsDevelopment() ? "strcstart:\n" + e : e.GetType().Name)}");
|
||||||
|
|
||||||
|
await ProblemExceptionResponse(httpContext!, e, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProblemExceptionResponse(HttpContext httpContext, Exception e, string id)
|
||||||
|
{
|
||||||
|
//Create problem
|
||||||
|
var problem = new ProblemDetails
|
||||||
|
{
|
||||||
|
Title = DefaultMessage,
|
||||||
|
Status = StatusCodes.Status500InternalServerError
|
||||||
|
};
|
||||||
|
//Add stacktrace if env is development
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
problem.Detail = $"{e.Message} in {e.GetType().Name}";
|
||||||
|
var lines = new List<string> { "Next strings is stacktrace. Enjoy, dude :)", "strcstart:" }
|
||||||
|
.Concat(e.StackTrace!.Split("\r\n").Select(e => e.TrimStart()));
|
||||||
|
|
||||||
|
problem.Extensions.Add(new KeyValuePair<string, object?>("StackTrace", lines));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
problem.Detail = $"{e.Message} in {e}";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (e)
|
||||||
|
{
|
||||||
|
case NotImplementedException:
|
||||||
|
problem.Status = StatusCodes.Status501NotImplemented;
|
||||||
|
break;
|
||||||
|
case ValidationException ve:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResponse = JsonSerializer
|
||||||
|
.Serialize(problem, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||||
|
});
|
||||||
|
|
||||||
|
httpContext.Response.StatusCode = problem.Status ?? StatusCodes.Status500InternalServerError;
|
||||||
|
httpContext.Response.ContentType = "application/problem+json";
|
||||||
|
|
||||||
|
await httpContext.Response
|
||||||
|
.WriteAsync(jsonResponse);
|
||||||
|
}
|
||||||
|
}
|
13
SWAD.API/Models/Config/ApiServices/ApiServicesConfig.cs
Normal file
13
SWAD.API/Models/Config/ApiServices/ApiServicesConfig.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using SWAD.API.Models.Config.ApiServices.Structure;
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.Config.ApiServices;
|
||||||
|
|
||||||
|
// ReSharper disable once ClassNeverInstantiated.Global because: Is structure for config
|
||||||
|
public class ApiServicesConfig
|
||||||
|
{
|
||||||
|
public const string ConfigName = "APIServices";
|
||||||
|
|
||||||
|
// ReSharper disable once PropertyCanBeMadeInitOnly.Global
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
public ApiServiceData[] ServicesData { get; set; } = null!;
|
||||||
|
}
|
10
SWAD.API/Models/Config/ApiServices/Structure/ApiPaths.cs
Normal file
10
SWAD.API/Models/Config/ApiServices/Structure/ApiPaths.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace SWAD.API.Models.Config.ApiServices.Structure;
|
||||||
|
|
||||||
|
// ReSharper disable once ClassNeverInstantiated.Global because: Is structure for config
|
||||||
|
public class ApiPaths
|
||||||
|
{
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
public string Search { get; set; } = null!;
|
||||||
|
public string GetTrack { get; set; } = null!;
|
||||||
|
public string GetArtist { get; set; } = null!;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace SWAD.API.Models.Config.ApiServices.Structure;
|
||||||
|
|
||||||
|
// ReSharper disable once ClassNeverInstantiated.Global because: Is structure for config
|
||||||
|
public class ApiServiceData
|
||||||
|
{
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
public string ClientId { get; set; } = null!;
|
||||||
|
public string Secret { get; set; } = null!;
|
||||||
|
public ApiServiceEndpoints Endpoints { get; set; } = null!;
|
||||||
|
public ApiPaths ApiPaths { get; set; } = null!;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
namespace SWAD.API.Models.Config.ApiServices.Structure;
|
||||||
|
|
||||||
|
// ReSharper disable once ClassNeverInstantiated.Global because: Is structure for config
|
||||||
|
public class ApiServiceEndpoints
|
||||||
|
{
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
public string Base { get; set; } = null!;
|
||||||
|
public string Api { get; set; } = null!;
|
||||||
|
public string Token { get; set; } = null!;
|
||||||
|
public string[] MusicLink { get; set; } = null!;
|
||||||
|
}
|
8
SWAD.API/Models/Config/ServicesEndpointsConfig.cs
Normal file
8
SWAD.API/Models/Config/ServicesEndpointsConfig.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace SWAD.API.Models.Config;
|
||||||
|
|
||||||
|
public class ServicesEndpointsConfig
|
||||||
|
{
|
||||||
|
public const string ConfigName = "ServicesEndpoints";
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
public string Redis { get; init; } = null!;
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI;
|
||||||
|
|
||||||
|
public abstract class DefaultAuthResponse : IAuthResponse
|
||||||
|
{
|
||||||
|
private int? _expire;
|
||||||
|
private DateTime? _revokedAt;
|
||||||
|
|
||||||
|
[JsonPropertyName("access_token")]
|
||||||
|
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
public string Token { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("expires_in")]
|
||||||
|
public int? ExpireTime
|
||||||
|
{
|
||||||
|
get => _expire;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_expire = value;
|
||||||
|
_revokedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? ExpireAt => _revokedAt?.AddSeconds(ExpireTime ?? 0);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Spotify;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Json Structure for spotify auth response
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyAuthResponse : DefaultAuthResponse;
|
@ -0,0 +1,31 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Spotify;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Json Structure for spotify search response
|
||||||
|
/// </summary>
|
||||||
|
internal class SpotifySearchResponse
|
||||||
|
{
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
[JsonPropertyName("tracks")] public TrackList Tracks { get; set; } = null!;
|
||||||
|
|
||||||
|
public class TrackList
|
||||||
|
{
|
||||||
|
[JsonPropertyName("items")] public List<Item> Items { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExternalUrls
|
||||||
|
{
|
||||||
|
[JsonPropertyName("spotify")] public string Spotify { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Item
|
||||||
|
{
|
||||||
|
[JsonPropertyName("href")] public string? Href { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("id")] public string? Id { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("external_urls")] public ExternalUrls ExternalUrls { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Spotify;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Json Structure for tidal track response
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyTrackResponse
|
||||||
|
{
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
[JsonPropertyName("name")] public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("artists")] public Artist[] Artists { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("album")] public Album Albums { get; set; } = null!;
|
||||||
|
|
||||||
|
public class Artist
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public string Id { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("name")] public string Name { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Album
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public string Id { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("name")] public string Name { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Tidal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Json Structure for tidal track response
|
||||||
|
/// </summary>
|
||||||
|
public class TidalArtistResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("data")] public DataObject Data { get; set; } = new();
|
||||||
|
|
||||||
|
public class DataObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("attributes")] public AttributesObject Attributes { get; set; } = new();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AttributesObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")] public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Tidal;
|
||||||
|
|
||||||
|
public class TidalAuthResponse : DefaultAuthResponse;
|
@ -0,0 +1,15 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Tidal;
|
||||||
|
|
||||||
|
internal class TidalSearchResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("data")] public TidalTrackResponse.DataObject Data { get; set; } = null!;
|
||||||
|
|
||||||
|
public class Track
|
||||||
|
{
|
||||||
|
[JsonPropertyName("resource")] public TidalTrackResponse.DataObject DataObject { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
// ReSharper disable NullableWarningSuppressionIsUsed
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Tidal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Json Structure for tidal track response
|
||||||
|
/// </summary>
|
||||||
|
public class TidalTrackResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("data")] public DataObject Data { get; set; } = new();
|
||||||
|
|
||||||
|
public class DataObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("attributes")] public AttributesObject Attributes { get; set; } = new();
|
||||||
|
[JsonPropertyName("relationships")] public RelationShipsObject RelationShips { get; set; } = new();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AttributesObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("title")] public string Title { get; set; } = null!;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RelationShipsObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("artists")] public ArtistObject Artist { get; set; } = null!;
|
||||||
|
[JsonPropertyName("tracks")] public ArtistObject Track { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ArtistObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("data")] public List<ArtistDataObject> Data { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrackObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("data")] public List<TrackDataObject> Data { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrackDataObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public string Id { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class ArtistDataObject
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public string Id { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,268 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Yandex;
|
||||||
|
/*
|
||||||
|
* ВНИМАНИЕ
|
||||||
|
* Yandex API писали шизы поэтому когда будешь добавлять поля в YandexSearchResponse
|
||||||
|
* ПОМНИ: ID у артистов и альбомов в разных местах JSON в одних и тех же объектах то string то number
|
||||||
|
* ЭТО ВЫЗОВЕТ ИСКЛЮЧЕНИЕ
|
||||||
|
* Думой
|
||||||
|
*/
|
||||||
|
public class YandexSearchResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("status")]
|
||||||
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("entities")]
|
||||||
|
public List<Entity> Entities { get; set; }
|
||||||
|
}
|
||||||
|
public class Album
|
||||||
|
{
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("year")]
|
||||||
|
public int? Year { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackCount")]
|
||||||
|
public int? TrackCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Album2
|
||||||
|
{
|
||||||
|
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("year")]
|
||||||
|
public int? Year { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackCount")]
|
||||||
|
public int? TrackCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Artist
|
||||||
|
{
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Artist2
|
||||||
|
{
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Counts
|
||||||
|
{
|
||||||
|
[JsonPropertyName("tracks")]
|
||||||
|
public int? Tracks { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("directAlbums")]
|
||||||
|
public int? DirectAlbums { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("alsoAlbums")]
|
||||||
|
public int? AlsoAlbums { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("alsoTracks")]
|
||||||
|
public int? AlsoTracks { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Cover
|
||||||
|
{
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("prefix")]
|
||||||
|
public string Prefix { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("uri")]
|
||||||
|
public string Uri { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DerivedColors
|
||||||
|
{
|
||||||
|
[JsonPropertyName("average")]
|
||||||
|
public string Average { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("waveText")]
|
||||||
|
public string WaveText { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("miniPlayer")]
|
||||||
|
public string MiniPlayer { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("accent")]
|
||||||
|
public string Accent { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Entity
|
||||||
|
{
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("results")]
|
||||||
|
public List<Result> Results { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Fade
|
||||||
|
{
|
||||||
|
[JsonPropertyName("inStart")]
|
||||||
|
public double? InStart { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("inStop")]
|
||||||
|
public double? InStop { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("outStart")]
|
||||||
|
public double? OutStart { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("outStop")]
|
||||||
|
public double? OutStop { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Label
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LyricsInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("hasAvailableSyncLyrics")]
|
||||||
|
public bool? HasAvailableSyncLyrics { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("hasAvailableTextLyrics")]
|
||||||
|
public bool? HasAvailableTextLyrics { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Major
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class R128
|
||||||
|
{
|
||||||
|
[JsonPropertyName("i")]
|
||||||
|
public double? I { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("tp")]
|
||||||
|
public double? Tp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Ratings
|
||||||
|
{
|
||||||
|
[JsonPropertyName("month")]
|
||||||
|
public int? Month { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Result
|
||||||
|
{
|
||||||
|
[JsonPropertyName("text")]
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("track")]
|
||||||
|
public Track Track { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Track
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("realId")]
|
||||||
|
public string RealId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("version")]
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackSource")]
|
||||||
|
public string TrackSource { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("major")]
|
||||||
|
public Major Major { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("available")]
|
||||||
|
public bool? Available { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForPremiumUsers")]
|
||||||
|
public bool? AvailableForPremiumUsers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableFullWithoutPermission")]
|
||||||
|
public bool? AvailableFullWithoutPermission { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("disclaimers")]
|
||||||
|
public List<object> Disclaimers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForOptions")]
|
||||||
|
public List<string> AvailableForOptions { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("albums")]
|
||||||
|
public List<Album> Albums { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("durationMs")]
|
||||||
|
public int? DurationMs { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("storageDir")]
|
||||||
|
public string StorageDir { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fileSize")]
|
||||||
|
public int? FileSize { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("r128")]
|
||||||
|
public R128 R128 { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fade")]
|
||||||
|
public Fade Fade { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("previewDurationMs")]
|
||||||
|
public int? PreviewDurationMs { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("coverUri")]
|
||||||
|
public string CoverUri { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ogImage")]
|
||||||
|
public string OgImage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyricsAvailable")]
|
||||||
|
public bool? LyricsAvailable { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyricsInfo")]
|
||||||
|
public LyricsInfo LyricsInfo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("derivedColors")]
|
||||||
|
public DerivedColors DerivedColors { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("rememberPosition")]
|
||||||
|
public bool? RememberPosition { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackSharingFlag")]
|
||||||
|
public string TrackSharingFlag { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("contentWarning")]
|
||||||
|
public string ContentWarning { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrackPosition
|
||||||
|
{
|
||||||
|
[JsonPropertyName("volume")]
|
||||||
|
public int? Volume { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("index")]
|
||||||
|
public int? Index { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,222 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SWAD.API.Models.JsonStructures.MusicAPI.Yandex;
|
||||||
|
|
||||||
|
public class YandexTrackResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("artists")]
|
||||||
|
public List<Artist> Artists { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("track")] public Track Track { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyric")] public List<Lyric> Lyric { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlsoInAlbum
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public int Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")] public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("metaType")] public string MetaType { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("contentWarning")] public string ContentWarning { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("year")] public int Year { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("releaseDate")] public DateTime ReleaseDate { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("coverUri")] public string CoverUri { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ogImage")] public string OgImage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("genre")] public string Genre { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackCount")] public int TrackCount { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("likesCount")] public int LikesCount { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("recent")] public bool Recent { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("veryImportant")] public bool VeryImportant { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("artists")] public List<Artist> Artists { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("labels")] public List<System.Reflection.Emit.Label> Labels { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("available")] public bool Available { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForPremiumUsers")]
|
||||||
|
public bool AvailableForPremiumUsers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForOptions")]
|
||||||
|
public List<string> AvailableForOptions { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForMobile")]
|
||||||
|
public bool AvailableForMobile { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availablePartially")]
|
||||||
|
public bool AvailablePartially { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("bests")] public List<int> Bests { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("disclaimers")] public List<object> Disclaimers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackPosition")] public TrackPosition TrackPosition { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Credit
|
||||||
|
{
|
||||||
|
[JsonPropertyName("title")] public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("value")] public string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Link
|
||||||
|
{
|
||||||
|
[JsonPropertyName("title")] public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("href")] public string Href { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")] public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("socialNetwork")] public string SocialNetwork { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Live
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("realId")] public string RealId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")] public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("version")] public string Version { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("contentWarning")] public string ContentWarning { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("major")] public Major Major { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("available")] public bool Available { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForPremiumUsers")]
|
||||||
|
public bool AvailableForPremiumUsers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableFullWithoutPermission")]
|
||||||
|
public bool AvailableFullWithoutPermission { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForOptions")]
|
||||||
|
public List<string> AvailableForOptions { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("disclaimers")] public List<object> Disclaimers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("storageDir")] public string StorageDir { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("durationMs")] public int DurationMs { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fileSize")] public int FileSize { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("r128")] public R128 R128 { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fade")] public Fade Fade { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("previewDurationMs")]
|
||||||
|
public int PreviewDurationMs { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("artists")] public List<Artist> Artists { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("albums")] public List<Album> Albums { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("coverUri")] public string CoverUri { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("derivedColors")] public DerivedColors DerivedColors { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ogImage")] public string OgImage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyricsAvailable")] public bool LyricsAvailable { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")] public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("rememberPosition")] public bool RememberPosition { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackSharingFlag")] public string TrackSharingFlag { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyricsInfo")] public LyricsInfo LyricsInfo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackSource")] public string TrackSource { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Lyric
|
||||||
|
{
|
||||||
|
[JsonPropertyName("lyricsAvailable")] public bool LyricsAvailable { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fullLyrics")] public string FullLyrics { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyrics")] public string Lyrics { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OtherVersions
|
||||||
|
{
|
||||||
|
[JsonPropertyName("live")] public List<Live> Live { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SimilarTrack
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("realId")] public string RealId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")] public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("major")] public Major Major { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("available")] public bool Available { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForPremiumUsers")]
|
||||||
|
public bool AvailableForPremiumUsers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableFullWithoutPermission")]
|
||||||
|
public bool AvailableFullWithoutPermission { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("availableForOptions")]
|
||||||
|
public List<string> AvailableForOptions { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("disclaimers")] public List<object> Disclaimers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("storageDir")] public string StorageDir { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("durationMs")] public int DurationMs { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fileSize")] public int FileSize { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("r128")] public R128 R128 { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fade")] public Fade Fade { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("previewDurationMs")]
|
||||||
|
public int PreviewDurationMs { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("artists")] public List<Artist> Artists { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("albums")] public List<Album> Albums { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("coverUri")] public string CoverUri { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("derivedColors")] public DerivedColors DerivedColors { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ogImage")] public string OgImage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyricsAvailable")] public bool LyricsAvailable { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")] public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("rememberPosition")] public bool RememberPosition { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackSharingFlag")] public string TrackSharingFlag { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lyricsInfo")] public LyricsInfo LyricsInfo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("trackSource")] public string TrackSource { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("contentWarning")] public string ContentWarning { get; set; }
|
||||||
|
}
|
34
SWAD.API/Program.cs
Normal file
34
SWAD.API/Program.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace SWAD.API;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
Startup.Configure(builder);
|
||||||
|
Startup.ConfigureServices(builder.Services);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
Startup.ConfigureApplication(app);
|
||||||
|
Splash(app.Logger);
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Splash(ILogger logger)
|
||||||
|
{
|
||||||
|
var splash = """
|
||||||
|
____ ____ _ _ ____ ___
|
||||||
|
/ ___|_ ____ _ _ __ | _ \ _ _ __| | ___ / \ | _ \_ _|
|
||||||
|
\___ \ \ /\ / / _` | '_ \| | | | | | |/ _` |/ _ \ / _ \ | |_) | |
|
||||||
|
___) \ V V / (_| | |_) | |_| | |_| | (_| | __/_ / ___ \| __/| |
|
||||||
|
|____/ \_/\_/ \__,_| .__/|____/ \__,_|\__,_|\___(_)_/ \_\_| |___|
|
||||||
|
|_|
|
||||||
|
""";
|
||||||
|
logger.LogWarning(splash);
|
||||||
|
logger.LogInformation(
|
||||||
|
$"Build assembly version: {FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion}");
|
||||||
|
}
|
||||||
|
}
|
37
SWAD.API/Properties/launchSettings.json
Normal file
37
SWAD.API/Properties/launchSettings.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5287"
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7013;http://localhost:5287"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:20004",
|
||||||
|
"sslPort": 44385
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
SWAD.API/SWAD.API.csproj
Normal file
37
SWAD.API/SWAD.API.csproj
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<RootNamespace>SWAD.API</RootNamespace>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AssemblyName>SWAD.API</AssemblyName>
|
||||||
|
<VersionSuffix>0.1.$([System.DateTime]::UtcNow.ToString(MMdd)).$([System.DateTime]::Now.ToString(HHmm))</VersionSuffix>
|
||||||
|
<AssemblyVersion Condition=" '$(VersionSuffix)' == '' ">0.0.0.1</AssemblyVersion>
|
||||||
|
<AssemblyVersion Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>
|
||||||
|
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.1.0</Version>
|
||||||
|
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
|
||||||
|
<Company>SpectruMTeamCode</Company>
|
||||||
|
<Authors>Lisoveliy</Authors>
|
||||||
|
<Copyright>Copyright © $(Company) $([System.DateTime]::UtcNow.ToString(yyyy))</Copyright>
|
||||||
|
<Product>SWAD Platform</Product>
|
||||||
|
<Description>Platform for sharing music links from one music service to another for free! (REST API Back-end)</Description>
|
||||||
|
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||||
|
<DocumentationFile>doc.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="index.html">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StackExchange.Redis" Version="2.7.33" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="wwwroot\swagger-ui\theme-flattop.css" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
121
SWAD.API/Services/Links/LinksService.cs
Normal file
121
SWAD.API/Services/Links/LinksService.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using StackExchange.Redis;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Controllers.DTOs;
|
||||||
|
using SWAD.API.Exceptions;
|
||||||
|
using SWAD.API.Services.MusicAPI.Api;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.Links;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for manipulating with links (LinkController)
|
||||||
|
/// </summary>
|
||||||
|
public class LinksService
|
||||||
|
{
|
||||||
|
private readonly Dictionary<MusicService, ApiService> _services;
|
||||||
|
private readonly IDatabase _redis;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public LinksService(IEnumerable<ApiService> services, IDatabase redis, ILogger<LinksService> logger)
|
||||||
|
{
|
||||||
|
_services = services.ToList()
|
||||||
|
.ConvertAll(x => new KeyValuePair<MusicService, ApiService>(x.ServiceType, x)).ToDictionary();
|
||||||
|
_redis = redis;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get link by query
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="query">Search query</param>
|
||||||
|
/// <returns>Success, Failure, BadRequest, NoResponse</returns>
|
||||||
|
public async Task<(string? link, ServiceResult result)> GetLinkByQuery(TrackDto query)
|
||||||
|
{
|
||||||
|
//Mapping service
|
||||||
|
var service = _services.GetValueOrDefault(query.Service);
|
||||||
|
//Throw result
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var output = await service!.GetLinkByQuery(query);
|
||||||
|
return (output, output != null ? ServiceResult.Success : ServiceResult.Failure);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
return (null, ServiceResult.BadRequest);
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
return (null, ServiceResult.NoResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get MusicService by url
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="link"></param>
|
||||||
|
/// <returns>Success, Failure</returns>
|
||||||
|
public async Task<(MusicService? service, ServiceResult result)> GetServiceByLink(string link)
|
||||||
|
{
|
||||||
|
var cacheKey = $"GET_SERVICE_{link}";
|
||||||
|
var cache = await _redis.StringGetAsync(cacheKey);
|
||||||
|
if (cache.HasValue)
|
||||||
|
{
|
||||||
|
var result = (MusicService?)Enum.Parse(typeof(MusicService), cache.ToString());
|
||||||
|
return (result, ServiceResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"Getting service for {link}...");
|
||||||
|
foreach (var service in _services.Values)
|
||||||
|
if (service.Config.Endpoints.MusicLink.Any(link.StartsWith))
|
||||||
|
{
|
||||||
|
await _redis.StringSetAsync(cacheKey, service.ServiceType.ToString(), TimeSpan.FromDays(30));
|
||||||
|
return (service.ServiceType, ServiceResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"FAIL");
|
||||||
|
return (null, ServiceResult.Failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get Link from other service
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="trackLink">link from first service</param>
|
||||||
|
/// <returns>link from another service, Success, Failure, BadRequest, NoResponse</returns>
|
||||||
|
public async Task<(string? link, ServiceResult result)> MapLinks(TrackLinkDto trackLink)
|
||||||
|
{
|
||||||
|
var service = await GetServiceByLink(trackLink.Link);
|
||||||
|
if (service.result != ServiceResult.Success) return (null, service.result);
|
||||||
|
|
||||||
|
var cacheKey = $"{service.service}_{trackLink.Link}_{Enum.GetName(trackLink.Service)}";
|
||||||
|
var cache = await _redis.StringGetAsync(cacheKey);
|
||||||
|
if (cache.HasValue)
|
||||||
|
{
|
||||||
|
return (cache.ToString(), ServiceResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"Getting {trackLink.Link} for {Enum.GetName(trackLink.Service)}...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var apiService = _services[service.service!.Value];
|
||||||
|
var query = await apiService.GetQueryObject(trackLink.Link);
|
||||||
|
var linkApiService = _services[trackLink.Service];
|
||||||
|
var link = await linkApiService.GetLinkByQuery(query);
|
||||||
|
if (link == null)
|
||||||
|
return (null, ServiceResult.Failure);
|
||||||
|
await _redis.StringSetAsync(cacheKey, link, TimeSpan.FromDays(30));
|
||||||
|
return (link, ServiceResult.Success);
|
||||||
|
}
|
||||||
|
catch (TrackNotFoundException)
|
||||||
|
{
|
||||||
|
return (null, ServiceResult.NotFound);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
return (null, ServiceResult.BadRequest);
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
return (null, ServiceResult.NoResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
SWAD.API/Services/MusicAPI/Api/ApiService.cs
Normal file
35
SWAD.API/Services/MusicAPI/Api/ApiService.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Controllers.DTOs;
|
||||||
|
using SWAD.API.Models.Config.ApiServices.Structure;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.MusicAPI.Api;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract ApiService of music services
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ApiService
|
||||||
|
{
|
||||||
|
public ApiServiceData Config { get; protected init; } = null!;
|
||||||
|
public MusicService ServiceType { get; protected init; }
|
||||||
|
public abstract Task<string?> GetLinkByQuery(TrackDto query);
|
||||||
|
public abstract Task<TrackDto> GetQueryObject(string link, string CountryCode = "US");
|
||||||
|
protected string GetQuery(TrackDto dto) => $"{dto.Name} - {dto.Artist}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all implemented API Services
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>API Services types</returns>
|
||||||
|
public static IEnumerable<Type> GetAllImplementations()
|
||||||
|
{
|
||||||
|
var type = typeof(ApiService);
|
||||||
|
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.SelectMany(s => s.GetTypes())
|
||||||
|
.Where(p => type.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract);
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected struct Messages
|
||||||
|
{
|
||||||
|
public const string AuthFailMessage = "Update token request for service {0} failed";
|
||||||
|
}
|
||||||
|
}
|
109
SWAD.API/Services/MusicAPI/Api/SpotifyService.cs
Normal file
109
SWAD.API/Services/MusicAPI/Api/SpotifyService.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Web;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Controllers.DTOs;
|
||||||
|
using SWAD.API.Models.Config.ApiServices;
|
||||||
|
using SWAD.API.Models.JsonStructures.MusicAPI.Spotify;
|
||||||
|
using SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.MusicAPI.Api;
|
||||||
|
|
||||||
|
public class SpotifyService : ApiService
|
||||||
|
{
|
||||||
|
private readonly SpotifyAuthService _authService;
|
||||||
|
private readonly Uri _searchUri;
|
||||||
|
private SpotifyAuthResponse? _token;
|
||||||
|
|
||||||
|
public SpotifyService(IOptions<ApiServicesConfig> config, IEnumerable<AbstractAuthService> authServices)
|
||||||
|
{
|
||||||
|
ServiceType = MusicService.Spotify;
|
||||||
|
var configServices = config.Value.ServicesData;
|
||||||
|
Config = configServices.First(x => x.Name == ServiceType.ToString());
|
||||||
|
_authService = (authServices.First(x => x.ServiceType == ServiceType) as SpotifyAuthService) ?? throw new ApplicationException("Auth service not found");
|
||||||
|
_searchUri = new Uri(new Uri(Config.Endpoints.Api), Config.ApiPaths.Search);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Auto revoke on Expire
|
||||||
|
private SpotifyAuthResponse? Token
|
||||||
|
{
|
||||||
|
get => _token?.ExpireAt < DateTime.UtcNow ? null : _token;
|
||||||
|
set => _token = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get link to spotify by search query
|
||||||
|
/// </summary>
|
||||||
|
/// <param Name="query">DTO with search query</param>
|
||||||
|
/// <returns>link from spotify</returns>
|
||||||
|
/// <exception cref="AuthenticationFailureException">If token problems</exception>
|
||||||
|
/// <exception cref="HttpRequestException">If response is bad</exception>
|
||||||
|
public override async Task<string?> GetLinkByQuery(TrackDto query)
|
||||||
|
{
|
||||||
|
if (Token == null)
|
||||||
|
{
|
||||||
|
var newToken = await _authService.GetToken() as SpotifyAuthResponse;
|
||||||
|
Token = newToken ??
|
||||||
|
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var client = new HttpClient();
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
|
||||||
|
|
||||||
|
var url = new UriBuilder(_searchUri)
|
||||||
|
{
|
||||||
|
Port = -1
|
||||||
|
};
|
||||||
|
var urlQuery = HttpUtility.ParseQueryString(url.Query);
|
||||||
|
urlQuery["q"] = GetQuery(query);
|
||||||
|
urlQuery["type"] = "track";
|
||||||
|
urlQuery["limit"] = "1";
|
||||||
|
urlQuery["offset"] = "0";
|
||||||
|
url.Query = urlQuery.ToString();
|
||||||
|
|
||||||
|
var response = await client.GetAsync(url.Uri);
|
||||||
|
if (!response.IsSuccessStatusCode) throw new HttpRequestException("Request unsuccessful");
|
||||||
|
var json = await JsonSerializer.DeserializeAsync<SpotifySearchResponse>(
|
||||||
|
await response.Content.ReadAsStreamAsync());
|
||||||
|
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
return json!.Tracks.Items[0].ExternalUrls.Spotify;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<TrackDto> GetQueryObject(string link, string countryCode = "US")
|
||||||
|
{
|
||||||
|
var url = new Uri(link);
|
||||||
|
var id = url.Segments[^1];
|
||||||
|
|
||||||
|
if (Token == null)
|
||||||
|
{
|
||||||
|
var newToken = await _authService.GetToken() as SpotifyAuthResponse;
|
||||||
|
Token = newToken ??
|
||||||
|
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
|
||||||
|
}
|
||||||
|
using var client = new HttpClient();
|
||||||
|
//Prepare request
|
||||||
|
var clientHeaders = client.DefaultRequestHeaders;
|
||||||
|
clientHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
|
||||||
|
|
||||||
|
var builder = new UriBuilder(Config.Endpoints.Api + Path.Combine(Config.ApiPaths.GetTrack, id))
|
||||||
|
{
|
||||||
|
Port = -1,
|
||||||
|
Query = $"market={countryCode}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpRequest = new HttpRequestMessage(HttpMethod.Get, builder.Uri);
|
||||||
|
//Get response
|
||||||
|
var response = await client.SendAsync(httpRequest);
|
||||||
|
if (!response.IsSuccessStatusCode) throw new HttpRequestException("Request unsuccessful");
|
||||||
|
|
||||||
|
var json =
|
||||||
|
await JsonSerializer.DeserializeAsync<SpotifyTrackResponse>(await response.Content.ReadAsStreamAsync());
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
var artists = string.Join(", ", json!.Artists.ToList().ConvertAll(x => x.Name));
|
||||||
|
|
||||||
|
return new TrackDto(json.Name, artists, json.Albums.Name, ServiceType);
|
||||||
|
}
|
||||||
|
}
|
189
SWAD.API/Services/MusicAPI/Api/TidalService.cs
Normal file
189
SWAD.API/Services/MusicAPI/Api/TidalService.cs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Web;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using SWAD.API.Consts;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Controllers.DTOs;
|
||||||
|
using SWAD.API.Exceptions;
|
||||||
|
using SWAD.API.Models.Config.ApiServices;
|
||||||
|
using SWAD.API.Models.JsonStructures.MusicAPI.Tidal;
|
||||||
|
using SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.MusicAPI.Api;
|
||||||
|
|
||||||
|
public class TidalService : ApiService
|
||||||
|
{
|
||||||
|
private readonly TidalAuthService _authService;
|
||||||
|
private TidalAuthResponse? _token;
|
||||||
|
|
||||||
|
public TidalService(IOptions<ApiServicesConfig> config, IEnumerable<AbstractAuthService> authServices)
|
||||||
|
{
|
||||||
|
ServiceType = MusicService.Tidal;
|
||||||
|
var configServices = config.Value.ServicesData;
|
||||||
|
Config = configServices.First(x => x.Name == ServiceType.ToString());
|
||||||
|
_authService = authServices.First(x => x.ServiceType == ServiceType) as TidalAuthService
|
||||||
|
?? throw new ApplicationException("Auth service not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Auto revoke on Expire
|
||||||
|
private TidalAuthResponse? Token
|
||||||
|
{
|
||||||
|
get => _token?.ExpireAt < DateTime.UtcNow ? null : _token;
|
||||||
|
set => _token = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<string?> GetLinkByQuery(TrackDto query)
|
||||||
|
{
|
||||||
|
var searchUri = new UriBuilder(Config.Endpoints.Api);
|
||||||
|
if (Token == null)
|
||||||
|
{
|
||||||
|
var newToken = await _authService.GetToken() as TidalAuthResponse;
|
||||||
|
Token = newToken ??
|
||||||
|
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var client = new HttpClient();
|
||||||
|
var clientHeaders = client.DefaultRequestHeaders;
|
||||||
|
clientHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
|
||||||
|
clientHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.api+json"));
|
||||||
|
|
||||||
|
searchUri.Scheme = "https";
|
||||||
|
searchUri.Path = Path.Combine(searchUri.Path, Config.ApiPaths.Search, WebUtility.UrlEncode(GetQuery(query)
|
||||||
|
//TODO: Это нужно, разработчики TIDAL дауны
|
||||||
|
));
|
||||||
|
var url = new UriBuilder(searchUri.Uri)
|
||||||
|
{
|
||||||
|
Port = -1
|
||||||
|
};
|
||||||
|
var urlQuery = HttpUtility.ParseQueryString(url.Query);
|
||||||
|
//TODO: придумать что-то с этим
|
||||||
|
urlQuery["countryCode"] = "US";
|
||||||
|
urlQuery["include"] = "tracks";
|
||||||
|
|
||||||
|
url.Query = urlQuery.ToString();
|
||||||
|
var httpRequest = new HttpRequestMessage(HttpMethod.Get, url.Uri);
|
||||||
|
httpRequest.Content =
|
||||||
|
new StringContent(string.Empty, new MediaTypeHeaderValue("application/vnd.tidal.v1+json"));
|
||||||
|
|
||||||
|
var response = await client.SendAsync(httpRequest);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var requestDebugData = await response.Content.ReadAsStringAsync();
|
||||||
|
throw new HttpRequestException(ErrorResources.Unsuccessful);
|
||||||
|
}
|
||||||
|
|
||||||
|
var json =
|
||||||
|
await JsonSerializer.DeserializeAsync<TidalSearchResponse>(await response.Content.ReadAsStreamAsync());
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
if (json.Data.RelationShips.Track.Data.Count < 1)
|
||||||
|
{
|
||||||
|
throw new TrackNotFoundException($"Track is not found in {ServiceType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//return null!; //TODO: Rewrite whole service
|
||||||
|
return Config.Endpoints.MusicLink[0] + json.Data.RelationShips.Track.Data[0].Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<TrackDto> GetQueryObject(string link, string countryCode = "US")
|
||||||
|
{
|
||||||
|
var url = new Uri(link);
|
||||||
|
var id = url.Segments[^1];
|
||||||
|
|
||||||
|
if (Token == null)
|
||||||
|
{
|
||||||
|
var newToken = await _authService.GetToken() as TidalAuthResponse;
|
||||||
|
Token = newToken ??
|
||||||
|
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Prepare request
|
||||||
|
using var client = new HttpClient();
|
||||||
|
|
||||||
|
//Get response
|
||||||
|
var nameResponse = await client.SendAsync(GetRequestMessage(client, id, countryCode));
|
||||||
|
if (!nameResponse.IsSuccessStatusCode)
|
||||||
|
if (nameResponse.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
//TODO: переписать на норм обработку CountryCode
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
nameResponse = await client.SendAsync(GetRequestMessage(client, id, "GB")); //Оверрайдим на европу
|
||||||
|
if (!nameResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
throw new HttpRequestException(ErrorResources.Unsuccessful);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new HttpRequestException(ErrorResources.Unsuccessful);
|
||||||
|
|
||||||
|
var trackJson =
|
||||||
|
await JsonSerializer.DeserializeAsync<TidalTrackResponse>(await nameResponse.Content.ReadAsStreamAsync());
|
||||||
|
|
||||||
|
StringBuilder artists = new();
|
||||||
|
foreach (var data in trackJson?.Data.RelationShips.Artist.Data)
|
||||||
|
{
|
||||||
|
var artistsResponse = await client.SendAsync(GetRequestMessage(client, data.Id, countryCode, true));
|
||||||
|
if (!artistsResponse.IsSuccessStatusCode)
|
||||||
|
if (artistsResponse.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
//TODO: переписать на норм обработку CountryCode
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
artistsResponse =
|
||||||
|
await client.SendAsync(GetRequestMessage(client, id, "GB", true)); //Оверрайдим на европу
|
||||||
|
if (!artistsResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
throw new HttpRequestException(ErrorResources.Unsuccessful);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new HttpRequestException(ErrorResources.Unsuccessful);
|
||||||
|
|
||||||
|
var artistJson =
|
||||||
|
await JsonSerializer.DeserializeAsync<TidalArtistResponse>(
|
||||||
|
await artistsResponse.Content.ReadAsStreamAsync());
|
||||||
|
artists.AppendJoin(",", artistJson?.Data.Attributes.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
//var artists = string.Join(", ", json!.Resource.Artists.ToList().ConvertAll(x => x.Name));
|
||||||
|
return new TrackDto(trackJson?.Data.Attributes.Title!, artists.ToString(), null!, ServiceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequestMessage GetRequestMessage(HttpClient client, string id, string countryCode,
|
||||||
|
bool isArtist = false)
|
||||||
|
{
|
||||||
|
var clientHeaders = client.DefaultRequestHeaders;
|
||||||
|
clientHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
|
||||||
|
clientHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.tidal.v1+json"));
|
||||||
|
|
||||||
|
UriBuilder builder;
|
||||||
|
if (isArtist)
|
||||||
|
{
|
||||||
|
builder = new UriBuilder(Config.Endpoints.Api + Path.Combine(Config.ApiPaths.GetArtist, id))
|
||||||
|
{
|
||||||
|
Port = -1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder = new UriBuilder(Config.Endpoints.Api + Path.Combine(Config.ApiPaths.GetTrack, id))
|
||||||
|
{
|
||||||
|
Port = -1,
|
||||||
|
Query = "include=artists"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlQuery = HttpUtility.ParseQueryString(builder.Query);
|
||||||
|
//TODO: Придумать что-то с countryCode
|
||||||
|
urlQuery["countryCode"] = countryCode;
|
||||||
|
builder.Query = urlQuery.ToString();
|
||||||
|
|
||||||
|
var httpRequest = new HttpRequestMessage(HttpMethod.Get, builder.Uri);
|
||||||
|
httpRequest.Content = new StringContent(string.Empty, Encoding.UTF8, "application/vnd.tidal.v1+json");
|
||||||
|
return httpRequest;
|
||||||
|
}
|
||||||
|
}
|
88
SWAD.API/Services/MusicAPI/Api/YandexService.cs
Normal file
88
SWAD.API/Services/MusicAPI/Api/YandexService.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Web;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using SWAD.API.Consts;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Controllers.DTOs;
|
||||||
|
using SWAD.API.Exceptions;
|
||||||
|
using SWAD.API.Models.Config.ApiServices;
|
||||||
|
using SWAD.API.Models.JsonStructures.MusicAPI.Yandex;
|
||||||
|
using SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.MusicAPI.Api;
|
||||||
|
|
||||||
|
public class YandexService : ApiService
|
||||||
|
{
|
||||||
|
public YandexService(IOptions<ApiServicesConfig> config, IEnumerable<AbstractAuthService> authServices)
|
||||||
|
{
|
||||||
|
ServiceType = MusicService.Yandex;
|
||||||
|
var configServices = config.Value.ServicesData;
|
||||||
|
Config = configServices.First(x => x.Name == ServiceType.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task<string?> GetLinkByQuery(TrackDto query)
|
||||||
|
{
|
||||||
|
var searchUri = new Uri(new Uri(Config.Endpoints.Api), Config.ApiPaths.Search);
|
||||||
|
|
||||||
|
using var client = new HttpClient();
|
||||||
|
var clientHeaders = client.DefaultRequestHeaders;
|
||||||
|
|
||||||
|
var response = await client.PostAsync(searchUri, new FormUrlEncodedContent([
|
||||||
|
new("text", GetQuery(query))
|
||||||
|
]));
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var requestDebugData = await response.Content.ReadAsStringAsync();
|
||||||
|
throw new HttpRequestException($"{ErrorResources.Unsuccessful}\n Service returned: {requestDebugData}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = await response.Content.ReadFromJsonAsync<YandexSearchResponse>();
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
if (json.Entities.Count < 1)
|
||||||
|
{
|
||||||
|
throw new TrackNotFoundException($"Track is not found in {ServiceType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = json.Entities.FirstOrDefault(x => x.Type == "track").Results;
|
||||||
|
/*
|
||||||
|
* ВНИМАНИЕ
|
||||||
|
* Yandex API писали шизы поэтому когда будешь добавлять поля в YandexSearchResponse и здесь их юзать
|
||||||
|
* ПОМНИ: ID у артистов и альбомов в разных местах JSON в одних и тех же объектах то string то number
|
||||||
|
* ЭТО ВЫЗОВЕТ ИСКЛЮЧЕНИЕ
|
||||||
|
* Думой
|
||||||
|
*/
|
||||||
|
var resultTrack = results.FirstOrDefault(x => x.Track.Title == query.Name) ??
|
||||||
|
results[0];
|
||||||
|
return $"{Config.Endpoints.MusicLink[0]}{resultTrack.Track.Id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<TrackDto> GetQueryObject(string link, string countryCode = "RU")
|
||||||
|
{
|
||||||
|
var url = new Uri(link);
|
||||||
|
var id = url.Segments[^1];
|
||||||
|
//Prepare request
|
||||||
|
using var client = new HttpClient();
|
||||||
|
var getTrackUrl = new UriBuilder(new Uri(new Uri(Config.Endpoints.Api), Config.ApiPaths.GetTrack))
|
||||||
|
{
|
||||||
|
Port = -1
|
||||||
|
};
|
||||||
|
var urlQuery = HttpUtility.ParseQueryString(url.Query);
|
||||||
|
urlQuery["track"] = id;
|
||||||
|
getTrackUrl.Query = urlQuery.ToString();
|
||||||
|
//Get response
|
||||||
|
var response = await client.GetAsync(getTrackUrl.Uri);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
throw new TrackNotFoundException("Ну, сори, яндекс кал");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new HttpRequestException(ErrorResources.Unsuccessful);
|
||||||
|
|
||||||
|
var json = await response.Content.ReadFromJsonAsync<YandexTrackResponse>();
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
var artists = string.Join(", ", json!.Artists.ToList().ConvertAll(x => x.Name));
|
||||||
|
return new TrackDto(json.Track.Title, artists, json.Track.Albums[0].Title, ServiceType);
|
||||||
|
}
|
||||||
|
}
|
33
SWAD.API/Services/MusicAPI/Auth/AbstractAuthService.cs
Normal file
33
SWAD.API/Services/MusicAPI/Auth/AbstractAuthService.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Models.Config.ApiServices.Structure;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
public abstract class AbstractAuthService
|
||||||
|
{
|
||||||
|
public MusicService ServiceType { get; protected init; }
|
||||||
|
|
||||||
|
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||||
|
protected ApiServiceData Data { get; init; } = null!;
|
||||||
|
public abstract Task<IAuthResponse?> GetToken();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all implemented Auth Services
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>API Services types</returns>
|
||||||
|
public static IEnumerable<Type> GetAllImplementations()
|
||||||
|
{
|
||||||
|
var type = typeof(AbstractAuthService);
|
||||||
|
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.SelectMany(s => s.GetTypes())
|
||||||
|
.Where(p => type.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract);
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAuthResponse
|
||||||
|
{
|
||||||
|
public string Token { get; set; }
|
||||||
|
public int? ExpireTime { get; set; }
|
||||||
|
public DateTime? ExpireAt { get; }
|
||||||
|
}
|
44
SWAD.API/Services/MusicAPI/Auth/SpotifyAuthService.cs
Normal file
44
SWAD.API/Services/MusicAPI/Auth/SpotifyAuthService.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Models.Config.ApiServices;
|
||||||
|
using SWAD.API.Models.JsonStructures.MusicAPI.Spotify;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class SpotifyAuthService : AbstractAuthService
|
||||||
|
{
|
||||||
|
public SpotifyAuthService(IOptions<ApiServicesConfig> data)
|
||||||
|
{
|
||||||
|
ServiceType = MusicService.Spotify;
|
||||||
|
Data = data.Value.ServicesData.First(x => x.Name == ServiceType.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<IAuthResponse?> GetToken()
|
||||||
|
{
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
var content = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "grant_type", "client_credentials" },
|
||||||
|
{ "client_id", Data.ClientId },
|
||||||
|
{ "client_secret", Data.Secret }
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await httpClient.PostAsync(Data.Endpoints.Token, new FormUrlEncodedContent(content));
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var ans = await JsonSerializer.DeserializeAsync<SpotifyAuthResponse>(
|
||||||
|
await response.Content.ReadAsStreamAsync());
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
46
SWAD.API/Services/MusicAPI/Auth/TidalAuthService.cs
Normal file
46
SWAD.API/Services/MusicAPI/Auth/TidalAuthService.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
using SWAD.API.Models.Config.ApiServices;
|
||||||
|
using SWAD.API.Models.JsonStructures.MusicAPI.Tidal;
|
||||||
|
|
||||||
|
namespace SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class TidalAuthService : AbstractAuthService
|
||||||
|
{
|
||||||
|
public TidalAuthService(IOptions<ApiServicesConfig> data)
|
||||||
|
{
|
||||||
|
ServiceType = MusicService.Tidal;
|
||||||
|
Data = data.Value.ServicesData.First(x => x.Name == ServiceType.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<IAuthResponse?> GetToken()
|
||||||
|
{
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
var base64Token = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Data.ClientId}:{Data.Secret}"));
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64Token);
|
||||||
|
var content = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "grant_type", "client_credentials" }
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await httpClient.PostAsync(Data.Endpoints.Token, new FormUrlEncodedContent(content));
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var ans = await JsonSerializer.DeserializeAsync<TidalAuthResponse>(
|
||||||
|
await response.Content.ReadAsStreamAsync());
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
117
SWAD.API/Startup.cs
Normal file
117
SWAD.API/Startup.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
using SWAD.API.Middlewares;
|
||||||
|
using SWAD.API.Models.Config;
|
||||||
|
using SWAD.API.Models.Config.ApiServices;
|
||||||
|
using SWAD.API.Services.Links;
|
||||||
|
using SWAD.API.Services.MusicAPI.Api;
|
||||||
|
using SWAD.API.Services.MusicAPI.Auth;
|
||||||
|
|
||||||
|
namespace SWAD.API;
|
||||||
|
|
||||||
|
public static class Startup
|
||||||
|
{
|
||||||
|
private static ServicesEndpointsConfig? _servicesEndpointsConfig;
|
||||||
|
|
||||||
|
public static void Configure(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
SetConfigurations(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
SetServiceUsages(services);
|
||||||
|
SetCustomServices(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ConfigureApplication(WebApplication app)
|
||||||
|
{
|
||||||
|
SetAppUsages(app);
|
||||||
|
SetAppMappings(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetConfigurations(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
//Json config setup
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.json", false, true) //load base settings
|
||||||
|
.AddJsonFile("appsettings.local.json", true, true); //load local settings
|
||||||
|
|
||||||
|
builder.Services.Configure<ApiServicesConfig>
|
||||||
|
(builder.Configuration.GetSection(ApiServicesConfig.ConfigName));
|
||||||
|
builder.Services.Configure<ServicesEndpointsConfig>
|
||||||
|
(builder.Configuration.GetSection(ServicesEndpointsConfig.ConfigName));
|
||||||
|
|
||||||
|
//Get config for setup
|
||||||
|
_servicesEndpointsConfig = builder.Configuration
|
||||||
|
.GetSection(ServicesEndpointsConfig.ConfigName)
|
||||||
|
.Get<ServicesEndpointsConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetServiceUsages(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddProblemDetails();
|
||||||
|
services.AddControllers();
|
||||||
|
|
||||||
|
services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
options.SwaggerDoc("v1", new OpenApiInfo
|
||||||
|
{
|
||||||
|
Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString(),
|
||||||
|
Title = "SWAD.API",
|
||||||
|
Description =
|
||||||
|
"Share With All, Dude - Platform for sharing music links from one music service to another for free!\n" +
|
||||||
|
"API Reference for SWAD.Front and for guys who trying to make themself stuff :)",
|
||||||
|
TermsOfService = new Uri("https://github.com/SpectruMTeamCode/SWAD/tree/main"),
|
||||||
|
Contact = new OpenApiContact
|
||||||
|
{
|
||||||
|
Name = "GitHub",
|
||||||
|
Url = new Uri("https://github.com/SpectruMTeamCode/SWAD/tree/main")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "doc.xml"));
|
||||||
|
options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetCustomServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
//Custom services setup
|
||||||
|
|
||||||
|
//TODO read from config
|
||||||
|
services.AddSingleton(ConnectionMultiplexer.Connect(_servicesEndpointsConfig.Redis));
|
||||||
|
services.AddSingleton(p =>
|
||||||
|
p.GetService<ConnectionMultiplexer>()?.GetDatabase() ??
|
||||||
|
throw new NullReferenceException("Чёт с инжектом редиса не то"));
|
||||||
|
services.AddSingleton(p => ConnectionMultiplexer.Connect(_servicesEndpointsConfig.Redis));
|
||||||
|
services.AddSingleton(p => p.GetService<ConnectionMultiplexer>()!.GetDatabase());
|
||||||
|
services.AddSingleton<LinksService>();
|
||||||
|
|
||||||
|
//Register all IAPIService implementations
|
||||||
|
foreach (var service in ApiService.GetAllImplementations()) services.AddSingleton(typeof(ApiService), service);
|
||||||
|
//Register all AuthService implementations
|
||||||
|
foreach (var service in AbstractAuthService.GetAllImplementations())
|
||||||
|
services.AddSingleton(typeof(AbstractAuthService), service);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetAppUsages(WebApplication app)
|
||||||
|
{
|
||||||
|
app.UseMiddleware<ExceptionMiddleware>();
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(options =>
|
||||||
|
{
|
||||||
|
options.InjectStylesheet("/swagger-ui/theme-flattop.css");
|
||||||
|
options.IndexStream = () => Assembly.GetEntryAssembly()!.GetManifestResourceStream("SWAD.API.index.html");
|
||||||
|
});
|
||||||
|
app.UseStaticFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetAppMappings(WebApplication app)
|
||||||
|
{
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
//Redirect to swagger
|
||||||
|
app.Map("/", () => { return Results.LocalRedirect("/swagger"); });
|
||||||
|
}
|
||||||
|
}
|
73
SWAD.API/appsettings.json
Normal file
73
SWAD.API/appsettings.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"APIServices": {
|
||||||
|
"ServicesData": [
|
||||||
|
{
|
||||||
|
"Name": "Spotify",
|
||||||
|
"ClientId": "INSERT CLIENTID HERE",
|
||||||
|
"Secret": "INSERT SECRET HERE",
|
||||||
|
"Endpoints": {
|
||||||
|
"Base": "https://open.spotify.com/",
|
||||||
|
"Api": "https://api.spotify.com/v1/",
|
||||||
|
"Token": "https://accounts.spotify.com/api/token",
|
||||||
|
"MusicLink": [
|
||||||
|
"https://play.spotify.com/track/",
|
||||||
|
"https://open.spotify.com/track/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"APIPaths": {
|
||||||
|
"Search": "search",
|
||||||
|
"GetTrack": "tracks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Tidal",
|
||||||
|
"ClientId": "INSERT CLIENTID HERE",
|
||||||
|
"Secret": "INSERT SECRET HERE",
|
||||||
|
"Endpoints": {
|
||||||
|
"Base": "https://tidal.com",
|
||||||
|
"Api": "https://openapi.tidal.com/v2/",
|
||||||
|
"Token": "https://auth.tidal.com/v1/oauth2/token",
|
||||||
|
"MusicLink": [
|
||||||
|
"https://tidal.com/track/",
|
||||||
|
"https://tidal.com/browse/track/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"APIPaths": {
|
||||||
|
"Search": "searchresults",
|
||||||
|
"GetTrack": "tracks",
|
||||||
|
"GetArtist": "artists"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Yandex",
|
||||||
|
"ClientId": "",
|
||||||
|
"Secret": "",
|
||||||
|
"Endpoints": {
|
||||||
|
"Base": "https://music.yandex.ru",
|
||||||
|
"Api": "https://music.yandex.ru",
|
||||||
|
"Token": "https://music.yandex.ru",
|
||||||
|
"MusicLink": [
|
||||||
|
"https://music.yandex.ru/track/",
|
||||||
|
"https://music.yandex.ru/album/",
|
||||||
|
"https://music.yandex.by/track/",
|
||||||
|
"https://music.yandex.by/album/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"APIPaths": {
|
||||||
|
"Search": "handlers/suggest.jsx",
|
||||||
|
"GetTrack": "handlers/track.jsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ServicesEndpoints": {
|
||||||
|
"Redis": "redis:6379"
|
||||||
|
}
|
||||||
|
}
|
249
SWAD.API/doc.xml
Normal file
249
SWAD.API/doc.xml
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<doc>
|
||||||
|
<assembly>
|
||||||
|
<name>SWAD.API</name>
|
||||||
|
</assembly>
|
||||||
|
<members>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.MusicService.Spotify">
|
||||||
|
<summary>
|
||||||
|
Spotify Service
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.MusicService.Tidal">
|
||||||
|
<summary>
|
||||||
|
Tidal Service
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.MusicService.Yandex">
|
||||||
|
<summary>
|
||||||
|
Yandex Service
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Consts.Enums.ServiceResult">
|
||||||
|
<summary>
|
||||||
|
Service errors for controller
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.ServiceResult.Success">
|
||||||
|
<summary>
|
||||||
|
Returns on the successful executed state
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.ServiceResult.Failure">
|
||||||
|
<summary>
|
||||||
|
Returns on the unsuccessful executed state
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.ServiceResult.NoResponse">
|
||||||
|
<summary>
|
||||||
|
Returns on the unsuccessful executed state not by code
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.ServiceResult.BadRequest">
|
||||||
|
<summary>
|
||||||
|
Returns on the unsuccessful executed state by user
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:SWAD.API.Consts.Enums.ServiceResult.NotFound">
|
||||||
|
<summary>
|
||||||
|
Returns on the unsuccessful executed state by service
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Controllers.DTOs.TrackDto">
|
||||||
|
<summary>
|
||||||
|
Track directly from query
|
||||||
|
</summary>
|
||||||
|
<param Name="Name">Name of track</param>
|
||||||
|
<param Name="Artist">Artist of track</param>
|
||||||
|
<param Name="AlbumObject">AlbumObject of track</param>
|
||||||
|
<param Name="Service">Service provider</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.DTOs.TrackDto.#ctor(System.String,System.String,System.String,SWAD.API.Consts.Enums.MusicService)">
|
||||||
|
<summary>
|
||||||
|
Track directly from query
|
||||||
|
</summary>
|
||||||
|
<param Name="Name">Name of track</param>
|
||||||
|
<param Name="Artist">Artist of track</param>
|
||||||
|
<param Name="AlbumObject">AlbumObject of track</param>
|
||||||
|
<param Name="Service">Service provider</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Controllers.DTOs.TrackLinkDto">
|
||||||
|
<summary>
|
||||||
|
Track from service link
|
||||||
|
</summary>
|
||||||
|
<param Name="Link">
|
||||||
|
Spotify example: https://open.spotify.com/track/2K7xn816oNHJZ0aVqdQsha
|
||||||
|
Tidal example: https://tidal.com/track/294942856
|
||||||
|
Yandex.Music example: https://music.yandex.by/Albums/25851387/track/113810002 P.S.: Будда какая параша, даже тут
|
||||||
|
яндекс отличился
|
||||||
|
</param>
|
||||||
|
<param Name="Service">
|
||||||
|
</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.DTOs.TrackLinkDto.#ctor(System.String,SWAD.API.Consts.Enums.MusicService)">
|
||||||
|
<summary>
|
||||||
|
Track from service link
|
||||||
|
</summary>
|
||||||
|
<param Name="Link">
|
||||||
|
Spotify example: https://open.spotify.com/track/2K7xn816oNHJZ0aVqdQsha
|
||||||
|
Tidal example: https://tidal.com/track/294942856
|
||||||
|
Yandex.Music example: https://music.yandex.by/Albums/25851387/track/113810002 P.S.: Будда какая параша, даже тут
|
||||||
|
яндекс отличился
|
||||||
|
</param>
|
||||||
|
<param Name="Service">
|
||||||
|
</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Controllers.DTOs.ServiceDto">
|
||||||
|
<summary>
|
||||||
|
Service Name
|
||||||
|
</summary>
|
||||||
|
<param Name="Service">Enum MusicService</param>
|
||||||
|
<param Name="Name">MusicService.ToString</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.DTOs.ServiceDto.#ctor(SWAD.API.Consts.Enums.MusicService,System.String)">
|
||||||
|
<summary>
|
||||||
|
Service Name
|
||||||
|
</summary>
|
||||||
|
<param Name="Service">Enum MusicService</param>
|
||||||
|
<param Name="Name">MusicService.ToString</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Controllers.DTOs.LinkResultDto">
|
||||||
|
<summary>
|
||||||
|
Result of GetLink method
|
||||||
|
</summary>
|
||||||
|
<param Name="Link"></param>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.DTOs.LinkResultDto.#ctor(System.String)">
|
||||||
|
<summary>
|
||||||
|
Result of GetLink method
|
||||||
|
</summary>
|
||||||
|
<param Name="Link"></param>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Controllers.LinkController">
|
||||||
|
<summary>
|
||||||
|
Controller for get links from music providers
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.LinkController.#ctor(SWAD.API.Services.Links.LinksService)">
|
||||||
|
<summary>
|
||||||
|
Controller for get links from music providers
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.LinkController.GetLink(SWAD.API.Controllers.DTOs.TrackLinkDto)">
|
||||||
|
<summary>
|
||||||
|
Get link from other link
|
||||||
|
</summary>
|
||||||
|
<param Name="track">Track query such as Name, artist, Albums</param>
|
||||||
|
<returns>Link for track</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.LinkController.GetLink(SWAD.API.Controllers.DTOs.TrackDto)">
|
||||||
|
<summary>
|
||||||
|
Get link from search query such as artist and song Name
|
||||||
|
</summary>
|
||||||
|
<param Name="track">Track query such as Name, artist, Albums</param>
|
||||||
|
<returns>Link for track</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Controllers.LinkController.GetServiceOfLink(System.String)">
|
||||||
|
<summary>
|
||||||
|
Get service what uses that link
|
||||||
|
</summary>
|
||||||
|
<param name="link"></param>
|
||||||
|
<returns></returns>
|
||||||
|
<exception cref="T:System.ApplicationException"></exception>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Middlewares.ExceptionMiddleware">
|
||||||
|
<summary>
|
||||||
|
DONT TOUCH THAT!!! Generate good messages for responses and handle to logger exceptions
|
||||||
|
</summary>
|
||||||
|
<param Name="logger"></param>
|
||||||
|
<param Name="env"></param>
|
||||||
|
<param Name="next"></param>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Middlewares.ExceptionMiddleware.#ctor(Microsoft.Extensions.Logging.ILogger{SWAD.API.Middlewares.ExceptionMiddleware},Microsoft.AspNetCore.Hosting.IWebHostEnvironment,Microsoft.AspNetCore.Http.RequestDelegate)">
|
||||||
|
<summary>
|
||||||
|
DONT TOUCH THAT!!! Generate good messages for responses and handle to logger exceptions
|
||||||
|
</summary>
|
||||||
|
<param Name="logger"></param>
|
||||||
|
<param Name="env"></param>
|
||||||
|
<param Name="next"></param>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Models.JsonStructures.MusicAPI.Spotify.SpotifyAuthResponse">
|
||||||
|
<summary>
|
||||||
|
Json Structure for spotify auth response
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Models.JsonStructures.MusicAPI.Spotify.SpotifySearchResponse">
|
||||||
|
<summary>
|
||||||
|
Json Structure for spotify search response
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Models.JsonStructures.MusicAPI.Spotify.SpotifyTrackResponse">
|
||||||
|
<summary>
|
||||||
|
Json Structure for tidal track response
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Models.JsonStructures.MusicAPI.Tidal.TidalTrackResponse">
|
||||||
|
<summary>
|
||||||
|
Json Structure for tidal track response
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Services.Links.LinksService">
|
||||||
|
<summary>
|
||||||
|
Service for manipulating with links (LinkController)
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Services.Links.LinksService.GetLinkByQuery(SWAD.API.Controllers.DTOs.TrackDto)">
|
||||||
|
<summary>
|
||||||
|
Get link by query
|
||||||
|
</summary>
|
||||||
|
<param Name="query">Search query</param>
|
||||||
|
<returns>Success, Failure, BadRequest, NoResponse</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Services.Links.LinksService.GetServiceByLink(System.String)">
|
||||||
|
<summary>
|
||||||
|
Get MusicService by url
|
||||||
|
</summary>
|
||||||
|
<param Name="link"></param>
|
||||||
|
<returns>Success, Failure</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Services.Links.LinksService.MapLinks(SWAD.API.Controllers.DTOs.TrackLinkDto)">
|
||||||
|
<summary>
|
||||||
|
Get Link from other service
|
||||||
|
</summary>
|
||||||
|
<param Name="trackLink">link from first service</param>
|
||||||
|
<returns>link from another service, Success, Failure, BadRequest, NoResponse</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Services.MusicAPI.Api.ApiService">
|
||||||
|
<summary>
|
||||||
|
Abstract ApiService of music services
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Services.MusicAPI.Api.ApiService.GetAllImplementations">
|
||||||
|
<summary>
|
||||||
|
Get all implemented API Services
|
||||||
|
</summary>
|
||||||
|
<returns>API Services types</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Services.MusicAPI.Api.SpotifyService.GetLinkByQuery(SWAD.API.Controllers.DTOs.TrackDto)">
|
||||||
|
<summary>
|
||||||
|
Get link to spotify by search query
|
||||||
|
</summary>
|
||||||
|
<param Name="query">DTO with search query</param>
|
||||||
|
<returns>link from spotify</returns>
|
||||||
|
<exception cref="T:Microsoft.AspNetCore.Authentication.AuthenticationFailureException">If token problems</exception>
|
||||||
|
<exception cref="T:System.Net.Http.HttpRequestException">If response is bad</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:SWAD.API.Services.MusicAPI.Auth.AbstractAuthService.GetAllImplementations">
|
||||||
|
<summary>
|
||||||
|
Get all implemented Auth Services
|
||||||
|
</summary>
|
||||||
|
<returns>API Services types</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Services.MusicAPI.Auth.SpotifyAuthService">
|
||||||
|
<inheritdoc />
|
||||||
|
</member>
|
||||||
|
<member name="T:SWAD.API.Services.MusicAPI.Auth.TidalAuthService">
|
||||||
|
<inheritdoc />
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
</doc>
|
119
SWAD.API/index.html
Normal file
119
SWAD.API/index.html
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<!-- HTML for static distribution bundle build -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>SWAD.API Reference</title>
|
||||||
|
<link href="./swagger-ui.css" rel="stylesheet" type="text/css">
|
||||||
|
<link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/>
|
||||||
|
<link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: -moz-scrollbars-vertical;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
%(HeadContent)
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<!-- Workaround for https://github.com/swagger-api/swagger-editor/issues/1371 -->
|
||||||
|
<script>
|
||||||
|
console.log("Swagger loaded!");
|
||||||
|
if (window.navigator.userAgent.indexOf("Edge") > -1) {
|
||||||
|
console.log("Removing native Edge fetch in favor of swagger-ui's polyfill")
|
||||||
|
window.fetch = undefined;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="./swagger-ui-bundle.js"></script>
|
||||||
|
<script src="./swagger-ui-standalone-preset.js"></script>
|
||||||
|
<script>
|
||||||
|
/* Source: https://gist.github.com/lamberta/3768814
|
||||||
|
* Parse a string function definition and return a function object. Does not use eval.
|
||||||
|
* @param {string} str
|
||||||
|
* @return {function}
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* var f = function (x, y) { return x * y; };
|
||||||
|
* var g = parseFunction(f.toString());
|
||||||
|
* g(33, 3); //=> 99
|
||||||
|
*/
|
||||||
|
function parseFunction(str) {
|
||||||
|
if (!str) return void (0);
|
||||||
|
|
||||||
|
var fn_body_idx = str.indexOf('{'),
|
||||||
|
fn_body = str.substring(fn_body_idx + 1, str.lastIndexOf('}')),
|
||||||
|
fn_declare = str.substring(0, fn_body_idx),
|
||||||
|
fn_params = fn_declare.substring(fn_declare.indexOf('(') + 1, fn_declare.lastIndexOf(')')),
|
||||||
|
args = fn_params.split(',');
|
||||||
|
|
||||||
|
args.push(fn_body);
|
||||||
|
|
||||||
|
function Fn() {
|
||||||
|
return Function.apply(this, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
Fn.prototype = Function.prototype;
|
||||||
|
|
||||||
|
return new Fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
var configObject = JSON.parse('%(ConfigObject)');
|
||||||
|
var oauthConfigObject = JSON.parse('%(OAuthConfigObject)');
|
||||||
|
|
||||||
|
// Workaround for https://github.com/swagger-api/swagger-ui/issues/5945
|
||||||
|
configObject.urls.forEach(function (item) {
|
||||||
|
if (item.url.startsWith("http") || item.url.startsWith("/")) return;
|
||||||
|
item.url = window.location.href.replace("index.html", item.url).split('#')[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
// If validatorUrl is not explicitly provided, disable the feature by setting to null
|
||||||
|
if (!configObject.hasOwnProperty("validatorUrl"))
|
||||||
|
configObject.validatorUrl = null
|
||||||
|
|
||||||
|
// If oauth2RedirectUrl isn't specified, use the built-in default
|
||||||
|
if (!configObject.hasOwnProperty("oauth2RedirectUrl"))
|
||||||
|
configObject.oauth2RedirectUrl = (new URL("oauth2-redirect.html", window.location.href)).href;
|
||||||
|
|
||||||
|
// Apply mandatory parameters
|
||||||
|
configObject.dom_id = "#swagger-ui";
|
||||||
|
configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset];
|
||||||
|
configObject.layout = "StandaloneLayout";
|
||||||
|
|
||||||
|
// Parse and add interceptor functions
|
||||||
|
var interceptors = JSON.parse('%(Interceptors)');
|
||||||
|
if (interceptors.RequestInterceptorFunction)
|
||||||
|
configObject.requestInterceptor = parseFunction(interceptors.RequestInterceptorFunction);
|
||||||
|
if (interceptors.ResponseInterceptorFunction)
|
||||||
|
configObject.responseInterceptor = parseFunction(interceptors.ResponseInterceptorFunction);
|
||||||
|
|
||||||
|
// Begin Swagger UI call region
|
||||||
|
|
||||||
|
const ui = SwaggerUIBundle(configObject);
|
||||||
|
|
||||||
|
ui.initOAuth(oauthConfigObject);
|
||||||
|
|
||||||
|
// End Swagger UI call region
|
||||||
|
|
||||||
|
window.ui = ui
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1673
SWAD.API/wwwroot/swagger-ui/theme-flattop.css
Normal file
1673
SWAD.API/wwwroot/swagger-ui/theme-flattop.css
Normal file
File diff suppressed because it is too large
Load Diff
8
SWAD.code-workspace
Normal file
8
SWAD.code-workspace
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
16
SwapDude.Front/.editorconfig
Normal file
16
SwapDude.Front/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
42
SwapDude.Front/.gitignore
vendored
Normal file
42
SwapDude.Front/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
13
SwapDude.Front/.hintrc
Normal file
13
SwapDude.Front/.hintrc
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"development"
|
||||||
|
],
|
||||||
|
"hints": {
|
||||||
|
"axe/name-role-value": [
|
||||||
|
"default",
|
||||||
|
{
|
||||||
|
"link-name": "off"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
4
SwapDude.Front/.vscode/extensions.json
vendored
Normal file
4
SwapDude.Front/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||||
|
"recommendations": ["angular.ng-template"]
|
||||||
|
}
|
20
SwapDude.Front/.vscode/launch.json
vendored
Normal file
20
SwapDude.Front/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "ng serve",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "npm: start",
|
||||||
|
"url": "http://localhost:4200/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ng test",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "npm: test",
|
||||||
|
"url": "http://localhost:9876/debug.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
42
SwapDude.Front/.vscode/tasks.json
vendored
Normal file
42
SwapDude.Front/.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "start",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "typescript",
|
||||||
|
"pattern": "$tsc",
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": {
|
||||||
|
"regexp": "(.*?)"
|
||||||
|
},
|
||||||
|
"endsPattern": {
|
||||||
|
"regexp": "bundle generation complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "test",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "typescript",
|
||||||
|
"pattern": "$tsc",
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": {
|
||||||
|
"regexp": "(.*?)"
|
||||||
|
},
|
||||||
|
"endsPattern": {
|
||||||
|
"regexp": "bundle generation complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
27
SwapDude.Front/README.md
Normal file
27
SwapDude.Front/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# SwapDudeFront
|
||||||
|
|
||||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.3.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||||
|
|
||||||
|
## Further help
|
||||||
|
|
||||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
99
SwapDude.Front/angular.json
Normal file
99
SwapDude.Front/angular.json
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"SwapDude.Front": {
|
||||||
|
"i18n": {
|
||||||
|
"sourceLocale": "en-UK",
|
||||||
|
"locales": { "ru-RU": "src/locale/messages.ru.xlf" }
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
//"localize": ["ru-RU"],
|
||||||
|
"outputPath": "dist/swap-dude.front",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": ["zone.js"],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": ["src/favicon.ico", "src/assets"],
|
||||||
|
"styles": ["src/styles.scss"],
|
||||||
|
"scripts": [],
|
||||||
|
"server": "src/main.server.ts",
|
||||||
|
"prerender": true,
|
||||||
|
"ssr": {
|
||||||
|
"entry": "server.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "1mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "SwapDude.Front:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "SwapDude.Front:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "SwapDude.Front:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": ["zone.js", "zone.js/testing"],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": ["src/favicon.ico", "src/assets"],
|
||||||
|
"styles": ["src/styles.scss"],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": "be9d04c5-9ecd-4e58-a026-e4c863827ae9"
|
||||||
|
}
|
||||||
|
}
|
12343
SwapDude.Front/package-lock.json
generated
Normal file
12343
SwapDude.Front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
SwapDude.Front/package.json
Normal file
48
SwapDude.Front/package.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "swap-dude.front",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test",
|
||||||
|
"serve:ssr:SwapDude.Front": "node dist/swap-dude.front/server/server.mjs"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^17.1.0",
|
||||||
|
"@angular/common": "^17.1.0",
|
||||||
|
"@angular/compiler": "^17.1.0",
|
||||||
|
"@angular/core": "^17.1.0",
|
||||||
|
"@angular/forms": "^17.1.0",
|
||||||
|
"@angular/platform-browser": "^17.1.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^17.1.0",
|
||||||
|
"@angular/platform-server": "^17.1.0",
|
||||||
|
"@angular/router": "^17.1.0",
|
||||||
|
"@angular/ssr": "^17.1.3",
|
||||||
|
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"bootstrap": "^5.3.2",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.14.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^17.1.3",
|
||||||
|
"@angular/cli": "^17.1.3",
|
||||||
|
"@angular/compiler-cli": "^17.1.0",
|
||||||
|
"@angular/localize": "^17.1.0",
|
||||||
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@types/node": "^18.18.0",
|
||||||
|
"jasmine-core": "~5.1.0",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"typescript": "~5.3.2"
|
||||||
|
}
|
||||||
|
}
|
56
SwapDude.Front/server.ts
Normal file
56
SwapDude.Front/server.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { CommonEngine } from '@angular/ssr';
|
||||||
|
import express from 'express';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, join, resolve } from 'node:path';
|
||||||
|
import bootstrap from './src/main.server';
|
||||||
|
|
||||||
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
|
export function app(): express.Express {
|
||||||
|
const server = express();
|
||||||
|
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
||||||
|
const indexHtml = join(serverDistFolder, 'index.server.html');
|
||||||
|
|
||||||
|
const commonEngine = new CommonEngine();
|
||||||
|
|
||||||
|
server.set('view engine', 'html');
|
||||||
|
server.set('views', browserDistFolder);
|
||||||
|
|
||||||
|
// Example Express Rest API endpoints
|
||||||
|
// server.get('/api/**', (req, res) => { });
|
||||||
|
// Serve static files from /browser
|
||||||
|
server.get('*.*', express.static(browserDistFolder, {
|
||||||
|
maxAge: '1y'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// All regular routes use the Angular engine
|
||||||
|
server.get('*', (req, res, next) => {
|
||||||
|
const { protocol, originalUrl, baseUrl, headers } = req;
|
||||||
|
|
||||||
|
commonEngine
|
||||||
|
.render({
|
||||||
|
bootstrap,
|
||||||
|
documentFilePath: indexHtml,
|
||||||
|
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||||
|
publicPath: browserDistFolder,
|
||||||
|
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
|
||||||
|
})
|
||||||
|
.then((html) => res.send(html))
|
||||||
|
.catch((err) => next(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(): void {
|
||||||
|
const port = process.env['PORT'] || 4000;
|
||||||
|
|
||||||
|
// Start up the Node server
|
||||||
|
const server = app();
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`Node Express server listening on http://localhost:${port}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
2
SwapDude.Front/src/app/app.component.html
Normal file
2
SwapDude.Front/src/app/app.component.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<app-home></app-home>
|
||||||
|
<router-outlet />
|
0
SwapDude.Front/src/app/app.component.scss
Normal file
0
SwapDude.Front/src/app/app.component.scss
Normal file
29
SwapDude.Front/src/app/app.component.spec.ts
Normal file
29
SwapDude.Front/src/app/app.component.spec.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AppComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have the 'SwapDude.Front' title`, () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app.title).toEqual('SwapDude.Front');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement as HTMLElement;
|
||||||
|
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, SwapDude.Front');
|
||||||
|
});
|
||||||
|
});
|
14
SwapDude.Front/src/app/app.component.ts
Normal file
14
SwapDude.Front/src/app/app.component.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import { HomeComponent } from "./pages/home/home.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrl: './app.component.scss',
|
||||||
|
imports: [RouterOutlet, HomeComponent]
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
title = 'SwapDude.Front';
|
||||||
|
}
|
11
SwapDude.Front/src/app/app.config.server.ts
Normal file
11
SwapDude.Front/src/app/app.config.server.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideServerRendering } from '@angular/platform-server';
|
||||||
|
import { appConfig } from './app.config';
|
||||||
|
|
||||||
|
const serverConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideServerRendering()
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
9
SwapDude.Front/src/app/app.config.ts
Normal file
9
SwapDude.Front/src/app/app.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
|
import { routes } from './app.routes';
|
||||||
|
import { provideClientHydration } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [provideRouter(routes), provideClientHydration()]
|
||||||
|
};
|
3
SwapDude.Front/src/app/app.routes.ts
Normal file
3
SwapDude.Front/src/app/app.routes.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export const routes: Routes = [];
|
4
SwapDude.Front/src/app/pages/home/home.component.html
Normal file
4
SwapDude.Front/src/app/pages/home/home.component.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<div class="container text-center">
|
||||||
|
<h1><a href="{{ domain }}">SwapDude.pro</a></h1>
|
||||||
|
<h3 i18n>Get links for your friends now so easy!</h3>
|
||||||
|
</div>
|
23
SwapDude.Front/src/app/pages/home/home.component.spec.ts
Normal file
23
SwapDude.Front/src/app/pages/home/home.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HomeComponent } from './home.component';
|
||||||
|
|
||||||
|
describe('HomeComponent', () => {
|
||||||
|
let component: HomeComponent;
|
||||||
|
let fixture: ComponentFixture<HomeComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [HomeComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(HomeComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
18
SwapDude.Front/src/app/pages/home/home.component.ts
Normal file
18
SwapDude.Front/src/app/pages/home/home.component.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { DOCUMENT } from '@angular/common';
|
||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
standalone: true,
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrl: './home.component.scss'
|
||||||
|
})
|
||||||
|
export class HomeComponent {
|
||||||
|
domain: string
|
||||||
|
title = "SwapDude.pro - Home"
|
||||||
|
constructor(@Inject(DOCUMENT) private document: Document) { }
|
||||||
|
ngOnInit(){
|
||||||
|
this.domain = this.document.location.hostname;
|
||||||
|
}
|
||||||
|
}
|
0
SwapDude.Front/src/assets/.gitkeep
Normal file
0
SwapDude.Front/src/assets/.gitkeep
Normal file
BIN
SwapDude.Front/src/favicon.ico
Normal file
BIN
SwapDude.Front/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
13
SwapDude.Front/src/index.html
Normal file
13
SwapDude.Front/src/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>SwapDudeFront</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
SwapDude.Front/src/locale/messages.ru.xlf
Normal file
14
SwapDude.Front/src/locale/messages.ru.xlf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file source-language="en-UK" datatype="plaintext" original="ng2.template">
|
||||||
|
<body>
|
||||||
|
<trans-unit id="899810183811423241" datatype="html">
|
||||||
|
<source>Делится ссылками на музыку с друзьями теперь ТАК просто!</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/pages/home/home.component.html</context>
|
||||||
|
<context context-type="linenumber">3,4</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
14
SwapDude.Front/src/locale/messages.xlf
Normal file
14
SwapDude.Front/src/locale/messages.xlf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file source-language="en-UK" datatype="plaintext" original="ng2.template">
|
||||||
|
<body>
|
||||||
|
<trans-unit id="899810183811423241" datatype="html">
|
||||||
|
<source>Get links for your friends now easy!</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/pages/home/home.component.html</context>
|
||||||
|
<context context-type="linenumber">3,4</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
7
SwapDude.Front/src/main.server.ts
Normal file
7
SwapDude.Front/src/main.server.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { config } from './app/app.config.server';
|
||||||
|
|
||||||
|
const bootstrap = () => bootstrapApplication(AppComponent, config);
|
||||||
|
|
||||||
|
export default bootstrap;
|
8
SwapDude.Front/src/main.ts
Normal file
8
SwapDude.Front/src/main.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="@angular/localize" />
|
||||||
|
|
||||||
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, appConfig)
|
||||||
|
.catch((err) => console.error(err));
|
4
SwapDude.Front/src/styles.scss
Normal file
4
SwapDude.Front/src/styles.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
|
||||||
|
/* Importing Bootstrap SCSS file. */
|
||||||
|
@import 'bootstrap/scss/bootstrap';
|
19
SwapDude.Front/tsconfig.app.json
Normal file
19
SwapDude.Front/tsconfig.app.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
"@angular/localize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.ts",
|
||||||
|
"src/main.server.ts",
|
||||||
|
"server.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
34
SwapDude.Front/tsconfig.json
Normal file
34
SwapDude.Front/tsconfig.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
15
SwapDude.Front/tsconfig.spec.json
Normal file
15
SwapDude.Front/tsconfig.spec.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"@angular/localize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
366
TelegramBot/.gitignore
vendored
Normal file
366
TelegramBot/.gitignore
vendored
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# DocFx
|
||||||
|
/.vscode/
|
||||||
|
log/
|
||||||
|
obj/
|
||||||
|
_site/
|
||||||
|
.optemp/
|
||||||
|
_themes/
|
||||||
|
_themes.MSDN.Modern/
|
||||||
|
_themes.VS.Modern/
|
||||||
|
|
||||||
|
.openpublishing.buildcore.ps1
|
||||||
|
.openpublishing.redirection.sorted.json
|
||||||
|
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Folder configuration on Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Custom added by ghogen
|
||||||
|
settings.json
|
||||||
|
telegramconfig.local.json
|
61
TelegramBot/BotHandler.cs
Normal file
61
TelegramBot/BotHandler.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
using Telegram.Bot.Types.Enums;
|
||||||
|
using TelegramBot.Commands;
|
||||||
|
using TelegramBot.Commands.CommandMessages;
|
||||||
|
|
||||||
|
namespace TelegramBot;
|
||||||
|
|
||||||
|
public static class BotHandler
|
||||||
|
{
|
||||||
|
private static readonly LinkMessage LinkMessageProvider = new();
|
||||||
|
private static readonly StartCommandMessage StartCommandMessageProvider = new();
|
||||||
|
private static readonly ChangelogCommandMessage ChangelogCommandMessageProvider = new();
|
||||||
|
private static readonly ErrorMessage ErrorMessageProvider = new();
|
||||||
|
public static async Task HandleUpdateAsync(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
Update update,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
switch (update.Type)
|
||||||
|
{
|
||||||
|
case UpdateType.Message:
|
||||||
|
switch (update.Message?.Text)
|
||||||
|
{
|
||||||
|
case StartCommandMessage.CommandName:
|
||||||
|
await StartCommandMessageProvider.InvokeFromMessage(botClient, update, cancellationToken);
|
||||||
|
break;
|
||||||
|
case ChangelogCommandMessage.CommandName:
|
||||||
|
await ChangelogCommandMessageProvider.InvokeFromMessage(botClient, update, cancellationToken);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Console.WriteLine(update.Message?.Text);
|
||||||
|
if (string.IsNullOrEmpty(update.Message?.Text))
|
||||||
|
{
|
||||||
|
await ErrorMessageProvider.InvokeFromMessage(botClient, update, cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await LinkMessageProvider.InvokeFromMessage(botClient, update, cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UpdateType.InlineQuery:
|
||||||
|
await LinkMessageProvider.InvokeFromInlineQuery(botClient, update, cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task HandleErrorAsync(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
Exception exception,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
Console.WriteLine("Ошибка ядра:\n--------------------------");
|
||||||
|
Console.WriteLine(exception);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
// throw exception;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
using Telegram.Bot.Types.Enums;
|
||||||
|
|
||||||
|
namespace TelegramBot.Commands.CommandMessages;
|
||||||
|
|
||||||
|
public class ChangelogCommandMessage : CommandMessage
|
||||||
|
{
|
||||||
|
public const string CommandName = "/changelog";
|
||||||
|
public override async Task InvokeFromMessage(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var text = new StringBuilder();
|
||||||
|
foreach (var log in Program.Config.Changelog)
|
||||||
|
{
|
||||||
|
text.Append($"Нововведения от <b>{log.Date}</b>:\n\n{log.Caption}\n\n");
|
||||||
|
}
|
||||||
|
await botClient.SendTextMessageAsync(update.Message?.Chat!, text.ToString(), cancellationToken: cancellationToken, parseMode: ParseMode.Html);
|
||||||
|
}
|
||||||
|
}
|
12
TelegramBot/Commands/CommandMessages/CommandMessage.cs
Normal file
12
TelegramBot/Commands/CommandMessages/CommandMessage.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace TelegramBot.Commands.CommandMessages;
|
||||||
|
|
||||||
|
public abstract class CommandMessage : IMessage
|
||||||
|
{
|
||||||
|
public abstract Task InvokeFromMessage(ITelegramBotClient botClient, Update update,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
}
|
20
TelegramBot/Commands/CommandMessages/StartCommandMessage.cs
Normal file
20
TelegramBot/Commands/CommandMessages/StartCommandMessage.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace TelegramBot.Commands.CommandMessages;
|
||||||
|
|
||||||
|
public class StartCommandMessage : CommandMessage
|
||||||
|
{
|
||||||
|
public const string CommandName = "/start";
|
||||||
|
public override async Task InvokeFromMessage(ITelegramBotClient botClient,
|
||||||
|
Update update,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var text = Program.Config.StartMessage;
|
||||||
|
text += $"\nBuild assembly version: {FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion}";
|
||||||
|
text += $"\n\nНововведения по комманде /changelog";
|
||||||
|
await botClient.SendTextMessageAsync(update.Message?.Chat!, text, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
12
TelegramBot/Commands/ErrorMessage.cs
Normal file
12
TelegramBot/Commands/ErrorMessage.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace TelegramBot.Commands;
|
||||||
|
|
||||||
|
public class ErrorMessage : IMessage
|
||||||
|
{
|
||||||
|
public async Task InvokeFromMessage(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await botClient.SendTextMessageAsync(update.Message?.Chat!, "Ошибка! Команда не найдена", cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
11
TelegramBot/Commands/IMessage.cs
Normal file
11
TelegramBot/Commands/IMessage.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace TelegramBot.Commands;
|
||||||
|
|
||||||
|
public interface IMessage
|
||||||
|
{
|
||||||
|
public Task InvokeFromMessage(ITelegramBotClient botClient,
|
||||||
|
Update update,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
}
|
98
TelegramBot/Commands/LinkMessage.cs
Normal file
98
TelegramBot/Commands/LinkMessage.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
using Telegram.Bot.Types.InlineQueryResults;
|
||||||
|
using TelegramBot.DTOs;
|
||||||
|
|
||||||
|
namespace TelegramBot.Commands;
|
||||||
|
|
||||||
|
public class LinkMessage : IMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create message from API
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="botClient"></param>
|
||||||
|
/// <param name="inputUrl"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
async Task<(string? message, string? preview)> GetMessage(ITelegramBotClient botClient, string inputUrl,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new HttpClient();
|
||||||
|
var res = await client.GetAsync($"{Program.Config.ApiPath}/link/getService?link=" + inputUrl,
|
||||||
|
cancellationToken);
|
||||||
|
var typeRes = await res.Content.ReadFromJsonAsync<GetLink>(cancellationToken: cancellationToken);
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return (string.Empty, $"Произошла ошибка при обработке запроса\n{res.StatusCode.ToString()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new StringBuilder();
|
||||||
|
var preview = new StringBuilder();
|
||||||
|
foreach (var service in typeRes?.service.Another()!)
|
||||||
|
{
|
||||||
|
res = await client.PostAsJsonAsync($"{Program.Config.ApiPath}/link/fromLink", new
|
||||||
|
{
|
||||||
|
link = inputUrl,
|
||||||
|
service,
|
||||||
|
}, cancellationToken: cancellationToken);
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
preview.AppendLine($"❌ {service}\n");
|
||||||
|
message.AppendLine(
|
||||||
|
$"❌ {service}: 🤨👨🏿🦰😡😮💨😰 {(await res.Content.ReadFromJsonAsync<ErrorDto>(cancellationToken: cancellationToken))?.title}");
|
||||||
|
message.AppendLine();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiRes = await res.Content.ReadFromJsonAsync<LinkDto>(cancellationToken: cancellationToken);
|
||||||
|
preview.AppendLine($"✅ {service}\n");
|
||||||
|
message.AppendLine($"✅ {service} - {apiRes?.link}");
|
||||||
|
message.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (message.ToString(), preview.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Ошибка:\n--------------------------");
|
||||||
|
Console.WriteLine(e);
|
||||||
|
return (e.ToString(), "Произошла ошибка при обработке запроса\nдетали в сообщении");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeFromMessage(ITelegramBotClient botClient, Update update,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var result = (await GetMessage(botClient, update.Message?.Text!, cancellationToken)).message;
|
||||||
|
await botClient.SendTextMessageAsync(update.Message?.Chat!, string.IsNullOrEmpty(result) ? "Ошибка🤔:\nНе удалось определить сервис исходной ссылки.\nДоступные сервисы для конвертации:\nTidal\nYandex\nSpotify" : result, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task InvokeFromInlineQuery(ITelegramBotClient botClient, Update update,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrEmpty(update.InlineQuery!.Query))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var message = await new LinkMessage().GetMessage(botClient, update.InlineQuery!.Query, cancellationToken);
|
||||||
|
if(message.message != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await botClient.AnswerInlineQueryAsync(update.InlineQuery.Id,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new InlineQueryResultArticle("0", message.preview!,
|
||||||
|
new InputTextMessageContent(message.message))
|
||||||
|
}, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to send answer to {update.Message?.Chat.Username}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
15
TelegramBot/Config/AppSettings.cs
Normal file
15
TelegramBot/Config/AppSettings.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace TelegramBot.Config;
|
||||||
|
|
||||||
|
public class AppSettings
|
||||||
|
{
|
||||||
|
public string? Token { get; init; }
|
||||||
|
public string? ApiPath { get; init; }
|
||||||
|
public string? StartMessage { get; init; }
|
||||||
|
public List<Changelog> Changelog { get; init; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Changelog
|
||||||
|
{
|
||||||
|
public string? Date { get; init; }
|
||||||
|
public string? Caption { get; init; }
|
||||||
|
}
|
15
TelegramBot/DTOs/GetLink.cs
Normal file
15
TelegramBot/DTOs/GetLink.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
|
||||||
|
namespace TelegramBot.DTOs;
|
||||||
|
|
||||||
|
public record GetLink(
|
||||||
|
MusicService service
|
||||||
|
);
|
||||||
|
|
||||||
|
public record LinkDto(
|
||||||
|
string? link
|
||||||
|
);
|
||||||
|
|
||||||
|
public record ErrorDto(
|
||||||
|
string title
|
||||||
|
);
|
15
TelegramBot/Dockerfile
Normal file
15
TelegramBot/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# https://hub.docker.com/_/microsoft-dotnet
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
WORKDIR /source
|
||||||
|
|
||||||
|
# copy csproj and restore as distinct layers
|
||||||
|
COPY ./ ./
|
||||||
|
RUN dotnet restore
|
||||||
|
WORKDIR /source/TelegramBot
|
||||||
|
RUN dotnet publish -c release -o /app --no-restore
|
||||||
|
|
||||||
|
# final stage/image
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app ./
|
||||||
|
ENTRYPOINT ["dotnet", "/app/TelegramBot.dll"]
|
13
TelegramBot/EnumExtensions.cs
Normal file
13
TelegramBot/EnumExtensions.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using SWAD.API.Consts.Enums;
|
||||||
|
|
||||||
|
namespace TelegramBot;
|
||||||
|
|
||||||
|
public static class EnumExtensions
|
||||||
|
{
|
||||||
|
public static List<MusicService> Another(this MusicService service)
|
||||||
|
{
|
||||||
|
var list = Enum.GetValues<MusicService>().ToList();
|
||||||
|
list.Remove(service);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
71
TelegramBot/Program.cs
Normal file
71
TelegramBot/Program.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Polling;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using TelegramBot.Config;
|
||||||
|
|
||||||
|
namespace TelegramBot;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
private static ILogger Logger { get; } = LoggerFactory.Create(x => { x.AddConsole(); })
|
||||||
|
.CreateLogger("MainThread");
|
||||||
|
|
||||||
|
private static ITelegramBotClient bot { get; set; }
|
||||||
|
|
||||||
|
public static AppSettings Config { get; private set; }
|
||||||
|
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
Splash(Logger);
|
||||||
|
LoadConfig();
|
||||||
|
var cts = LoadBot();
|
||||||
|
while(!cts.IsCancellationRequested)
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Splash(ILogger logger)
|
||||||
|
{
|
||||||
|
var splash = """
|
||||||
|
____ ____ _ ____ _
|
||||||
|
/ ___|_ ____ _ _ __ | _ \ _ _ __| | ___ | __ ) ___ | |_
|
||||||
|
\___ \ \ /\ / / _` | '_ \| | | | | | |/ _` |/ _ \ | _ \ / _ \| __|
|
||||||
|
___) \ V V / (_| | |_) | |_| | |_| | (_| | __/_| |_) | (_) | |_
|
||||||
|
|____/ \_/\_/ \__,_| .__/|____/ \__,_|\__,_|\___(_)____/ \___/ \__|
|
||||||
|
|_|
|
||||||
|
""";
|
||||||
|
logger.LogWarning(splash);
|
||||||
|
logger.LogInformation(
|
||||||
|
$"Build assembly version: {FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LoadConfig()
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Loading config...");
|
||||||
|
IConfigurationRoot configuration = new ConfigurationBuilder()
|
||||||
|
.AddJsonFile("telegramconfig.json", optional: false)
|
||||||
|
#if DEBUG
|
||||||
|
.AddJsonFile("telegramconfig.local.json", true, true)
|
||||||
|
#endif
|
||||||
|
.Build();
|
||||||
|
Config = configuration.GetSection("AppSettings").Get<AppSettings>() ?? throw new NullReferenceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CancellationTokenSource LoadBot()
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Creating bot...");
|
||||||
|
|
||||||
|
bot = new TelegramBotClient(Config.Token ?? throw new NullReferenceException());
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
var receiverOptions = new ReceiverOptions
|
||||||
|
{
|
||||||
|
AllowedUpdates = { }
|
||||||
|
};
|
||||||
|
bot.StartReceiving(BotHandler.HandleUpdateAsync,
|
||||||
|
BotHandler.HandleErrorAsync, receiverOptions, cts.Token);
|
||||||
|
|
||||||
|
return cts;
|
||||||
|
}
|
||||||
|
}
|
47
TelegramBot/TelegramBot.csproj
Normal file
47
TelegramBot/TelegramBot.csproj
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<VersionSuffix>0.1.$([System.DateTime]::UtcNow.ToString(MMdd)).$([System.DateTime]::Now.ToString(HHmm))</VersionSuffix>
|
||||||
|
<AssemblyVersion Condition=" '$(VersionSuffix)' == '' ">0.0.0.1</AssemblyVersion>
|
||||||
|
<AssemblyVersion Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>
|
||||||
|
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.1.0</Version>
|
||||||
|
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
|
||||||
|
<Company>SpectruMTeamCode</Company>
|
||||||
|
<Authors>Lisoveliy, Sluppy(Gl3b4ty)</Authors>
|
||||||
|
<Copyright>Copyright © $(Company) $([System.DateTime]::UtcNow.ToString(yyyy))</Copyright>
|
||||||
|
<Product>SWAD Platform</Product>
|
||||||
|
<Description>Platform for sharing music links from one music service to another for free! (REST API Back-end)</Description>
|
||||||
|
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Telegram.Bot" Version="19.0.0" />
|
||||||
|
<PackageReference Include="Telegram.Bots.Extensions.Polling" Version="5.9.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SWAD.API\SWAD.API.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="telegramconfig.json" />
|
||||||
|
<Content Include="telegramconfig.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Update="telegramconfig.local.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<None Remove="telegramconfig.local.json" />
|
||||||
|
<Content Include="telegramconfig.local.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user