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
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# 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
|
||||
|
||||
################################################################################
|
||||
# Данный GITIGNORE-файл был автоматически создан Microsoft(R) Visual Studio.
|
||||
################################################################################
|
||||
|
||||
/SWAD.API/appsettings.local.json
|
||||
/SWAD.API/appsettings.Development.json
|
||||
.vs
|
||||
.idea
|
||||
|
||||
#Rider sucks
|
||||
.idea/.idea.SWAD.API/.idea/workspace.xml
|
||||
SWAD.Front/node_modules
|
||||
TelegramBot/obj
|
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
|
||||
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