├── .gitignore └── NineSong ├── internal ├── tag_util │ └── tag_util.go └── internal_system │ ├── fake_util │ └── fake_util.go │ └── token_util │ └── token_util.go ├── usecase ├── usecase_file_entity │ └── scene_audio │ │ └── scene_audio_util │ │ ├── usecase_db_audio_acoustid.go │ │ ├── usecase_db_audio_go-mp4.go │ │ ├── usecase_db_audio_libsox.go │ │ ├── usecase_db_audio_taglib_cgo.go │ │ ├── usecase_db_audio_go-audio-aiff.go │ │ ├── usecase_db_audio_go-audio-audio.go │ │ ├── usecase_db_audio_go-audio-midi.go │ │ ├── usecase_db_audio_go-audio-music.go │ │ ├── usecase_db_audio_go-audio-riff.go │ │ ├── usecase_db_audio_go-audio-wav.go │ │ ├── usecase_db_audio_music_brainz.go │ │ ├── usecase_db_audio_go-audio-audiotools.go │ │ ├── usecase_db_audio_go-audio-generator.go │ │ ├── usecase_db_audio_go-audio-transforms.go │ │ └── usecase_db_audio_go-audio-chunk.go ├── usecase_auth │ ├── profile_usecase.go │ ├── task_usecase.go │ ├── login_usecase.go │ ├── refresh_token_usecase.go │ ├── signup_usecase.go │ ├── task_usecase_test.go │ └── update_user_usecase.go ├── usecase_system │ ├── system_info_usecase.go │ └── system_configuration_usecase.go └── usecase_app │ ├── usecase_app_config │ ├── app_config_usercase.go │ ├── ui_config_usercase.go │ ├── server_config_usercase.go │ ├── audio_config_usercase.go │ ├── library_config_usercase.go │ └── playlist_id_config_usercase.go │ └── usecase_app_library │ └── media_file_library_usercase.go ├── api ├── route │ ├── route_file_entity │ │ ├── scene_audio_route_api_route │ │ │ ├── scene_audio_musicbrainz_route.go │ │ │ ├── scene_audio_home_route.go │ │ │ ├── scene_audio_album_route.go │ │ │ ├── scene_audio_retrieval_route.go │ │ │ ├── scene_audio_annotation_route.go │ │ │ ├── scene_audio_playlist_route.go │ │ │ ├── scene_audio_artist_route.go │ │ │ ├── scene_audio_media_file_cue_route.go │ │ │ ├── scene_audio_media_file_route.go │ │ │ ├── scene_audio_playlist_track_route.go │ │ │ ├── scene_audio_word_cloud_route.go │ │ │ └── scene_audio_recommend_route.go │ │ └── scene_audio_db_api_route │ │ │ └── folder_entity_route.go │ ├── route_auth │ │ ├── profile_route.go │ │ ├── task_route.go │ │ ├── login_route.go │ │ ├── signup_route.go │ │ ├── refresh_token_route.go │ │ └── user_update_route.go │ ├── route_system │ │ ├── system_info_route.go │ │ └── system_configuration_route.go │ └── route_app │ │ ├── route_app_config │ │ ├── app_config_route.go │ │ ├── ui_config_route.go │ │ ├── audio_config_route.go │ │ ├── library_config_route.go │ │ ├── playlist_id_config_route.go │ │ └── server_config_route.go │ │ └── route_app_library │ │ └── media_file_library_route.go ├── controller │ ├── controller_file_entity │ │ └── scene_audio_route_api_controller │ │ │ ├── scene_audio_musicbrainz_controller.go │ │ │ └── scene_audio_home_controller.go │ ├── controller_auth │ │ ├── profile_controller.go │ │ ├── task_controller.go │ │ ├── login_controller.go │ │ ├── refresh_token_controller.go │ │ └── signup_controller.go │ ├── response_handler.go │ ├── controller_system │ │ ├── system_info_controller.go │ │ └── system_configuration_controller.go │ └── controller_app │ │ ├── controller_app_config │ │ ├── app_config_controller.go │ │ ├── ui_config_controller.go │ │ ├── audio_config_controller.go │ │ ├── library_config_controller.go │ │ └── playlist_id_config_controller.go │ │ └── controller_app_library │ │ └── media_file_library_controller.go └── middleware │ └── middleware_system │ └── jwt_auth_middleware.go ├── domain ├── domain_file_entity │ ├── scene_audio │ │ ├── scene_audio_db │ │ │ ├── scene_audio_db_models │ │ │ │ ├── model_musicbrainz.go │ │ │ │ ├── model_playlist_track.go │ │ │ │ ├── model_lyrics_file.go │ │ │ │ ├── model_external_resource.go │ │ │ │ ├── model_media_mv.go │ │ │ │ ├── model_media_lyrics.go │ │ │ │ ├── model_word_cloud.go │ │ │ │ ├── model_playlist.go │ │ │ │ ├── utils_media_file_metadata.go │ │ │ │ ├── model_media_track.go │ │ │ │ ├── model_recommend.go │ │ │ │ └── model_annotation.go │ │ │ ├── scene_audio_db_interface │ │ │ │ ├── interface_musicbrainz.go │ │ │ │ ├── interface_temp_metadata.go │ │ │ │ ├── interface_lyrics_file.go │ │ │ │ ├── interface_word_cloud.go │ │ │ │ ├── interface_playlist.go │ │ │ │ ├── interface_home.go │ │ │ │ ├── interface_playlist_track.go │ │ │ │ ├── interface_media_file_cue.go │ │ │ │ ├── interface_artist.go │ │ │ │ └── interface_media_file.go │ │ │ └── scene_audio_utils_models │ │ │ │ ├── model_media_sound_effects.go │ │ │ │ ├── model_media_music-theory.go │ │ │ │ ├── model_media_transforms.go │ │ │ │ └── model_media_generator.go │ │ └── scene_audio_route │ │ │ ├── scene_audio_route_models │ │ │ ├── model_playlist_track.go │ │ │ ├── model_playlist.go │ │ │ ├── model_retrieval.go │ │ │ ├── model_album.go │ │ │ ├── model_artist.go │ │ │ └── model_media_file.go │ │ │ └── scene_audio_route_interface │ │ │ ├── interface_retrieval.go │ │ │ ├── interface_recommend.go │ │ │ ├── interface_word_cloud.go │ │ │ ├── interface_home.go │ │ │ ├── interface_annotation.go │ │ │ ├── interface_playlist.go │ │ │ ├── interface_album.go │ │ │ ├── interface_artist.go │ │ │ ├── interface_media_file_cue.go │ │ │ ├── interface_playlist_track.go │ │ │ └── interface_media_file.go │ └── file_entity_domain_basic_folder.go ├── success_response.go ├── domain_resource │ └── stopwords.go ├── domain_util │ ├── sort_order.go │ ├── time_stamp.go │ ├── query_options.go │ ├── task_progress.go │ └── min_heap.go ├── domain_auth │ ├── profile.go │ ├── jwt_custom.go │ ├── user.go │ ├── login.go │ ├── task.go │ ├── refresh_token.go │ ├── signup.go │ └── user_update.go ├── error_response.go ├── domain_app │ └── domain_app_config │ │ ├── app_config.go │ │ ├── ui_config.go │ │ ├── audio_config.go │ │ ├── library_config.go │ │ ├── playlist_id_config.go │ │ └── server_config.go └── mocks │ ├── ProfileUsecase.go │ ├── TaskUsecase.go │ └── TaskRepository.go ├── assets ├── button-view-api-docs.png ├── go-backend-arch-diagram.png ├── go-backend-clean-architecture.png ├── go-arch-private-api-request-flow.png └── go-arch-public-api-request-flow.png ├── repository ├── repository_file_entity │ └── scene_audio │ │ └── scene_audio_db_repository │ │ ├── repository_db_musicbrainz.go │ │ ├── repository_db_external_resource.go │ │ └── repository_db_lyrics_file.go ├── repository_system │ ├── system_info_repository.go │ └── system_configuration_repository.go ├── repository_app │ ├── repository_app_config │ │ ├── server_config_repository.go │ │ ├── ui_config_repository.go │ │ ├── app_config_repository.go │ │ ├── audio_config_repository.go │ │ ├── library_config_repository.go │ │ └── playlist_id_config_repository.go │ └── repository_app_library │ │ └── media_file_library_repository.go └── repository_auth │ ├── task_repository.go │ ├── user_update_repository.go │ ├── user_repository.go │ └── user_repository_test.go ├── docker-compose-test-srs.yaml ├── .env.example ├── docker-compose-local-mongo.yaml ├── docker-compose-test-mediamtx.yaml ├── bootstrap ├── app.go ├── database.go └── env.go ├── docker-compose-local-mongo-macos.yaml ├── docker-compose-local-develop-init-windows.ps1 ├── srs_audio.conf ├── cmd └── main.go ├── qodana.yaml ├── mongo └── mocks │ ├── SingleResult.go │ ├── Database.go │ └── Cursor.go ├── .air.toml ├── docker-compose-open.yaml ├── docker-compose-local-develop-init.yaml ├── docker-compose.yaml ├── test_mediamtx_ffmepg.example ├── test_srs.example └── Dockerfile /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .VSCodeCounter 3 | Ninesong/tmp 4 | -------------------------------------------------------------------------------- /NineSong/internal/tag_util/tag_util.go: -------------------------------------------------------------------------------- 1 | package tag_util 2 | -------------------------------------------------------------------------------- /NineSong/internal/internal_system/fake_util/fake_util.go: -------------------------------------------------------------------------------- 1 | package fake_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_acoustid.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-mp4.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_libsox.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_taglib_cgo.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-aiff.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-audio.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-midi.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-music.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-riff.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-wav.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_music_brainz.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_musicbrainz_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_musicbrainz.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | -------------------------------------------------------------------------------- /NineSong/domain/success_response.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | type SuccessResponse struct { 4 | Message string `json:"message"` 5 | } 6 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-audiotools.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-generator.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_file_entity/scene_audio/scene_audio_util/usecase_db_audio_go-audio-transforms.go: -------------------------------------------------------------------------------- 1 | package usercase_audio_util 2 | -------------------------------------------------------------------------------- /NineSong/assets/button-view-api-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Badmen-Viper/NineSong/HEAD/NineSong/assets/button-view-api-docs.png -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_musicbrainz.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | -------------------------------------------------------------------------------- /NineSong/repository/repository_file_entity/scene_audio/scene_audio_db_repository/repository_db_musicbrainz.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_repository 2 | -------------------------------------------------------------------------------- /NineSong/assets/go-backend-arch-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Badmen-Viper/NineSong/HEAD/NineSong/assets/go-backend-arch-diagram.png -------------------------------------------------------------------------------- /NineSong/api/controller/controller_file_entity/scene_audio_route_api_controller/scene_audio_musicbrainz_controller.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_controller 2 | -------------------------------------------------------------------------------- /NineSong/assets/go-backend-clean-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Badmen-Viper/NineSong/HEAD/NineSong/assets/go-backend-clean-architecture.png -------------------------------------------------------------------------------- /NineSong/domain/domain_resource/stopwords.go: -------------------------------------------------------------------------------- 1 | package domain_resource 2 | 3 | import "embed" 4 | 5 | //go:embed stopwords/*.txt 6 | var StopWordsFS embed.FS 7 | -------------------------------------------------------------------------------- /NineSong/assets/go-arch-private-api-request-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Badmen-Viper/NineSong/HEAD/NineSong/assets/go-arch-private-api-request-flow.png -------------------------------------------------------------------------------- /NineSong/assets/go-arch-public-api-request-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Badmen-Viper/NineSong/HEAD/NineSong/assets/go-arch-public-api-request-flow.png -------------------------------------------------------------------------------- /NineSong/domain/domain_util/sort_order.go: -------------------------------------------------------------------------------- 1 | package domain_util 2 | 3 | type SortOrder struct { 4 | Sort string `bson:"sort" json:"sort"` // 排序字段 5 | Order string `bson:"order" json:"order"` // 排序方式(asc 或 desc) 6 | } 7 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_utils_models/model_media_sound_effects.go: -------------------------------------------------------------------------------- 1 | package scene_audio_utils_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type MediaSoundEffectsMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` 7 | } 8 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/profile.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import "context" 4 | 5 | type Profile struct { 6 | Name string `json:"name"` 7 | Email string `json:"email"` 8 | } 9 | 10 | type ProfileUsecase interface { 11 | GetProfileByID(c context.Context, userID string) (*Profile, error) 12 | } 13 | -------------------------------------------------------------------------------- /NineSong/docker-compose-test-srs.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | mediamtx: 5 | image: ossrs/srs:5 6 | container_name: mediamtx-test 7 | restart: unless-stopped 8 | ports: 9 | - "1935:1935" 10 | - "1985:1985" 11 | - "8080:8888" 12 | - "8000:8000/udp" 13 | - "10080:10080/udp" -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_utils_models/model_media_music-theory.go: -------------------------------------------------------------------------------- 1 | package scene_audio_utils_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | // github.com/go-audio/music 6 | type MediaMusicTheoryMetadata struct { 7 | ID primitive.ObjectID `bson:"_id"` 8 | } 9 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_utils_models/model_media_transforms.go: -------------------------------------------------------------------------------- 1 | package scene_audio_utils_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | // github.com/go-audio/transforms 6 | type MediaTransformsMetadata struct { 7 | ID primitive.ObjectID `bson:"_id"` 8 | } 9 | -------------------------------------------------------------------------------- /NineSong/domain/domain_util/time_stamp.go: -------------------------------------------------------------------------------- 1 | package domain_util 2 | 3 | import "time" 4 | 5 | // IsTimestampFolder 检查是否为时间戳格式目录(YYYYMMDDHHMMSS) 6 | func IsTimestampFolder(name string) bool { 7 | if len(name) != 14 { // 20060102150405 长度固定为14 8 | return false 9 | } 10 | _, err := time.Parse("20060102150405", name) 11 | return err == nil 12 | } 13 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/jwt_custom.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt/v4" 5 | ) 6 | 7 | type JwtCustomClaims struct { 8 | Name string `json:"name"` 9 | ID string `json:"id"` 10 | jwt.StandardClaims 11 | } 12 | 13 | type JwtCustomRefreshClaims struct { 14 | ID string `json:"id"` 15 | jwt.StandardClaims 16 | } 17 | -------------------------------------------------------------------------------- /NineSong/.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=development 2 | SERVER_ADDRESS=:8080 3 | PORT=8080 4 | CONTEXT_TIMEOUT=2 5 | DB_HOST=mongodb #localhost: local #mongodb: docker 6 | DB_PORT=27017 7 | DB_USER= 8 | DB_PASS= 9 | DB_NAME=go-backend-clean-architecture-db 10 | ACCESS_TOKEN_EXPIRY_HOUR = 2 11 | REFRESH_TOKEN_EXPIRY_HOUR = 168 12 | ACCESS_TOKEN_SECRET=access_token_secret 13 | REFRESH_TOKEN_SECRET=refresh_token_secret -------------------------------------------------------------------------------- /NineSong/docker-compose-local-mongo.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | mongodb-local: 5 | image: mongo:6.0 6 | container_name: ninesong-mongodb-local 7 | restart: unless-stopped 8 | env_file: .env 9 | ports: 10 | - "27017:27017" 11 | volumes: 12 | - C:/Users/Public/Documents/NineSong/MongoDB:/data/db 13 | deploy: 14 | resources: 15 | limits: 16 | memory: 128M -------------------------------------------------------------------------------- /NineSong/domain/domain_util/query_options.go: -------------------------------------------------------------------------------- 1 | package domain_util 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/mongo/readpref" 5 | "time" 6 | ) 7 | 8 | // queryOptions 查询选项配置 9 | type queryOptions struct { 10 | maxTimeout time.Duration 11 | allowPartial bool 12 | projection []string 13 | readPreference *readpref.ReadPref 14 | } 15 | 16 | // QueryOption 查询选项类型 17 | type QueryOption func(*queryOptions) 18 | -------------------------------------------------------------------------------- /NineSong/docker-compose-test-mediamtx.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | mediamtx: 5 | image: bluenviron/mediamtx:latest-ffmpeg 6 | container_name: mediamtx-test 7 | restart: unless-stopped 8 | env_file: .env 9 | environment: 10 | - MTX_RTSPTRANSPORTS=tcp 11 | ports: 12 | - "8554:8554" 13 | - "1935:1935" 14 | - "8888:8888" 15 | - "8889:8889" 16 | - "8890:8890/udp" 17 | - "8189:8189/udp" -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_temp_metadata.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type TempRepository interface { 8 | GetTempPath( 9 | ctx context.Context, 10 | metadataType string, 11 | ) (string, error) 12 | 13 | UpdateTempPath( 14 | ctx context.Context, 15 | metadataType string, 16 | folderPath string, 17 | ) (bool, error) 18 | } 19 | -------------------------------------------------------------------------------- /NineSong/bootstrap/app.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 4 | 5 | type Application struct { 6 | Env *Env 7 | Mongo mongo.Client 8 | } 9 | 10 | func App() Application { 11 | app := &Application{} 12 | app.Env = NewEnv() 13 | app.Mongo = NewMongoDatabase(app.Env) 14 | return *app 15 | } 16 | 17 | func (app *Application) CloseDBConnection() { 18 | CloseMongoDBConnection(app.Mongo) 19 | } 20 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_playlist_track.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type PlaylistTrackMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` 7 | PlaylistID primitive.ObjectID `bson:"playlist_id"` 8 | MediaFileID primitive.ObjectID `bson:"media_file_id"` 9 | Index int `bson:"index"` 10 | } 11 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_lyrics_file.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type LyricsFileRepository interface { 8 | GetLyricsFilePath(ctx context.Context, artist, title, fileType string) (string, error) 9 | UpdateLyricsFilePath(ctx context.Context, artist, title, fileType, path string) (bool, error) 10 | CleanAll(ctx context.Context) (bool, error) 11 | } 12 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models/model_playlist_track.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type PlaylistTrackMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` 7 | PlaylistID primitive.ObjectID `bson:"playlist_id"` 8 | MediaFileID primitive.ObjectID `bson:"media_file_id"` 9 | Index int `bson:"index"` 10 | } 11 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_lyrics_file.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type LyricsFileMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` 7 | Path string `bson:"path"` 8 | Title string `bson:"title"` 9 | Artist string `bson:"artist"` 10 | FileType string `bson:"fileType"` 11 | } 12 | -------------------------------------------------------------------------------- /NineSong/domain/domain_util/task_progress.go: -------------------------------------------------------------------------------- 1 | package domain_util 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | type TaskProgress struct { 9 | ID string 10 | TotalFiles int32 11 | WalkedFiles int32 12 | ProcessedFiles int32 13 | Mu sync.Mutex 14 | Initialized bool 15 | Status string 16 | } 17 | 18 | func (tp *TaskProgress) AddTotalFiles(count int) { 19 | tp.Mu.Lock() 20 | defer tp.Mu.Unlock() 21 | atomic.AddInt32(&tp.TotalFiles, int32(count)) 22 | tp.Initialized = true 23 | } 24 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_external_resource.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/bson/primitive" 5 | "time" 6 | ) 7 | 8 | type ExternalResource struct { 9 | ID primitive.ObjectID `bson:"_id"` 10 | MetadataType string `bson:"metadata_type"` 11 | FolderPath string `bson:"folder_path"` 12 | CreatedAt time.Time `bson:"created_at"` 13 | UpdatedAt time.Time `bson:"updated_at"` 14 | } 15 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_media_mv.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type MediaMvMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` // MV唯一标识[4](@ref) 7 | Type string `bson:"mv_type"` // 竖屏/横屏[4](@ref) 8 | Path string `bson:"mv_path"` // MV文件路径 9 | Resolution string `bson:"mv_resolution"` // 分辨率 10 | PlayCount int64 `bson:"mv_plays"` 11 | } 12 | -------------------------------------------------------------------------------- /NineSong/docker-compose-local-mongo-macos.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | mongodb-local: 5 | image: mongo:6.0 6 | container_name: ninesong-mongodb-local 7 | command: mongod --auth 8 | restart: unless-stopped 9 | env_file: .env 10 | environment: 11 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 12 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASS} 13 | ports: 14 | - "27017:27017" 15 | volumes: 16 | - dbdata:/data/db 17 | deploy: 18 | resources: 19 | limits: 20 | memory: 128M 21 | 22 | volumes: 23 | dbdata: -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_media_lyrics.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type MediaLyricsMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` 7 | MediaID string `bson:"media_id"` 8 | Hash string `bson:"lyrics_hash"` 9 | Type string `bson:"lyrics_type"` 10 | Path string `bson:"lyrics_path"` 11 | ClimaxTime string `bson:"lyrics_climax_time"` 12 | Lyrics string `bson:"lyrics"` 13 | } 14 | -------------------------------------------------------------------------------- /NineSong/domain/domain_util/min_heap.go: -------------------------------------------------------------------------------- 1 | package domain_util 2 | 3 | type WordCount struct { 4 | Word string 5 | Count int 6 | } 7 | 8 | // MinHeap 最小堆实现 (基于container/heap) 9 | type MinHeap []WordCount 10 | 11 | func (h MinHeap) Len() int { return len(h) } 12 | func (h MinHeap) Less(i, j int) bool { return h[i].Count < h[j].Count } 13 | func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 14 | func (h *MinHeap) Push(x interface{}) { *h = append(*h, x.(WordCount)) } 15 | func (h *MinHeap) Pop() interface{} { 16 | old := *h 17 | n := len(old) 18 | x := old[n-1] 19 | *h = old[0 : n-1] 20 | return x 21 | } 22 | -------------------------------------------------------------------------------- /NineSong/docker-compose-local-develop-init-windows.ps1: -------------------------------------------------------------------------------- 1 | $env:MongoDB_DATA_VOLUME = "C:\Users\Public\Documents\NineSong\MongoDB" 2 | $env:SQLITE_DATA_VOLUME = "C:\Users\Public\Documents\NineSong\Sqlite" 3 | $env:METADATA_DATA_VOLUME = "C:\Users\Public\Documents\NineSong\MetaData" 4 | $env:MUSIC_DATA_VOLUME = "E:\0_Music" 5 | 6 | $dataDirs = @($env:MongoDB_DATA_VOLUME, $env:SQLITE_DATA_VOLUME, $env:METADATA_DATA_VOLUME, $env:MUSIC_DATA_VOLUME) 7 | foreach ($dir in $dataDirs) { 8 | if (-not (Test-Path $dir)) { 9 | New-Item -ItemType Directory -Path $dir 10 | } 11 | } 12 | 13 | docker-compose -f docker-compose-local-init.yaml up -d -------------------------------------------------------------------------------- /NineSong/srs_audio.conf: -------------------------------------------------------------------------------- 1 | listen 1935; 2 | daemon off; 3 | 4 | vhost __defaultVhost__ { 5 | # 禁用视频处理模块 6 | hls { 7 | enabled on; 8 | hls_path ./objs/nginx/html/hls; 9 | hls_fragment 5s; # 更短的音频切片 10 | hls_acodec mp3; # 指定音频编码格式 11 | hls_vcodec off; # 关闭视频轨道[6](@ref) 12 | } 13 | 14 | # 纯音频优化配置 15 | http_remux { 16 | enabled on; 17 | mount [vhost]/[app]/[stream].mp3; # 直接暴露MP3流[4](@ref) 18 | } 19 | 20 | # 关闭视频相关模块 21 | transcode { enabled off; } 22 | rtc { 23 | rtmp_to_rtc off; # 禁用WebRTC视频转换 24 | rtc_to_rtmp off; 25 | } 26 | } -------------------------------------------------------------------------------- /NineSong/domain/error_response.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | type ErrorResponse struct { 9 | Message string `json:"message"` 10 | } 11 | 12 | var ( 13 | ErrEmptyCollection = errors.New("config collection is empty") 14 | ErrNotFound = errors.New("not found") 15 | ) 16 | 17 | func IsNotFound(err error) bool { 18 | return strings.Contains(err.Error(), "no documents in result") || 19 | strings.Contains(err.Error(), "not found") 20 | } 21 | 22 | func WrapDomainError(err error, message string) error { 23 | if err == nil { 24 | return nil 25 | } 26 | return errors.New(message + ": " + err.Error()) 27 | } 28 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/user.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type User struct { 10 | ID primitive.ObjectID `bson:"_id"` 11 | Name string `bson:"name"` 12 | Email string `bson:"email"` 13 | Password string `bson:"password"` 14 | Admin bool `bson:"admin"` 15 | } 16 | 17 | type UserRepository interface { 18 | Create(c context.Context, user *User) error 19 | Fetch(c context.Context) ([]User, error) 20 | GetByEmail(c context.Context, email string) (User, error) 21 | GetByID(c context.Context, id string) (User, error) 22 | } 23 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/login.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type LoginRequest struct { 8 | Email string `form:"email" binding:"required,email"` 9 | Password string `form:"password" binding:"required"` 10 | } 11 | 12 | type LoginResponse struct { 13 | AccessToken string `json:"accessToken"` 14 | RefreshToken string `json:"refreshToken"` 15 | } 16 | 17 | type LoginUsecase interface { 18 | GetUserByEmail(c context.Context, email string) (User, error) 19 | CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error) 20 | CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error) 21 | } 22 | -------------------------------------------------------------------------------- /NineSong/domain/domain_app/domain_app_config/app_config.go: -------------------------------------------------------------------------------- 1 | package domain_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | ) 7 | 8 | type AppConfig struct { 9 | ID primitive.ObjectID `bson:"_id,omitempty"` 10 | ConfigKey string `bson:"config_key"` 11 | ConfigValue string `bson:"config_value"` 12 | } 13 | 14 | // AppConfigUsecase defines the usecase interface for app configuration. 15 | // It embeds the generic ConfigUsecase to provide standard GetAll/ReplaceAll operations. 16 | type AppConfigUsecase interface { 17 | usecase.ConfigUsecase[AppConfig] 18 | } 19 | -------------------------------------------------------------------------------- /NineSong/domain/domain_app/domain_app_config/ui_config.go: -------------------------------------------------------------------------------- 1 | package domain_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | ) 7 | 8 | type AppUIConfig struct { 9 | ID primitive.ObjectID `bson:"_id,omitempty"` 10 | ConfigKey string `bson:"config_key"` 11 | ConfigValue string `bson:"config_value"` 12 | } 13 | 14 | // AppUIConfigUsecase defines the usecase interface for app UI configuration. 15 | // It embeds the generic ConfigUsecase to provide standard GetAll/ReplaceAll operations. 16 | type AppUIConfigUsecase interface { 17 | usecase.ConfigUsecase[AppUIConfig] 18 | } 19 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/task.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type Task struct { 10 | ID primitive.ObjectID `bson:"_id" json:"-"` 11 | Title string `bson:"title" form:"title" binding:"required" json:"title"` 12 | UserID primitive.ObjectID `bson:"userID" json:"-"` 13 | } 14 | 15 | type TaskRepository interface { 16 | Create(c context.Context, task *Task) error 17 | FetchByUserID(c context.Context, userID string) ([]Task, error) 18 | } 19 | 20 | type TaskUsecase interface { 21 | Create(c context.Context, task *Task) error 22 | FetchByUserID(c context.Context, userID string) ([]Task, error) 23 | } 24 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_word_cloud.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | ) 7 | 8 | type WordCloudDBRepository interface { 9 | BulkUpsert(ctx context.Context, files []*scene_audio_db_models.WordCloudMetadata) (int, error) 10 | AllDelete(ctx context.Context) error 11 | DropAllIndex(ctx context.Context) error 12 | CreateIndex(ctx context.Context, fieldName string, unique bool) error 13 | GetAll(ctx context.Context) ([]*scene_audio_db_models.WordCloudMetadata, error) 14 | } 15 | -------------------------------------------------------------------------------- /NineSong/domain/domain_app/domain_app_config/audio_config.go: -------------------------------------------------------------------------------- 1 | package domain_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | ) 7 | 8 | type AppAudioConfig struct { 9 | ID primitive.ObjectID `bson:"_id,omitempty"` 10 | ConfigKey string `bson:"config_key"` 11 | ConfigValue string `bson:"config_value"` 12 | } 13 | 14 | // AppAudioConfigUsecase defines the usecase interface for app audio configuration. 15 | // It embeds the generic ConfigUsecase to provide standard GetAll/ReplaceAll operations. 16 | type AppAudioConfigUsecase interface { 17 | usecase.ConfigUsecase[AppAudioConfig] 18 | } 19 | -------------------------------------------------------------------------------- /NineSong/domain/domain_app/domain_app_config/library_config.go: -------------------------------------------------------------------------------- 1 | package domain_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | ) 7 | 8 | type AppLibraryConfig struct { 9 | ID primitive.ObjectID `bson:"_id,omitempty"` 10 | ConfigKey string `bson:"config_key"` 11 | ConfigValue string `bson:"config_value"` 12 | } 13 | 14 | // AppLibraryConfigUsecase defines the usecase interface for app library configuration. 15 | // It embeds the generic ConfigUsecase to provide standard GetAll/ReplaceAll operations. 16 | type AppLibraryConfigUsecase interface { 17 | usecase.ConfigUsecase[AppLibraryConfig] 18 | } 19 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/refresh_token.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type RefreshTokenRequest struct { 8 | RefreshToken string `form:"refreshToken" binding:"required"` 9 | } 10 | 11 | type RefreshTokenResponse struct { 12 | AccessToken string `json:"accessToken"` 13 | RefreshToken string `json:"refreshToken"` 14 | } 15 | 16 | type RefreshTokenUsecase interface { 17 | GetUserByID(c context.Context, id string) (User, error) 18 | CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error) 19 | CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error) 20 | ExtractIDFromToken(requestToken string, secret string) (string, error) 21 | } 22 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_word_cloud.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/bson/primitive" 5 | ) 6 | 7 | type WordCloudMetadata struct { 8 | ID primitive.ObjectID `bson:"_id"` 9 | Name string `bson:"name"` 10 | Count int `bson:"count"` 11 | Type string `bson:"type"` // "artist", "album", "media", "media_cue" 12 | Rank int `bson:"rank"` 13 | } 14 | 15 | type WordCloudRecommendation struct { 16 | ID primitive.ObjectID `bson:"_id"` 17 | Type string `bson:"type"` 18 | Name string `bson:"name"` 19 | Score float64 `bson:"score"` // 相关性分数 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_app/domain_app_config/playlist_id_config.go: -------------------------------------------------------------------------------- 1 | package domain_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | ) 7 | 8 | type AppPlaylistIDConfig struct { 9 | ID primitive.ObjectID `bson:"_id,omitempty"` 10 | ConfigKey string `bson:"config_key"` 11 | ConfigValue string `bson:"config_value"` 12 | } 13 | 14 | // AppPlaylistIDConfigUsecase defines the usecase interface for app playlist ID configuration. 15 | // It embeds the generic ConfigUsecase to provide standard GetAll/ReplaceAll operations. 16 | type AppPlaylistIDConfigUsecase interface { 17 | usecase.ConfigUsecase[AppPlaylistIDConfig] 18 | } 19 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_auth/profile_controller.go: -------------------------------------------------------------------------------- 1 | package controller_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type ProfileController struct { 12 | ProfileUsecase domain_auth.ProfileUsecase 13 | } 14 | 15 | func (pc *ProfileController) Fetch(c *gin.Context) { 16 | userID := c.GetString("x-user-id") 17 | 18 | profile, err := pc.ProfileUsecase.GetProfileByID(c, userID) 19 | if err != nil { 20 | c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) 21 | return 22 | } 23 | 24 | c.JSON(http.StatusOK, profile) 25 | } 26 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models/model_playlist.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_models 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type PlaylistMetadata struct { 10 | ID primitive.ObjectID `bson:"_id"` 11 | Name string `bson:"name"` 12 | Comment string `bson:"comment"` 13 | Duration float64 `bson:"duration"` 14 | SongCount float64 `bson:"song_count"` 15 | CreatedAt time.Time `bson:"created_at"` 16 | UpdatedAt time.Time `bson:"updated_at"` 17 | Path string `bson:"path"` 18 | Size int `bson:"size"` 19 | //OwnerID string `bson:"owner_id"` 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_retrieval.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type RetrievalRepository interface { 8 | GetStreamPath(ctx context.Context, mediaFileId string, cueModel bool) (string, error) 9 | 10 | GetStreamTempPath(ctx context.Context, metadataType string) (string, error) 11 | 12 | GetDownloadPath(ctx context.Context, mediaFileId string) (string, error) 13 | 14 | GetCoverArtID(ctx context.Context, fileType string, targetID string) (string, error) 15 | 16 | GetLyricsLrcMetaData(ctx context.Context, mediaFileId, artist, title, fileType string) (string, error) 17 | 18 | GetLyricsLrcFile(ctx context.Context, mediaFileId string) (string, error) 19 | } 20 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/signup.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type SignupRequest struct { 8 | Name string `form:"name" binding:"required"` 9 | Email string `form:"email" binding:"required,email"` 10 | Password string `form:"password" binding:"required"` 11 | } 12 | 13 | type SignupResponse struct { 14 | AccessToken string `json:"accessToken"` 15 | RefreshToken string `json:"refreshToken"` 16 | } 17 | 18 | type SignupUsecase interface { 19 | Create(c context.Context, user *User) error 20 | GetUserByEmail(c context.Context, email string) (User, error) 21 | CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error) 22 | CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error) 23 | } 24 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_recommend.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type RecommendRouteRepository interface { 8 | GetGeneralRecommendations( 9 | ctx context.Context, 10 | recommendType string, 11 | limit int, 12 | randomSeed string, 13 | recommendOffset string, 14 | logShow bool, 15 | refresh bool, 16 | ) ([]interface{}, error) 17 | 18 | GetPersonalizedRecommendations( 19 | ctx context.Context, 20 | userId string, 21 | recommendType string, 22 | limit int, 23 | logShow bool, 24 | refresh bool, 25 | ) ([]interface{}, error) 26 | 27 | GetPopularRecommendations( 28 | ctx context.Context, 29 | recommendType string, 30 | limit int, 31 | logShow bool, 32 | refresh bool, 33 | ) ([]interface{}, error) 34 | } 35 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_playlist.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/bson/primitive" 5 | "time" 6 | ) 7 | 8 | type PlaylistMetadata struct { 9 | ID primitive.ObjectID `bson:"_id"` 10 | Name string `bson:"name"` 11 | Comment string `bson:"comment"` 12 | Duration float64 `bson:"duration"` 13 | SongCount float64 `bson:"song_count"` 14 | CreatedAt time.Time `bson:"created_at"` 15 | UpdatedAt time.Time `bson:"updated_at"` 16 | Path string `bson:"path"` 17 | Sync bool `bson:"sync"` 18 | Size int `bson:"size"` 19 | Rules string `bson:"rules"` 20 | EvaluatedAt time.Time `bson:"evaluated_at"` 21 | } 22 | -------------------------------------------------------------------------------- /NineSong/api/route/route_auth/profile_route.go: -------------------------------------------------------------------------------- 1 | package route_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_auth" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_auth" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_auth" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func NewProfileRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 15 | ur := repository_auth.NewUserRepository(db, domain.CollectionUser) 16 | pc := &controller_auth.ProfileController{ 17 | ProfileUsecase: usecase_auth.NewProfileUsecase(ur, timeout), 18 | } 19 | group.GET("/user/profile", pc.Fetch) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_word_cloud.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | ) 7 | 8 | type WordCloudRouteRepository interface { 9 | GetAllWordCloudSearch( 10 | ctx context.Context, 11 | ) ([]scene_audio_db_models.WordCloudMetadata, error) 12 | 13 | GetAllGenreSearch( 14 | ctx context.Context, 15 | ) ([]scene_audio_db_models.WordCloudMetadata, error) 16 | 17 | GetHighFrequencyWordCloudSearch( 18 | ctx context.Context, wordLimit int, 19 | ) ([]scene_audio_db_models.WordCloudMetadata, error) 20 | 21 | GetRecommendedWordCloudSearch( 22 | ctx context.Context, keywords []string, 23 | ) ([]scene_audio_db_models.WordCloudRecommendation, error) 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/api/route/route_auth/task_route.go: -------------------------------------------------------------------------------- 1 | package route_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_auth" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_auth" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_auth" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func NewTaskRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 15 | tr := repository_auth.NewTaskRepository(db, domain.CollectionTask) 16 | tc := &controller_auth.TaskController{ 17 | TaskUsecase: usecase_auth.NewTaskUsecase(tr, timeout), 18 | } 19 | group.GET("/user/task", tc.Fetch) 20 | group.POST("/user/task", tc.Create) 21 | } 22 | -------------------------------------------------------------------------------- /NineSong/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/route" 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func main() { 14 | app := bootstrap.App() 15 | env := app.Env 16 | db := app.Mongo.Database(env.DBName) 17 | defer app.CloseDBConnection() 18 | 19 | initializer := bootstrap.NewInitializer(env, db) 20 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 21 | defer cancel() 22 | if err := initializer.CheckAndInitialize(ctx); err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | gin.SetMode(gin.ReleaseMode) 27 | router := gin.Default() 28 | route.Setup(env, time.Duration(env.ContextTimeout)*time.Second, db, router) 29 | if err := router.Run(env.ServerAddress); err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_utils_models/model_media_generator.go: -------------------------------------------------------------------------------- 1 | package scene_audio_utils_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type MediaGeneratorMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` 7 | GeneratorType string `bson:"generator_type"` // 音频生成的类型(如正弦波、方波等) 8 | Frequency float64 `bson:"frequency"` // 基础频率(Hz) 9 | Waveform string `bson:"waveform"` // 波形类型(如正弦波、三角波等) 10 | 11 | // 合成参数 12 | Harmonics int `bson:"harmonics"` // 谐波数量[9](@ref) 13 | PhaseNoise float64 `bson:"phase_noise"` // 相位噪声强度[14](@ref) 14 | FormantRatio float64 `bson:"formant_ratio"` // 共振峰比例[10](@ref) 15 | 16 | // 生成模型参数 17 | GANLatentVector []float64 `bson:"gan_latent"` // 生成对抗网络潜向量[14](@ref) 18 | VAEKLDivergence float64 `bson:"vae_kld"` // 变分自编码器KL散度[14](@ref) 19 | } 20 | -------------------------------------------------------------------------------- /NineSong/api/route/route_system/system_info_route.go: -------------------------------------------------------------------------------- 1 | package route_system 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_system" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_system" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_system" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewSystemInfoRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_system.NewSystemInfoRepository(db, domain.CollectionSystemInfo) 15 | uc := usecase_system.NewSystemInfoUsecase(repo, timeout) 16 | ctrl := controller_system.NewSystemInfoController(uc) 17 | 18 | group.GET("/system/info", ctrl.Get) 19 | group.PUT("/system/info", ctrl.Upsert) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_auth/profile_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "time" 7 | ) 8 | 9 | type profileUsecase struct { 10 | userRepository domain_auth.UserRepository 11 | contextTimeout time.Duration 12 | } 13 | 14 | func NewProfileUsecase(userRepository domain_auth.UserRepository, timeout time.Duration) domain_auth.ProfileUsecase { 15 | return &profileUsecase{ 16 | userRepository: userRepository, 17 | contextTimeout: timeout, 18 | } 19 | } 20 | 21 | func (pu *profileUsecase) GetProfileByID(c context.Context, userID string) (*domain_auth.Profile, error) { 22 | ctx, cancel := context.WithTimeout(c, pu.contextTimeout) 23 | defer cancel() 24 | 25 | user, err := pu.userRepository.GetByID(ctx, userID) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return &domain_auth.Profile{Name: user.Name, Email: user.Email}, nil 31 | } 32 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_home.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models" 7 | ) 8 | 9 | type HomeRepository interface { 10 | GetRandomArtistList( 11 | ctx context.Context, 12 | end string, 13 | start string, 14 | ) ([]scene_audio_route_models.ArtistMetadata, error) 15 | GetRandomAlbumList( 16 | ctx context.Context, 17 | end string, 18 | start string, 19 | ) ([]scene_audio_route_models.AlbumMetadata, error) 20 | GetRandomMediaFileList( 21 | ctx context.Context, 22 | end string, 23 | start string, 24 | ) ([]scene_audio_route_models.MediaFileMetadata, error) 25 | GetRandomMediaCueList( 26 | ctx context.Context, 27 | end string, 28 | start string, 29 | ) ([]scene_audio_route_models.MediaFileCueMetadata, error) 30 | } 31 | -------------------------------------------------------------------------------- /NineSong/api/route/route_auth/login_route.go: -------------------------------------------------------------------------------- 1 | package route_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_auth" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_auth" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_auth" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" 11 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func NewLoginRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 16 | ur := repository_auth.NewUserRepository(db, domain.CollectionUser) 17 | lc := &controller_auth.LoginController{ 18 | LoginUsecase: usecase_auth.NewLoginUsecase(ur, timeout), 19 | Env: env, 20 | } 21 | group.POST("/user/login", lc.Login) 22 | } 23 | -------------------------------------------------------------------------------- /NineSong/repository/repository_system/system_info_repository.go: -------------------------------------------------------------------------------- 1 | package repository_system 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_system" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // SystemInfoRepository is an alias for the generic ConfigRepository. 11 | // No custom methods are needed for this configuration entity. 12 | type SystemInfoRepository interface { 13 | domain.ConfigRepository[domain_system.SystemInfo] 14 | } 15 | 16 | // NewSystemInfoRepository creates a new repository for system information. 17 | // It uses the generic ConfigMongoRepository implementation. 18 | func NewSystemInfoRepository(db mongo.Database, collection string) SystemInfoRepository { 19 | return repository.NewConfigMongoRepository[domain_system.SystemInfo](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_playlist.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | // PlaylistRepository 基础CRUD接口 10 | type PlaylistRepository interface { 11 | // 创建/更新 12 | Upsert(ctx context.Context, file *scene_audio_db_models.PlaylistMetadata) error 13 | BulkUpsert(ctx context.Context, files []*scene_audio_db_models.PlaylistMetadata) (int, error) 14 | 15 | // 删除 16 | DeleteByID(ctx context.Context, id primitive.ObjectID) error 17 | DeleteByPath(ctx context.Context, path string) error 18 | 19 | // 查询 20 | GetByID(ctx context.Context, id primitive.ObjectID) (*scene_audio_db_models.PlaylistMetadata, error) 21 | GetByPath(ctx context.Context, path string) (*scene_audio_db_models.PlaylistMetadata, error) 22 | } 23 | -------------------------------------------------------------------------------- /NineSong/api/route/route_auth/signup_route.go: -------------------------------------------------------------------------------- 1 | package route_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_auth" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_auth" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_auth" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" 11 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func NewSignupRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 16 | ur := repository_auth.NewUserRepository(db, domain.CollectionUser) 17 | sc := controller_auth.SignupController{ 18 | SignupUsecase: usecase_auth.NewSignupUsecase(ur, timeout), 19 | Env: env, 20 | } 21 | group.POST("/user/signup", sc.Signup) 22 | } 23 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_home.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | // AnnotationRepository 基础CRUD接口 10 | type AnnotationRepository interface { 11 | // 创建/更新 12 | Upsert(ctx context.Context, file *scene_audio_db_models.AnnotationMetadata) error 13 | BulkUpsert(ctx context.Context, files []*scene_audio_db_models.AnnotationMetadata) (int, error) 14 | 15 | // 删除 16 | DeleteByID(ctx context.Context, id primitive.ObjectID) error 17 | DeleteByPath(ctx context.Context, path string) error 18 | 19 | // 查询 20 | GetByID(ctx context.Context, id primitive.ObjectID) (*scene_audio_db_models.AnnotationMetadata, error) 21 | GetByPath(ctx context.Context, path string) (*scene_audio_db_models.AnnotationMetadata, error) 22 | } 23 | -------------------------------------------------------------------------------- /NineSong/repository/repository_app/repository_app_config/server_config_repository.go: -------------------------------------------------------------------------------- 1 | package repository_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // AppServerConfigRepository is an alias for the generic BaseRepository. 11 | type AppServerConfigRepository interface { 12 | domain.BaseRepository[domain_app_config.AppServerConfig] 13 | } 14 | 15 | // NewAppServerConfigRepository creates a new repository for app server configurations. 16 | // It uses the generic BaseMongoRepository implementation. 17 | func NewAppServerConfigRepository(db mongo.Database, collection string) AppServerConfigRepository { 18 | return repository.NewBaseMongoRepository[domain_app_config.AppServerConfig](db, collection) 19 | } 20 | -------------------------------------------------------------------------------- /NineSong/api/controller/response_handler.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | const ( 6 | APIVersion = "1.0.0" 7 | ServerVersion = "1.0.0" 8 | ServiceType = "NSMusicS" 9 | ) 10 | 11 | func SuccessResponse(c *gin.Context, dataKey string, data interface{}, count int) { 12 | c.JSON(200, gin.H{ 13 | "ninesong-response": gin.H{ 14 | "status": "ok", 15 | "version": APIVersion, 16 | "type": ServiceType, 17 | "serverVersion": ServerVersion, 18 | dataKey: data, 19 | "count": count, 20 | }, 21 | }) 22 | } 23 | 24 | func ErrorResponse(c *gin.Context, statusCode int, errorCode string, message string) { 25 | c.JSON(statusCode, gin.H{ 26 | "ninesong-response": gin.H{ 27 | "status": "error", 28 | "version": APIVersion, 29 | "type": ServiceType, 30 | "serverVersion": ServerVersion, 31 | "error": gin.H{ 32 | "code": errorCode, 33 | "message": message, 34 | }, 35 | }, 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /NineSong/api/route/route_system/system_configuration_route.go: -------------------------------------------------------------------------------- 1 | package route_system 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_system" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_system" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_system" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewSystemConfigurationRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_system.NewSystemConfigurationRepository(db, domain.CollectionSystemConfiguration) 15 | uc := usecase_system.NewSystemConfigurationUsecase(repo, timeout) 16 | ctrl := controller_system.NewSystemConfigurationController(uc) 17 | 18 | group.GET("/system/config", ctrl.Get) 19 | group.PUT("/system/config", ctrl.Upsert) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_app/domain_app_config/server_config.go: -------------------------------------------------------------------------------- 1 | package domain_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | "time" 7 | ) 8 | 9 | type AppServerConfig struct { 10 | ID primitive.ObjectID `bson:"_id"` 11 | ServerName string `bson:"serverName" bson:"server_name"` 12 | URL string `bson:"url" bson:"url"` 13 | UserName string `bson:"userName" bson:"user_name"` 14 | Password string `bson:"password" bson:"password"` 15 | LastLoginAt time.Time `bson:"lastLoginAt" bson:"last_login_at,omitempty"` 16 | Type string `bson:"type" bson:"type"` 17 | } 18 | 19 | // AppServerConfigUsecase defines the usecase interface for app server configuration. 20 | // It embeds the generic BaseUsecase to provide standard CRUD operations. 21 | type AppServerConfigUsecase interface { 22 | usecase.BaseUsecase[AppServerConfig] 23 | } 24 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_auth/task_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "time" 7 | ) 8 | 9 | type taskUsecase struct { 10 | taskRepository domain_auth.TaskRepository 11 | contextTimeout time.Duration 12 | } 13 | 14 | func NewTaskUsecase(taskRepository domain_auth.TaskRepository, timeout time.Duration) domain_auth.TaskUsecase { 15 | return &taskUsecase{ 16 | taskRepository: taskRepository, 17 | contextTimeout: timeout, 18 | } 19 | } 20 | 21 | func (tu *taskUsecase) Create(c context.Context, task *domain_auth.Task) error { 22 | ctx, cancel := context.WithTimeout(c, tu.contextTimeout) 23 | defer cancel() 24 | return tu.taskRepository.Create(ctx, task) 25 | } 26 | 27 | func (tu *taskUsecase) FetchByUserID(c context.Context, userID string) ([]domain_auth.Task, error) { 28 | ctx, cancel := context.WithTimeout(c, tu.contextTimeout) 29 | defer cancel() 30 | return tu.taskRepository.FetchByUserID(ctx, userID) 31 | } 32 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_playlist_track.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | // PlaylistTrackRepository 基础CRUD接口 10 | type PlaylistTrackRepository interface { 11 | // 创建/更新 12 | Upsert(ctx context.Context, file *scene_audio_db_models.PlaylistTrackMetadata) error 13 | BulkUpsert(ctx context.Context, files []*scene_audio_db_models.PlaylistTrackMetadata) (int, error) 14 | 15 | // 删除 16 | DeleteByID(ctx context.Context, id primitive.ObjectID) error 17 | DeleteByPath(ctx context.Context, path string) error 18 | 19 | // 查询 20 | GetByID(ctx context.Context, id primitive.ObjectID) (*scene_audio_db_models.PlaylistTrackMetadata, error) 21 | GetByPath(ctx context.Context, path string) (*scene_audio_db_models.PlaylistTrackMetadata, error) 22 | } 23 | -------------------------------------------------------------------------------- /NineSong/repository/repository_app/repository_app_config/ui_config_repository.go: -------------------------------------------------------------------------------- 1 | package repository_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // AppUIConfigRepository is an alias for the generic ConfigRepository. 11 | // It handles collections of UI configuration items. 12 | type AppUIConfigRepository interface { 13 | domain.ConfigRepository[domain_app_config.AppUIConfig] 14 | } 15 | 16 | // NewAppUIConfigRepository creates a new repository for app UI configurations. 17 | // It uses the generic ConfigMongoRepository implementation. 18 | func NewAppUIConfigRepository(db mongo.Database, collection string) AppUIConfigRepository { 19 | return repository.NewConfigMongoRepository[domain_app_config.AppUIConfig](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/api/route/route_auth/refresh_token_route.go: -------------------------------------------------------------------------------- 1 | package route_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_auth" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_auth" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_auth" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" 11 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func NewRefreshTokenRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 16 | ur := repository_auth.NewUserRepository(db, domain.CollectionUser) 17 | rtc := &controller_auth.RefreshTokenController{ 18 | RefreshTokenUsecase: usecase_auth.NewRefreshTokenUsecase(ur, timeout), 19 | Env: env, 20 | } 21 | group.POST("/user/refresh", rtc.RefreshToken) 22 | } 23 | -------------------------------------------------------------------------------- /NineSong/repository/repository_app/repository_app_config/app_config_repository.go: -------------------------------------------------------------------------------- 1 | package repository_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // AppConfigRepository is an alias for the generic ConfigRepository. 11 | // It handles collections of configuration items. 12 | type AppConfigRepository interface { 13 | domain.ConfigRepository[domain_app_config.AppConfig] 14 | } 15 | 16 | // NewAppConfigRepository creates a new repository for app configurations. 17 | // It uses the generic ConfigMongoRepository implementation which supports GetAll and ReplaceAll. 18 | func NewAppConfigRepository(db mongo.Database, collection string) AppConfigRepository { 19 | return repository.NewConfigMongoRepository[domain_app_config.AppConfig](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/api/route/route_app/route_app_config/app_config_route.go: -------------------------------------------------------------------------------- 1 | package route_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_app/controller_app_config" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_app/usecase_app_config" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewAppConfigRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_app_config.NewAppConfigRepository(db, domain.CollectionFileEntityAudioAppConfigs) 15 | uc := usecase_app_config.NewAppConfigUsecase(repo, timeout) 16 | ctrl := controller_app_config.NewAppConfigController(uc) 17 | 18 | group.GET("/app/config", ctrl.GetAll) 19 | group.PUT("/app/config", ctrl.ReplaceAll) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/api/route/route_app/route_app_config/ui_config_route.go: -------------------------------------------------------------------------------- 1 | package route_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_app/controller_app_config" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_app/usecase_app_config" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewAppUIConfigRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_app_config.NewAppUIConfigRepository(db, domain.CollectionFileEntityAudioAppUIConfigs) 15 | uc := usecase_app_config.NewAppUIConfigUsecase(repo, timeout) 16 | ctrl := controller_app_config.NewAppUIConfigController(uc) 17 | 18 | group.GET("/app/ui", ctrl.GetAll) 19 | group.PUT("/app/ui", ctrl.ReplaceAll) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/repository/repository_app/repository_app_config/audio_config_repository.go: -------------------------------------------------------------------------------- 1 | package repository_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // AppAudioConfigRepository is an alias for the generic ConfigRepository. 11 | // It handles collections of audio configuration items. 12 | type AppAudioConfigRepository interface { 13 | domain.ConfigRepository[domain_app_config.AppAudioConfig] 14 | } 15 | 16 | // NewAppAudioConfigRepository creates a new repository for app audio configurations. 17 | // It uses the generic ConfigMongoRepository implementation. 18 | func NewAppAudioConfigRepository(db mongo.Database, collection string) AppAudioConfigRepository { 19 | return repository.NewConfigMongoRepository[domain_app_config.AppAudioConfig](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/repository/repository_system/system_configuration_repository.go: -------------------------------------------------------------------------------- 1 | package repository_system 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_system" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // SystemConfigurationRepository is an alias for the generic ConfigRepository. 11 | // No custom methods are needed for this configuration entity. 12 | type SystemConfigurationRepository interface { 13 | domain.ConfigRepository[domain_system.SystemConfiguration] 14 | } 15 | 16 | // NewSystemConfigurationRepository creates a new repository for system configurations. 17 | // It uses the generic ConfigMongoRepository implementation. 18 | func NewSystemConfigurationRepository(db mongo.Database, collection string) SystemConfigurationRepository { 19 | return repository.NewConfigMongoRepository[domain_system.SystemConfiguration](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/utils_media_file_metadata.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/bson" 5 | ) 6 | 7 | func (m *MediaFileMetadata) ToUpdateDoc() bson.M { 8 | data, _ := bson.Marshal(m) 9 | var raw bson.M 10 | _ = bson.Unmarshal(data, &raw) 11 | 12 | delete(raw, "_id") 13 | delete(raw, "created_at") 14 | 15 | raw["thumbnail_url"] = m.ThumbnailURL 16 | raw["medium_image_url"] = m.MediumImageURL 17 | raw["high_image_url"] = m.HighImageURL 18 | 19 | return bson.M{"$set": raw} 20 | } 21 | 22 | func (m *MediaFileCueMetadata) ToUpdateDoc() bson.M { 23 | data, _ := bson.Marshal(m) 24 | var raw bson.M 25 | _ = bson.Unmarshal(data, &raw) 26 | 27 | delete(raw, "_id") 28 | delete(raw, "created_at") 29 | raw["back_image_url"] = m.CueResources.BackImage 30 | raw["cover_image_url"] = m.CueResources.CoverImage 31 | raw["disc_image_url"] = m.CueResources.DiscImage 32 | raw["has_cover_art"] = m.CueResources.CoverImage != "" 33 | 34 | return bson.M{"$set": raw} 35 | } 36 | -------------------------------------------------------------------------------- /NineSong/api/route/route_app/route_app_config/audio_config_route.go: -------------------------------------------------------------------------------- 1 | package route_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_app/controller_app_config" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_app/usecase_app_config" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewAppAudioConfigRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_app_config.NewAppAudioConfigRepository(db, domain.CollectionFileEntityAudioAppAudioConfigs) 15 | uc := usecase_app_config.NewAppAudioConfigUsecase(repo, timeout) 16 | ctrl := controller_app_config.NewAppAudioConfigController(uc) 17 | 18 | group.GET("/app/audio", ctrl.GetAll) 19 | group.PUT("/app/audio", ctrl.ReplaceAll) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_annotation.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 7 | ) 8 | 9 | type AnnotationRepository interface { 10 | UpdateStarred(ctx context.Context, itemId string, itemType string) (bool, error) 11 | UpdateUnStarred(ctx context.Context, itemId string, itemType string) (bool, error) 12 | UpdateRating(ctx context.Context, itemId string, itemType string, rating int) (bool, error) 13 | UpdateScrobble(ctx context.Context, itemId string, itemType string) (bool, error) 14 | UpdateCompleteScrobble(ctx context.Context, itemId string, itemType string) (bool, error) 15 | 16 | UpdateTagSource(ctx context.Context, itemId string, itemType string, tags []scene_audio_db_models.TagSource) (bool, error) 17 | UpdateWeightedTag(ctx context.Context, itemId string, itemType string, tags []scene_audio_db_models.WeightedTag) (bool, error) 18 | } 19 | -------------------------------------------------------------------------------- /NineSong/repository/repository_app/repository_app_config/library_config_repository.go: -------------------------------------------------------------------------------- 1 | package repository_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // AppLibraryConfigRepository is an alias for the generic ConfigRepository. 11 | // It handles collections of library configuration items. 12 | type AppLibraryConfigRepository interface { 13 | domain.ConfigRepository[domain_app_config.AppLibraryConfig] 14 | } 15 | 16 | // NewAppLibraryConfigRepository creates a new repository for app library configurations. 17 | // It uses the generic ConfigMongoRepository implementation. 18 | func NewAppLibraryConfigRepository(db mongo.Database, collection string) AppLibraryConfigRepository { 19 | return repository.NewConfigMongoRepository[domain_app_config.AppLibraryConfig](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_system/system_info_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase_system 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_system" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_system" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // systemInfoUsecase implements the usecase interface for system information. 12 | // It embeds the generic ConfigUsecase to handle the core Get/Upsert logic. 13 | type systemInfoUsecase struct { 14 | usecase.ConfigUsecase[domain_system.SystemInfo] 15 | } 16 | 17 | // NewSystemInfoUsecase creates a new usecase for system information. 18 | // It uses the generic NewConfigUsecase constructor for consistency and code reuse. 19 | func NewSystemInfoUsecase(repo repository_system.SystemInfoRepository, timeout time.Duration) domain_system.SystemInfoUsecase { 20 | baseUsecase := usecase.NewConfigUsecase[domain_system.SystemInfo](repo, timeout) 21 | return &systemInfoUsecase{ 22 | ConfigUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/api/route/route_app/route_app_config/library_config_route.go: -------------------------------------------------------------------------------- 1 | package route_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_app/controller_app_config" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_app/usecase_app_config" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewAppLibraryConfigRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_app_config.NewAppLibraryConfigRepository(db, domain.CollectionFileEntityAudioAppLibraryConfigs) 15 | uc := usecase_app_config.NewAppLibraryConfigUsecase(repo, timeout) 16 | ctrl := controller_app_config.NewAppLibraryConfigController(uc) 17 | 18 | group.GET("/app/library", ctrl.GetAll) 19 | group.PUT("/app/library", ctrl.ReplaceAll) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_playlist.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models" 7 | ) 8 | 9 | type PlaylistRepository interface { 10 | GetPlaylistsAll( 11 | ctx context.Context, 12 | ) ([]scene_audio_route_models.PlaylistMetadata, error) 13 | 14 | GetPlaylist( 15 | ctx context.Context, 16 | playlistId string, 17 | ) (*scene_audio_route_models.PlaylistMetadata, error) 18 | 19 | CreatePlaylist( 20 | ctx context.Context, 21 | playlist scene_audio_route_models.PlaylistMetadata, 22 | ) (*scene_audio_route_models.PlaylistMetadata, error) 23 | 24 | DeletePlaylist( 25 | ctx context.Context, 26 | playlistId string, 27 | ) (bool, error) 28 | 29 | UpdatePlaylistInfo( 30 | ctx context.Context, 31 | playlistId string, 32 | playlist scene_audio_route_models.PlaylistMetadata, 33 | ) (*scene_audio_route_models.PlaylistMetadata, error) 34 | } 35 | -------------------------------------------------------------------------------- /NineSong/qodana.yaml: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------# 2 | # Qodana analysis is configured by qodana.yaml file # 3 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html # 4 | #-------------------------------------------------------------------------------# 5 | version: "1.0" 6 | 7 | #Specify inspection profile for code analysis 8 | profile: 9 | name: qodana.starter 10 | 11 | #Enable inspections 12 | #include: 13 | # - name: 14 | 15 | #Disable inspections 16 | #exclude: 17 | # - name: 18 | # paths: 19 | # - 20 | 21 | #Execute shell command before Qodana execution (Applied in CI/CD pipeline) 22 | #bootstrap: sh ./prepare-qodana.sh 23 | 24 | #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) 25 | #plugins: 26 | # - id: #(plugin id can be found at https://plugins.jetbrains.com) 27 | 28 | #Specify Qodana linter for analysis (Applied in CI/CD pipeline) 29 | linter: jetbrains/qodana-go:latest 30 | -------------------------------------------------------------------------------- /NineSong/api/route/route_app/route_app_config/playlist_id_config_route.go: -------------------------------------------------------------------------------- 1 | package route_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_app/controller_app_config" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_app/usecase_app_config" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewAppPlaylistIDConfigRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_app_config.NewAppPlaylistIDConfigRepository(db, domain.CollectionFileEntityAudioAppPlaylistIDConfigs) 15 | uc := usecase_app_config.NewAppPlaylistIDConfigUsecase(repo, timeout) 16 | ctrl := controller_app_config.NewAppPlaylistIDConfigController(uc) 17 | 18 | group.GET("/app/playlist", ctrl.GetAll) 19 | group.PUT("/app/playlist", ctrl.ReplaceAll) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/api/route/route_app/route_app_library/media_file_library_route.go: -------------------------------------------------------------------------------- 1 | package route_app_library 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_app/controller_app_library" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_library" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_app/usecase_app_library" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewAppMediaFileLibraryRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_app_library.NewAppMediaFileLibraryRepository(db, domain.CollectionFileEntityAudioAppMediaFileLibrary) 15 | uc := usecase_app_library.NewAppMediaFileLibraryUsecase(repo, timeout) 16 | ctrl := controller_app_library.NewAppMediaFileLibraryController(uc) 17 | 18 | group.GET("/app/media", ctrl.GetAll) 19 | group.PUT("/app/media", ctrl.ReplaceAll) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/repository/repository_app/repository_app_config/playlist_id_config_repository.go: -------------------------------------------------------------------------------- 1 | package repository_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // AppPlaylistIDConfigRepository is an alias for the generic ConfigRepository. 11 | // It handles collections of playlist ID configuration items. 12 | type AppPlaylistIDConfigRepository interface { 13 | domain.ConfigRepository[domain_app_config.AppPlaylistIDConfig] 14 | } 15 | 16 | // NewAppPlaylistIDConfigRepository creates a new repository for app playlist ID configurations. 17 | // It uses the generic ConfigMongoRepository implementation. 18 | func NewAppPlaylistIDConfigRepository(db mongo.Database, collection string) AppPlaylistIDConfigRepository { 19 | return repository.NewConfigMongoRepository[domain_app_config.AppPlaylistIDConfig](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/repository/repository_app/repository_app_library/media_file_library_repository.go: -------------------------------------------------------------------------------- 1 | package repository_app_library 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_library" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository" 8 | ) 9 | 10 | // AppMediaFileLibraryRepository is an alias for the generic ConfigRepository. 11 | // It handles the collection of media file library documents. 12 | type AppMediaFileLibraryRepository interface { 13 | domain.ConfigRepository[domain_app_library.AppMediaFileLibrary] 14 | } 15 | 16 | // NewAppMediaFileLibraryRepository creates a new repository for the media file library. 17 | // It uses the generic ConfigMongoRepository implementation. 18 | func NewAppMediaFileLibraryRepository(db mongo.Database, collection string) AppMediaFileLibraryRepository { 19 | return repository.NewConfigMongoRepository[domain_app_library.AppMediaFileLibrary](db, collection) 20 | } 21 | -------------------------------------------------------------------------------- /NineSong/api/route/route_app/route_app_config/server_config_route.go: -------------------------------------------------------------------------------- 1 | package route_app_config 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_app/controller_app_config" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_app/usecase_app_config" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewAppServerConfigRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | repo := repository_app_config.NewAppServerConfigRepository(db, domain.CollectionFileEntityAudioAppServerConfigs) 15 | uc := usecase_app_config.NewAppServerConfigUsecase(repo, timeout) 16 | ctrl := controller_app_config.NewAppServerConfigController(uc) 17 | 18 | group.GET("/app/server", ctrl.GetAll) 19 | group.PUT("/app/server", ctrl.Upsert) 20 | group.DELETE("/app/server", ctrl.DeleteByID) 21 | } 22 | -------------------------------------------------------------------------------- /NineSong/mongo/mocks/SingleResult.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.16.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // SingleResult is an autogenerated mock type for the SingleResult type 8 | type SingleResult struct { 9 | mock.Mock 10 | } 11 | 12 | // Decode provides a mock function with given fields: _a0 13 | func (_m *SingleResult) Decode(_a0 interface{}) error { 14 | ret := _m.Called(_a0) 15 | 16 | var r0 error 17 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 18 | r0 = rf(_a0) 19 | } else { 20 | r0 = ret.Error(0) 21 | } 22 | 23 | return r0 24 | } 25 | 26 | type mockConstructorTestingTNewSingleResult interface { 27 | mock.TestingT 28 | Cleanup(func()) 29 | } 30 | 31 | // NewSingleResult creates a new instance of SingleResult. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 32 | func NewSingleResult(t mockConstructorTestingTNewSingleResult) *SingleResult { 33 | mock := &SingleResult{} 34 | mock.Mock.Test(t) 35 | 36 | t.Cleanup(func() { mock.AssertExpectations(t) }) 37 | 38 | return mock 39 | } 40 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_app/usecase_app_config/app_config_usercase.go: -------------------------------------------------------------------------------- 1 | package usecase_app_config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // AppConfigUsecase implements the usecase interface for app configuration. 12 | // It embeds the generic ConfigUsecase to handle the core GetAll/ReplaceAll logic. 13 | type AppConfigUsecase struct { 14 | usecase.ConfigUsecase[domain_app_config.AppConfig] 15 | } 16 | 17 | // NewAppConfigUsecase creates a new usecase for app configuration. 18 | // It uses the generic NewConfigUsecase constructor for consistency. 19 | func NewAppConfigUsecase(repo repository_app_config.AppConfigRepository, timeout time.Duration) domain_app_config.AppConfigUsecase { 20 | baseUsecase := usecase.NewConfigUsecase[domain_app_config.AppConfig](repo, timeout) 21 | return &AppConfigUsecase{ 22 | ConfigUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_app/usecase_app_config/ui_config_usercase.go: -------------------------------------------------------------------------------- 1 | package usecase_app_config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // AppUIConfigUsecase implements the usecase interface for app UI configuration. 12 | // It embeds the generic ConfigUsecase to handle the core GetAll/ReplaceAll logic. 13 | type AppUIConfigUsecase struct { 14 | usecase.ConfigUsecase[domain_app_config.AppUIConfig] 15 | } 16 | 17 | // NewAppUIConfigUsecase creates a new usecase for app UI configuration. 18 | // It uses the generic NewConfigUsecase constructor for consistency. 19 | func NewAppUIConfigUsecase(repo repository_app_config.AppUIConfigRepository, timeout time.Duration) domain_app_config.AppUIConfigUsecase { 20 | baseUsecase := usecase.NewConfigUsecase[domain_app_config.AppUIConfig](repo, timeout) 21 | return &AppUIConfigUsecase{ 22 | ConfigUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_app/usecase_app_config/server_config_usercase.go: -------------------------------------------------------------------------------- 1 | package usecase_app_config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // AppServerConfigUsecase implements the usecase interface for app server configuration. 12 | // It embeds the generic BaseUsecase to handle the core CRUD logic. 13 | type AppServerConfigUsecase struct { 14 | usecase.BaseUsecase[domain_app_config.AppServerConfig] 15 | } 16 | 17 | // NewAppServerConfigUsecase creates a new usecase for app server configuration. 18 | // It uses the generic NewBaseUsecase constructor for consistency. 19 | func NewAppServerConfigUsecase(repo repository_app_config.AppServerConfigRepository, timeout time.Duration) domain_app_config.AppServerConfigUsecase { 20 | baseUsecase := usecase.NewBaseUsecase[domain_app_config.AppServerConfig](repo, timeout) 21 | return &AppServerConfigUsecase{ 22 | BaseUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_app/usecase_app_config/audio_config_usercase.go: -------------------------------------------------------------------------------- 1 | package usecase_app_config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // AppAudioConfigUsecase implements the usecase interface for app audio configuration. 12 | // It embeds the generic ConfigUsecase to handle the core GetAll/ReplaceAll logic. 13 | type AppAudioConfigUsecase struct { 14 | usecase.ConfigUsecase[domain_app_config.AppAudioConfig] 15 | } 16 | 17 | // NewAppAudioConfigUsecase creates a new usecase for app audio configuration. 18 | // It uses the generic NewConfigUsecase constructor for consistency. 19 | func NewAppAudioConfigUsecase(repo repository_app_config.AppAudioConfigRepository, timeout time.Duration) domain_app_config.AppAudioConfigUsecase { 20 | baseUsecase := usecase.NewConfigUsecase[domain_app_config.AppAudioConfig](repo, timeout) 21 | return &AppAudioConfigUsecase{ 22 | ConfigUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/domain/domain_auth/user_update.go: -------------------------------------------------------------------------------- 1 | package domain_auth 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | var ( 10 | ErrEmailAlreadyExists = errors.New("email already exists") 11 | ErrInvalidCredentials = errors.New("invalid credentials") 12 | ) 13 | 14 | type ( 15 | UpdateUsernameRequest struct { 16 | Name string `form:"name" binding:"required,min=1,max=20"` 17 | } 18 | 19 | UpdateEmailRequest struct { 20 | Email string `form:"email" binding:"required,email"` 21 | } 22 | 23 | UpdatePasswordRequest struct { 24 | OldPassword string `form:"old_password" binding:"required"` 25 | NewPassword string `form:"new_password" binding:"required"` 26 | } 27 | 28 | UpdateResponse struct { 29 | Message string `form:"message"` 30 | } 31 | ) 32 | 33 | type UpdateUserRepository interface { 34 | UpdateName(ctx context.Context, id primitive.ObjectID, name string) error 35 | UpdateEmail(ctx context.Context, id primitive.ObjectID, email string) error 36 | UpdatePassword(ctx context.Context, id primitive.ObjectID, password string) error 37 | IsEmailTaken(ctx context.Context, email string, excludeID primitive.ObjectID) (bool, error) 38 | } 39 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_app/usecase_app_config/library_config_usercase.go: -------------------------------------------------------------------------------- 1 | package usecase_app_config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // AppLibraryConfigUsecase implements the usecase interface for app library configuration. 12 | // It embeds the generic ConfigUsecase to handle the core GetAll/ReplaceAll logic. 13 | type AppLibraryConfigUsecase struct { 14 | usecase.ConfigUsecase[domain_app_config.AppLibraryConfig] 15 | } 16 | 17 | // NewAppLibraryConfigUsecase creates a new usecase for app library configuration. 18 | // It uses the generic NewConfigUsecase constructor for consistency. 19 | func NewAppLibraryConfigUsecase(repo repository_app_config.AppLibraryConfigRepository, timeout time.Duration) domain_app_config.AppLibraryConfigUsecase { 20 | baseUsecase := usecase.NewConfigUsecase[domain_app_config.AppLibraryConfig](repo, timeout) 21 | return &AppLibraryConfigUsecase{ 22 | ConfigUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_system/system_info_controller.go: -------------------------------------------------------------------------------- 1 | package controller_system 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_system" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | type SystemInfoController struct { 10 | usecase domain_system.SystemInfoUsecase 11 | } 12 | 13 | func NewSystemInfoController(uc domain_system.SystemInfoUsecase) *SystemInfoController { 14 | return &SystemInfoController{usecase: uc} 15 | } 16 | 17 | func (c *SystemInfoController) Get(ctx *gin.Context) { 18 | info, err := c.usecase.Get(ctx) 19 | if err != nil { 20 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 21 | return 22 | } 23 | ctx.JSON(http.StatusOK, info) 24 | } 25 | 26 | func (c *SystemInfoController) Upsert(ctx *gin.Context) { 27 | var req domain_system.SystemInfo 28 | if err := ctx.ShouldBindJSON(&req); err != nil { 29 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | if err := c.usecase.Upsert(ctx, &req); err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | ctx.JSON(http.StatusOK, gin.H{"message": "system info updated"}) 39 | } 40 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_app/usecase_app_library/media_file_library_usercase.go: -------------------------------------------------------------------------------- 1 | package usecase_app_library 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_library" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_library" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // AppMediaFileLibraryUsecase implements the usecase interface for the media file library. 12 | // It embeds the generic ConfigUsecase to handle the core GetAll/ReplaceAll logic. 13 | type AppMediaFileLibraryUsecase struct { 14 | usecase.ConfigUsecase[domain_app_library.AppMediaFileLibrary] 15 | } 16 | 17 | // NewAppMediaFileLibraryUsecase creates a new usecase for the media file library. 18 | // It uses the generic NewConfigUsecase constructor for consistency. 19 | func NewAppMediaFileLibraryUsecase(repo repository_app_library.AppMediaFileLibraryRepository, timeout time.Duration) domain_app_library.AppMediaFileLibraryUsecase { 20 | baseUsecase := usecase.NewConfigUsecase[domain_app_library.AppMediaFileLibrary](repo, timeout) 21 | return &AppMediaFileLibraryUsecase{ 22 | ConfigUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_app/usecase_app_config/playlist_id_config_usercase.go: -------------------------------------------------------------------------------- 1 | package usecase_app_config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_app/repository_app_config" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // AppPlaylistIDConfigUsecase implements the usecase interface for app playlist ID configuration. 12 | // It embeds the generic ConfigUsecase to handle the core GetAll/ReplaceAll logic. 13 | type AppPlaylistIDConfigUsecase struct { 14 | usecase.ConfigUsecase[domain_app_config.AppPlaylistIDConfig] 15 | } 16 | 17 | // NewAppPlaylistIDConfigUsecase creates a new usecase for app playlist ID configuration. 18 | // It uses the generic NewConfigUsecase constructor for consistency. 19 | func NewAppPlaylistIDConfigUsecase(repo repository_app_config.AppPlaylistIDConfigRepository, timeout time.Duration) domain_app_config.AppPlaylistIDConfigUsecase { 20 | baseUsecase := usecase.NewConfigUsecase[domain_app_config.AppPlaylistIDConfig](repo, timeout) 21 | return &AppPlaylistIDConfigUsecase{ 22 | ConfigUsecase: baseUsecase, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_home_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 6 | "time" 7 | 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func NewHomeRouter( 14 | timeout time.Duration, 15 | db mongo.Database, 16 | group *gin.RouterGroup, 17 | ) { 18 | repo := scene_audio_route_repository.NewHomeRepository(db) 19 | uc := scene_audio_route_usecase.NewHomeUsecase(repo, timeout) 20 | ctrl := scene_audio_route_api_controller.NewHomeController(uc) 21 | 22 | router := group.Group("/homes") 23 | { 24 | router.GET("/artists/random", ctrl.GetRandomArtistList) 25 | router.GET("/albums/random", ctrl.GetRandomAlbumList) 26 | router.GET("/medias/random", ctrl.GetRandomMediaFileList) 27 | router.GET("/cues/random", ctrl.GetRandomMediaCueList) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NineSong/.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | testdata_dir = "testdata" 3 | tmp_dir = "tmp" 4 | 5 | [build] 6 | args_bin = [] 7 | bin = "./tmp/main.exe" # 明确指定 Windows 可执行文件路径 8 | cmd = "go build -o ./tmp/main.exe ./cmd/main.go" # 使用 Unix 风格路径 9 | delay = 1000 10 | exclude_dir = ["assets", "tmp", "vendor", "testdata", "0_database_design"] 11 | exclude_file = [] 12 | exclude_regex = ["_test.go"] 13 | exclude_unchanged = false 14 | follow_symlink = false 15 | full_bin = "" 16 | include_dir = [] 17 | include_ext = ["go", "tpl", "tmpl", "html"] 18 | include_file = [] 19 | kill_delay = "0s" 20 | log = "build-errors.log" 21 | poll = false 22 | poll_interval = 0 23 | post_cmd = [] 24 | pre_cmd = [] 25 | rerun = false 26 | rerun_delay = 500 27 | send_interrupt = false 28 | stop_on_error = false 29 | 30 | [run] 31 | cmd = "./tmp/main.exe" 32 | env_file = ".env" 33 | 34 | [color] 35 | app = "" 36 | build = "yellow" 37 | main = "magenta" 38 | runner = "green" 39 | watcher = "cyan" 40 | 41 | [log] 42 | main_only = false 43 | silent = false 44 | time = false 45 | 46 | [misc] 47 | clean_on_exit = false 48 | 49 | [proxy] 50 | app_port = 0 51 | enabled = false 52 | proxy_port = 0 53 | 54 | [screen] 55 | clear_on_rebuild = false 56 | keep_scroll = true 57 | -------------------------------------------------------------------------------- /NineSong/bootstrap/database.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 10 | ) 11 | 12 | func NewMongoDatabase(env *Env) mongo.Client { 13 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 14 | defer cancel() 15 | 16 | dbHost := env.DBHost 17 | dbPort := env.DBPort 18 | dbUser := env.DBUser 19 | dbPass := env.DBPass 20 | 21 | // 使用 MongoDB 容器内部的端口(27017),而不是主机的端口(dbPort),实现自定义数据库外部端口 22 | mongodbURI := fmt.Sprintf("mongodb://%s:%s@%s:%s", dbUser, dbPass, dbHost, "27017") 23 | 24 | if dbUser == "" || dbPass == "" { 25 | mongodbURI = fmt.Sprintf("mongodb://%s:%s", dbHost, dbPort) 26 | } 27 | 28 | client, err := mongo.NewClient(mongodbURI) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | err = client.Connect(ctx) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | 38 | err = client.Ping(ctx) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | return client 44 | } 45 | 46 | func CloseMongoDBConnection(client mongo.Client) { 47 | if client == nil { 48 | return 49 | } 50 | 51 | err := client.Disconnect(context.TODO()) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | log.Println("Connection to MongoDB closed.") 57 | } 58 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_auth/login_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/internal/internal_system/token_util" 7 | "time" 8 | ) 9 | 10 | type loginUsecase struct { 11 | userRepository domain_auth.UserRepository 12 | contextTimeout time.Duration 13 | } 14 | 15 | func NewLoginUsecase(userRepository domain_auth.UserRepository, timeout time.Duration) domain_auth.LoginUsecase { 16 | return &loginUsecase{ 17 | userRepository: userRepository, 18 | contextTimeout: timeout, 19 | } 20 | } 21 | 22 | func (lu *loginUsecase) GetUserByEmail(c context.Context, email string) (domain_auth.User, error) { 23 | ctx, cancel := context.WithTimeout(c, lu.contextTimeout) 24 | defer cancel() 25 | return lu.userRepository.GetByEmail(ctx, email) 26 | } 27 | 28 | func (lu *loginUsecase) CreateAccessToken(user *domain_auth.User, secret string, expiry int) (accessToken string, err error) { 29 | return token_util.CreateAccessToken(user, secret, expiry) 30 | } 31 | 32 | func (lu *loginUsecase) CreateRefreshToken(user *domain_auth.User, secret string, expiry int) (refreshToken string, err error) { 33 | return token_util.CreateRefreshToken(user, secret, expiry) 34 | } 35 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_system/system_configuration_controller.go: -------------------------------------------------------------------------------- 1 | package controller_system 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_system" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | type SystemConfigurationController struct { 10 | usecase domain_system.SystemConfigurationUsecase 11 | } 12 | 13 | func NewSystemConfigurationController(uc domain_system.SystemConfigurationUsecase) *SystemConfigurationController { 14 | return &SystemConfigurationController{usecase: uc} 15 | } 16 | 17 | func (c *SystemConfigurationController) Get(ctx *gin.Context) { 18 | info, err := c.usecase.Get(ctx) 19 | if err != nil { 20 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 21 | return 22 | } 23 | ctx.JSON(http.StatusOK, info) 24 | } 25 | 26 | func (c *SystemConfigurationController) Upsert(ctx *gin.Context) { 27 | var req domain_system.SystemConfiguration 28 | if err := ctx.ShouldBindJSON(&req); err != nil { 29 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | if err := c.usecase.Upsert(ctx, &req); err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | ctx.JSON(http.StatusOK, gin.H{"message": "system info updated"}) 39 | } 40 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_db_api_route/folder_entity_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_db_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewFolderEntityRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { 14 | // 初始化仓库 15 | folderRepo := repository_file_entity.NewFolderRepo(db, domain.CollectionFileEntityFolderInfo) 16 | 17 | // 初始化用例 18 | libraryUsecase := usecase_file_entity.NewLibraryUsecase(folderRepo) 19 | 20 | // 注册控制器 21 | libCtrl := scene_audio_db_api_controller.NewLibraryController(libraryUsecase) 22 | 23 | // 新增路由 24 | group.GET("/folders", libCtrl.BrowseFolders) 25 | group.POST("/libraries", libCtrl.CreateLibrary) 26 | group.PUT("/libraries", libCtrl.UpdateLibrary) 27 | group.DELETE("/libraries", libCtrl.DeleteLibrary) 28 | group.GET("/libraries", libCtrl.GetLibraries) 29 | } 30 | -------------------------------------------------------------------------------- /NineSong/api/route/route_auth/user_update_route.go: -------------------------------------------------------------------------------- 1 | package route_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_auth" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_auth" 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_auth" 10 | "github.com/gin-gonic/gin" 11 | "time" 12 | ) 13 | 14 | func NewUpdateUserRouter( 15 | env *bootstrap.Env, 16 | timeout time.Duration, 17 | db mongo.Database, 18 | router *gin.RouterGroup, 19 | ) { 20 | userRepo := repository_auth.NewUserRepository(db, domain.CollectionUser) 21 | updateRepo := repository_auth.NewUpdateUserRepository(db, domain.CollectionUser) 22 | 23 | updateUsecase := usecase_auth.NewUpdateUsecase( 24 | userRepo, 25 | updateRepo, 26 | timeout, 27 | ) 28 | 29 | updateController := controller_auth.NewUpdateController(updateUsecase) 30 | 31 | authGroup := router.Group("/user") 32 | { 33 | authGroup.POST("/username", updateController.UpdateUsername) 34 | authGroup.POST("/email", updateController.UpdateEmail) 35 | authGroup.POST("/password", updateController.UpdatePassword) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /NineSong/bootstrap/env.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | type Env struct { 10 | AppEnv string `mapstructure:"APP_ENV"` 11 | ServerAddress string `mapstructure:"SERVER_ADDRESS"` 12 | ContextTimeout int `mapstructure:"CONTEXT_TIMEOUT"` 13 | DBHost string `mapstructure:"DB_HOST"` 14 | DBPort string `mapstructure:"DB_PORT"` 15 | DBUser string `mapstructure:"DB_USER"` 16 | DBPass string `mapstructure:"DB_PASS"` 17 | DBName string `mapstructure:"DB_NAME"` 18 | AccessTokenExpiryHour int `mapstructure:"ACCESS_TOKEN_EXPIRY_HOUR"` 19 | RefreshTokenExpiryHour int `mapstructure:"REFRESH_TOKEN_EXPIRY_HOUR"` 20 | AccessTokenSecret string `mapstructure:"ACCESS_TOKEN_SECRET"` 21 | RefreshTokenSecret string `mapstructure:"REFRESH_TOKEN_SECRET"` 22 | } 23 | 24 | func NewEnv() *Env { 25 | env := Env{} 26 | viper.SetConfigFile(".env") 27 | 28 | err := viper.ReadInConfig() 29 | if err != nil { 30 | log.Fatal("Can't find the file .env : ", err) 31 | } 32 | 33 | err = viper.Unmarshal(&env) 34 | if err != nil { 35 | log.Fatal("Environment can't be loaded: ", err) 36 | } 37 | 38 | if env.AppEnv == "development" { 39 | log.Println("The App is running in development env") 40 | } 41 | 42 | return &env 43 | } 44 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_album.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_util" 7 | 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models" 9 | ) 10 | 11 | type AlbumRepository interface { 12 | GetAlbumItems( 13 | ctx context.Context, 14 | start, end, sort, order, 15 | search, starred, 16 | artistId, 17 | minYear, maxYear string, 18 | ) ([]scene_audio_route_models.AlbumMetadata, error) 19 | 20 | GetAlbumMetadataItems( 21 | ctx context.Context, 22 | start, end, sort, order, 23 | search, starred, 24 | artistId, 25 | minYear, maxYear string, 26 | ) ([]scene_audio_db_models.AlbumMetadata, error) 27 | 28 | GetAlbumItemsMultipleSorting( 29 | ctx context.Context, 30 | start, end string, 31 | sortOrder []domain_util.SortOrder, 32 | search, starred, 33 | artistId, 34 | minYear, maxYear string, 35 | ) ([]scene_audio_route_models.AlbumMetadata, error) 36 | 37 | GetAlbumFilterItemsCount( 38 | ctx context.Context, 39 | ) (*scene_audio_route_models.AlbumFilterCounts, error) 40 | } 41 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_album_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func NewAlbumRouter( 15 | timeout time.Duration, 16 | db mongo.Database, 17 | group *gin.RouterGroup, 18 | ) { 19 | repo := scene_audio_route_repository.NewAlbumRepository(db, domain.CollectionFileEntityAudioSceneAlbum) 20 | 21 | usecase := scene_audio_route_usecase.NewAlbumUsecase(repo, timeout) 22 | ctrl := scene_audio_route_api_controller.NewAlbumController(usecase) 23 | 24 | albumGroup := group.Group("/albums") 25 | { 26 | albumGroup.GET("", ctrl.GetAlbumItems) 27 | albumGroup.GET("/metadatas", ctrl.GetAlbumMetadataItems) 28 | albumGroup.GET("/sort", ctrl.GetAlbumItemsMultipleSorting) 29 | albumGroup.GET("/filter_counts", ctrl.GetAlbumFilterCounts) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NineSong/docker-compose-open.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | frontend: 5 | image: nsmusics 6 | container_name: ${APP_CONTAINER_NAME} 7 | restart: unless-stopped 8 | env_file: .env 9 | environment: 10 | - NGINX_PORT=${APP_PORT} 11 | - BACKEND_SERVICE=${WEB_CONTAINER_NAME}:${SERVER_PORT} 12 | ports: 13 | - "${APP_PORT}:${APP_PORT}" 14 | volumes: 15 | - ./.env:/app/.env 16 | depends_on: 17 | - backend 18 | - mongodb 19 | 20 | backend: 21 | image: ninesong 22 | container_name: ${WEB_CONTAINER_NAME} 23 | restart: unless-stopped 24 | env_file: .env 25 | ports: 26 | - "${SERVER_PORT}:${SERVER_PORT}" 27 | volumes: 28 | - ./.env:/app/.env 29 | - "library:/data/library" 30 | depends_on: 31 | - mongodb 32 | 33 | mongodb: 34 | image: mongo:6.0 35 | container_name: ${MONGO_CONTAINER_NAME} 36 | restart: unless-stopped 37 | env_file: .env 38 | environment: 39 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 40 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASS} 41 | ports: 42 | - "${DB_PORT}:27017" 43 | volumes: 44 | - DB_DATA:/data/db 45 | healthcheck: 46 | test: echo 'db.runCommand("ping").ok' | mongosh --quiet -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --authenticationDatabase admin | grep 1 47 | interval: 10s 48 | timeout: 5s 49 | retries: 5 50 | 51 | volumes: 52 | library: 53 | DB_DATA: -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_retrieval_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 8 | "github.com/gin-gonic/gin" 9 | "time" 10 | ) 11 | 12 | func NewRetrievalRouter( 13 | timeout time.Duration, 14 | db mongo.Database, 15 | group *gin.RouterGroup, 16 | ) { 17 | repo := scene_audio_route_repository.NewRetrievalRepository(db) 18 | uc := scene_audio_route_usecase.NewRetrievalUsecase(repo, timeout) 19 | ctrl := scene_audio_route_api_controller.NewRetrievalController(uc) 20 | 21 | retrievalGroup := group.Group("/media") 22 | { 23 | retrievalGroup.GET("/stream", ctrl.FixedStreamHandler) 24 | retrievalGroup.GET("/stream/real", ctrl.RealStreamHandler) 25 | retrievalGroup.GET("/download", ctrl.DownloadHandler) 26 | retrievalGroup.GET("/cover", ctrl.CoverArtIDHandler) 27 | retrievalGroup.GET("/cover/path", ctrl.CoverArtPathHandler) 28 | retrievalGroup.GET("/lyrics", ctrl.LyricsHandlerMetadata) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_annotation_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 6 | "time" 7 | 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func NewAnnotationRouter( 14 | timeout time.Duration, 15 | db mongo.Database, 16 | group *gin.RouterGroup, 17 | ) { 18 | repo := scene_audio_route_repository.NewAnnotationRepository(db) 19 | uc := scene_audio_route_usecase.NewAnnotationUsecase(repo, timeout) 20 | ctrl := scene_audio_route_api_controller.NewAnnotationController(uc) 21 | 22 | router := group.Group("/annotations") 23 | { 24 | router.POST("/star", ctrl.UpdateStarred) 25 | router.POST("/unstar", ctrl.UpdateUnStarred) 26 | router.POST("/rating", ctrl.UpdateRating) 27 | router.POST("/scrobble", ctrl.UpdateScrobble) 28 | router.POST("/scrobble/complete", ctrl.UpdateCompleteScrobble) 29 | router.POST("/tags", ctrl.UpdateTagSource) 30 | router.POST("/weights", ctrl.UpdateWeightedTag) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_playlist_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewPlaylistRouter( 14 | timeout time.Duration, 15 | db mongo.Database, 16 | group *gin.RouterGroup, 17 | ) { 18 | repo := scene_audio_route_repository.NewPlaylistRepository(db, domain.CollectionFileEntityAudioScenePlaylist) 19 | usecase := scene_audio_route_usecase.NewPlaylistUsecase(repo, timeout) 20 | ctrl := scene_audio_route_api_controller.NewPlaylistController(usecase) 21 | 22 | playlistGroup := group.Group("/playlists") 23 | { 24 | playlistGroup.GET("", ctrl.GetPlaylists) 25 | playlistGroup.POST("", ctrl.CreatePlaylist) 26 | playlistGroup.GET("/detail", ctrl.GetPlaylist) 27 | playlistGroup.PUT("", ctrl.UpdatePlaylist) 28 | playlistGroup.DELETE("", ctrl.DeletePlaylist) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_artist.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_util" 8 | 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models" 10 | ) 11 | 12 | type ArtistRepository interface { 13 | GetArtistItems( 14 | ctx context.Context, 15 | start, end, sort, order, 16 | search, starred string, 17 | ) ([]scene_audio_route_models.ArtistMetadata, error) 18 | 19 | GetArtistMetadataItems( 20 | ctx context.Context, 21 | start, end, sort, order, 22 | search, starred string, 23 | ) ([]scene_audio_db_models.ArtistMetadata, error) 24 | 25 | GetArtistItemsMultipleSorting( 26 | ctx context.Context, 27 | start, end string, 28 | sortOrder []domain_util.SortOrder, 29 | search, starred string, 30 | ) ([]scene_audio_route_models.ArtistMetadata, error) 31 | 32 | GetArtistFilterItemsCount( 33 | ctx context.Context, 34 | ) (*scene_audio_route_models.ArtistFilterCounts, error) 35 | 36 | GetArtistTreeItems( 37 | ctx context.Context, 38 | start, end, artistId string, 39 | ) ([]scene_audio_route_models.ArtistTreeMetadata, error) 40 | } 41 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_media_file_cue.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_util" 8 | ) 9 | 10 | type MediaFileCueRepository interface { 11 | GetMediaFileCueItems( 12 | ctx context.Context, 13 | start, end, sort, order, 14 | search, starred, 15 | albumId, artistId, 16 | year string, 17 | ) ([]scene_audio_route_models.MediaFileCueMetadata, error) 18 | 19 | GetMediaFileCueMetadataItems( 20 | ctx context.Context, 21 | start, end, sort, order, 22 | search, starred, 23 | albumId, artistId, 24 | year string, 25 | ) ([]scene_audio_db_models.MediaFileCueMetadata, error) 26 | 27 | GetMediaFileCueItemsMultipleSorting( 28 | ctx context.Context, 29 | start, end string, 30 | sortOrder []domain_util.SortOrder, 31 | search, starred, 32 | albumId, artistId, 33 | year string, 34 | ) ([]scene_audio_route_models.MediaFileCueMetadata, error) 35 | 36 | GetMediaFileCueFilterItemsCount( 37 | ctx context.Context, 38 | ) (*scene_audio_route_models.MediaFileCueFilterCounts, error) 39 | } 40 | -------------------------------------------------------------------------------- /NineSong/repository/repository_auth/task_repository.go: -------------------------------------------------------------------------------- 1 | package repository_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 8 | "go.mongodb.org/mongo-driver/bson" 9 | "go.mongodb.org/mongo-driver/bson/primitive" 10 | ) 11 | 12 | type taskRepository struct { 13 | database mongo.Database 14 | collection string 15 | } 16 | 17 | func NewTaskRepository(db mongo.Database, collection string) domain_auth.TaskRepository { 18 | return &taskRepository{ 19 | database: db, 20 | collection: collection, 21 | } 22 | } 23 | 24 | func (tr *taskRepository) Create(c context.Context, task *domain_auth.Task) error { 25 | collection := tr.database.Collection(tr.collection) 26 | 27 | _, err := collection.InsertOne(c, task) 28 | 29 | return err 30 | } 31 | 32 | func (tr *taskRepository) FetchByUserID(c context.Context, userID string) ([]domain_auth.Task, error) { 33 | collection := tr.database.Collection(tr.collection) 34 | 35 | var tasks []domain_auth.Task 36 | 37 | idHex, err := primitive.ObjectIDFromHex(userID) 38 | if err != nil { 39 | return tasks, err 40 | } 41 | 42 | cursor, err := collection.Find(c, bson.M{"userID": idHex}) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | err = cursor.All(c, &tasks) 48 | if tasks == nil { 49 | return []domain_auth.Task{}, err 50 | } 51 | 52 | return tasks, err 53 | } 54 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_artist_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 10 | 11 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func NewArtistRouter( 16 | timeout time.Duration, 17 | db mongo.Database, 18 | group *gin.RouterGroup, 19 | ) { 20 | repo := scene_audio_route_repository.NewArtistRepository(db, domain.CollectionFileEntityAudioSceneArtist) 21 | 22 | usecase := scene_audio_route_usecase.NewArtistUsecase(repo, timeout) 23 | ctrl := scene_audio_route_api_controller.NewArtistController(usecase) 24 | 25 | artistGroup := group.Group("/artists") 26 | { 27 | artistGroup.GET("", ctrl.GetArtists) 28 | artistGroup.GET("/metadatas", ctrl.GetArtistMetadatas) 29 | artistGroup.GET("/sort", ctrl.GetArtistsMultipleSorting) 30 | artistGroup.GET("/filter_counts", ctrl.GetArtistFilterCounts) 31 | artistGroup.GET("/tree", ctrl.GetArtistTrees) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_media_file_cue_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func NewMediaFileCueRouter( 15 | timeout time.Duration, 16 | db mongo.Database, 17 | group *gin.RouterGroup, 18 | ) { 19 | repo := scene_audio_route_repository.NewMediaFileCueRepository(db, domain.CollectionFileEntityAudioSceneMediaFileCue) 20 | usecase := scene_audio_route_usecase.NewMediaFileCueUsecase(repo, timeout) 21 | ctrl := scene_audio_route_api_controller.NewMediaFileCueController(usecase) 22 | 23 | mediaCueGroup := group.Group("/cues") 24 | { 25 | mediaCueGroup.GET("", ctrl.GetMediaFileCues) 26 | mediaCueGroup.GET("/metadatas", ctrl.GetMediaFileMetadataCues) 27 | mediaCueGroup.GET("/sort", ctrl.GetMediaFileCuesMultipleSorting) 28 | mediaCueGroup.GET("/filter_counts", ctrl.GetMediaCueFilterCounts) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_media_file_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 8 | "time" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func NewMediaFileRouter( 15 | timeout time.Duration, 16 | db mongo.Database, 17 | group *gin.RouterGroup, 18 | ) { 19 | repo := scene_audio_route_repository.NewMediaFileRepository(db, domain.CollectionFileEntityAudioSceneMediaFile) 20 | usecase := scene_audio_route_usecase.NewMediaFileUsecase(repo, timeout) 21 | ctrl := scene_audio_route_api_controller.NewMediaFileController(usecase) 22 | 23 | mediaGroup := group.Group("/medias") 24 | { 25 | mediaGroup.GET("", ctrl.GetMediaFiles) 26 | mediaGroup.GET("/metadatas", ctrl.GetMediaFileMetadatas) 27 | mediaGroup.GET("/ids", ctrl.GetMediaFileIds) 28 | mediaGroup.GET("/sort", ctrl.GetMediaFilesMultipleSorting) 29 | mediaGroup.GET("/filter_counts", ctrl.GetMediaFilterCounts) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_system/system_configuration_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase_system 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_system" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_system" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase" 9 | ) 10 | 11 | // systemConfigurationUsecase implements the usecase interface for system configuration. 12 | // It embeds the generic ConfigUsecase to handle the core Get/Upsert logic. 13 | // This approach reduces boilerplate code and centralizes the common logic in base_usecase.go. 14 | type systemConfigurationUsecase struct { 15 | usecase.ConfigUsecase[domain_system.SystemConfiguration] 16 | } 17 | 18 | // NewSystemConfigurationUsecase creates a new usecase for system configuration. 19 | // It leverages the generic NewConfigUsecase constructor to wire up the repository 20 | // and timeout, promoting code reuse and consistency. 21 | func NewSystemConfigurationUsecase(repo repository_system.SystemConfigurationRepository, timeout time.Duration) domain_system.SystemConfigurationUsecase { 22 | // We use the generic constructor from the usecase package to create the base usecase. 23 | // This ensures that all standard CRUD logic is handled consistently. 24 | baseUsecase := usecase.NewConfigUsecase[domain_system.SystemConfiguration](repo, timeout) 25 | 26 | return &systemConfigurationUsecase{ 27 | ConfigUsecase: baseUsecase, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_media_track.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type MediaTrackMetadata struct { 6 | ID primitive.ObjectID `bson:"_id"` // 音轨唯一标识[8](@ref) 7 | Codec string `bson:"codec"` // 编码格式(AAC/Opus/FLAC)[4,8](@ref) 8 | BitDepth int `bson:"bit_depth"` // 量化位深(16/24/32bit)[2,6](@ref) 9 | SampleRate int `bson:"sample_rate"` // 采样率(44.1k/48k/96kHz)[2,8](@ref) 10 | Channels int `bson:"channels"` // 声道配置(1单声道/2立体声/5.1环绕)[2,6](@ref) 11 | Bitrate int `bson:"bitrate"` // 传输比特率(64-320kbps)[4,8](@ref) 12 | Pan float64 `bson:"pan"` // 声像定位(-1.0左~1.0右)[3,5](@ref) 13 | Gain float64 `bson:"gain"` // 增益补偿(-12dB~+12dB)[5,6](@ref) 14 | Latency int `bson:"latency"` // 传输延迟(ms)[8](@ref) 15 | Timestamp int64 `bson:"timestamp"` // 时间戳(μs精度)[8](@ref) 16 | SyncMarkers []int `bson:"sync_markers"` // 同步标记点[8](@ref) 17 | IsActive bool `bson:"is_active"` // 激活状态[5](@ref) 18 | Transmission struct { 19 | Protocol string `bson:"protocol"` // 传输协议(RTP/MADI/CobraNet)[7,8](@ref) 20 | PacketSize int `bson:"packet_size"` // 数据包大小(128-2048字节)[8](@ref) 21 | CRC string `bson:"crc"` // 校验算法(CRC16/CRC32)[8](@ref) 22 | } `bson:"transmission"` 23 | } 24 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_app/controller_app_config/app_config_controller.go: -------------------------------------------------------------------------------- 1 | package controller_app_config 2 | 3 | import ( 4 | "errors" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | type AppConfigController struct { 12 | usecase domain_app_config.AppConfigUsecase 13 | } 14 | 15 | func NewAppConfigController(uc domain_app_config.AppConfigUsecase) *AppConfigController { 16 | return &AppConfigController{usecase: uc} 17 | } 18 | 19 | func (ctrl *AppConfigController) ReplaceAll(c *gin.Context) { 20 | var req []*domain_app_config.AppConfig 21 | if err := c.ShouldBindJSON(&req); err != nil { 22 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"}) 23 | return 24 | } 25 | 26 | if err := ctrl.usecase.ReplaceAll(c.Request.Context(), req); err != nil { 27 | c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"}) 28 | return 29 | } 30 | c.JSON(http.StatusOK, gin.H{"message": "app config updated"}) 31 | } 32 | 33 | func (ctrl *AppConfigController) GetAll(c *gin.Context) { 34 | configs, err := ctrl.usecase.GetAll(c.Request.Context()) 35 | if err != nil { 36 | if errors.Is(err, domain.ErrEmptyCollection) { 37 | c.JSON(http.StatusOK, []interface{}{}) 38 | return 39 | } 40 | c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) 41 | return 42 | } 43 | c.JSON(http.StatusOK, configs) 44 | } 45 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_auth/refresh_token_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/internal/internal_system/token_util" 7 | "time" 8 | ) 9 | 10 | type refreshTokenUsecase struct { 11 | userRepository domain_auth.UserRepository 12 | contextTimeout time.Duration 13 | } 14 | 15 | func NewRefreshTokenUsecase(userRepository domain_auth.UserRepository, timeout time.Duration) domain_auth.RefreshTokenUsecase { 16 | return &refreshTokenUsecase{ 17 | userRepository: userRepository, 18 | contextTimeout: timeout, 19 | } 20 | } 21 | 22 | func (rtu *refreshTokenUsecase) GetUserByID(c context.Context, email string) (domain_auth.User, error) { 23 | ctx, cancel := context.WithTimeout(c, rtu.contextTimeout) 24 | defer cancel() 25 | return rtu.userRepository.GetByID(ctx, email) 26 | } 27 | 28 | func (rtu *refreshTokenUsecase) CreateAccessToken(user *domain_auth.User, secret string, expiry int) (accessToken string, err error) { 29 | return token_util.CreateAccessToken(user, secret, expiry) 30 | } 31 | 32 | func (rtu *refreshTokenUsecase) CreateRefreshToken(user *domain_auth.User, secret string, expiry int) (refreshToken string, err error) { 33 | return token_util.CreateRefreshToken(user, secret, expiry) 34 | } 35 | 36 | func (rtu *refreshTokenUsecase) ExtractIDFromToken(requestToken string, secret string) (string, error) { 37 | return token_util.ExtractIDFromToken(requestToken, secret) 38 | } 39 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_auth/signup_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/internal/internal_system/token_util" 7 | "time" 8 | ) 9 | 10 | type signupUsecase struct { 11 | userRepository domain_auth.UserRepository 12 | contextTimeout time.Duration 13 | } 14 | 15 | func NewSignupUsecase(userRepository domain_auth.UserRepository, timeout time.Duration) domain_auth.SignupUsecase { 16 | return &signupUsecase{ 17 | userRepository: userRepository, 18 | contextTimeout: timeout, 19 | } 20 | } 21 | 22 | func (su *signupUsecase) Create(c context.Context, user *domain_auth.User) error { 23 | ctx, cancel := context.WithTimeout(c, su.contextTimeout) 24 | defer cancel() 25 | return su.userRepository.Create(ctx, user) 26 | } 27 | 28 | func (su *signupUsecase) GetUserByEmail(c context.Context, email string) (domain_auth.User, error) { 29 | ctx, cancel := context.WithTimeout(c, su.contextTimeout) 30 | defer cancel() 31 | return su.userRepository.GetByEmail(ctx, email) 32 | } 33 | 34 | func (su *signupUsecase) CreateAccessToken(user *domain_auth.User, secret string, expiry int) (accessToken string, err error) { 35 | return token_util.CreateAccessToken(user, secret, expiry) 36 | } 37 | 38 | func (su *signupUsecase) CreateRefreshToken(user *domain_auth.User, secret string, expiry int) (refreshToken string, err error) { 39 | return token_util.CreateRefreshToken(user, secret, expiry) 40 | } 41 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_app/controller_app_config/ui_config_controller.go: -------------------------------------------------------------------------------- 1 | package controller_app_config 2 | 3 | import ( 4 | "errors" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | type AppUIConfigController struct { 12 | usecase domain_app_config.AppUIConfigUsecase 13 | } 14 | 15 | func NewAppUIConfigController(uc domain_app_config.AppUIConfigUsecase) *AppUIConfigController { 16 | return &AppUIConfigController{usecase: uc} 17 | } 18 | 19 | func (ctrl *AppUIConfigController) ReplaceAll(c *gin.Context) { 20 | var req []*domain_app_config.AppUIConfig 21 | if err := c.ShouldBindJSON(&req); err != nil { 22 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"}) 23 | return 24 | } 25 | 26 | if err := ctrl.usecase.ReplaceAll(c.Request.Context(), req); err != nil { 27 | c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"}) 28 | return 29 | } 30 | c.JSON(http.StatusOK, gin.H{"message": "app config updated"}) 31 | } 32 | 33 | func (ctrl *AppUIConfigController) GetAll(c *gin.Context) { 34 | configs, err := ctrl.usecase.GetAll(c.Request.Context()) 35 | if err != nil { 36 | if errors.Is(err, domain.ErrEmptyCollection) { 37 | c.JSON(http.StatusOK, []interface{}{}) 38 | return 39 | } 40 | c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) 41 | return 42 | } 43 | c.JSON(http.StatusOK, configs) 44 | } 45 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_recommend.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/bson/primitive" 5 | ) 6 | 7 | // RecommendationResult 推荐结果 8 | // 结合用户行为数据(播放次数、评分、收藏)和内容数据(词云标签)生成的推荐结果 9 | type RecommendationResult struct { 10 | ID primitive.ObjectID `bson:"_id"` 11 | ItemID string `bson:"item_id"` // 推荐项目的ID 12 | ItemType string `bson:"item_type"` // 推荐项目类型: media_file, media_file_cue, album, artist 13 | Name string `bson:"name"` // 项目名称 14 | Score float64 `bson:"score"` // 综合推荐分数 15 | Reason string `bson:"reason"` // 推荐理由 16 | PlayCount int `bson:"play_count"` // 播放次数 17 | Rating int `bson:"rating"` // 评分 18 | Starred bool `bson:"starred"` // 是否收藏 19 | Algorithm string `bson:"algorithm"` // 使用的推荐算法 20 | Parameters map[string]string `bson:"parameters"` // 使用的参数 21 | Basis []string `bson:"basis"` // 推荐依据(基于哪些数据) 22 | AnnotationBasis []AnnotationMetadata `bson:"annotation_basis"` // 基于的注释信息 23 | TagBasis []WordCloudMetadata `bson:"tag_basis"` // 基于的标签信息 24 | RelatedItems []WordCloudRecommendation `bson:"related_items"` // 相关项目信息 25 | } 26 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_app/controller_app_config/audio_config_controller.go: -------------------------------------------------------------------------------- 1 | package controller_app_config 2 | 3 | import ( 4 | "errors" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | type AppAudioConfigController struct { 12 | usecase domain_app_config.AppAudioConfigUsecase 13 | } 14 | 15 | func NewAppAudioConfigController(uc domain_app_config.AppAudioConfigUsecase) *AppAudioConfigController { 16 | return &AppAudioConfigController{usecase: uc} 17 | } 18 | 19 | func (ctrl *AppAudioConfigController) ReplaceAll(c *gin.Context) { 20 | var req []*domain_app_config.AppAudioConfig 21 | if err := c.ShouldBindJSON(&req); err != nil { 22 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"}) 23 | return 24 | } 25 | 26 | if err := ctrl.usecase.ReplaceAll(c.Request.Context(), req); err != nil { 27 | c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"}) 28 | return 29 | } 30 | c.JSON(http.StatusOK, gin.H{"message": "app config updated"}) 31 | } 32 | 33 | func (ctrl *AppAudioConfigController) GetAll(c *gin.Context) { 34 | configs, err := ctrl.usecase.GetAll(c.Request.Context()) 35 | if err != nil { 36 | if errors.Is(err, domain.ErrEmptyCollection) { 37 | c.JSON(http.StatusOK, []interface{}{}) 38 | return 39 | } 40 | c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) 41 | return 42 | } 43 | c.JSON(http.StatusOK, configs) 44 | } 45 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_auth/task_controller.go: -------------------------------------------------------------------------------- 1 | package controller_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | "go.mongodb.org/mongo-driver/bson/primitive" 10 | ) 11 | 12 | type TaskController struct { 13 | TaskUsecase domain_auth.TaskUsecase 14 | } 15 | 16 | func (tc *TaskController) Create(c *gin.Context) { 17 | var task domain_auth.Task 18 | 19 | err := c.ShouldBind(&task) 20 | if err != nil { 21 | c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) 22 | return 23 | } 24 | 25 | userID := c.GetString("x-user-id") 26 | task.ID = primitive.NewObjectID() 27 | 28 | task.UserID, err = primitive.ObjectIDFromHex(userID) 29 | if err != nil { 30 | c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) 31 | return 32 | } 33 | 34 | err = tc.TaskUsecase.Create(c, &task) 35 | if err != nil { 36 | c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) 37 | return 38 | } 39 | 40 | c.JSON(http.StatusOK, domain.SuccessResponse{ 41 | Message: "Task created successfully", 42 | }) 43 | } 44 | 45 | func (u *TaskController) Fetch(c *gin.Context) { 46 | userID := c.GetString("x-user-id") 47 | 48 | tasks, err := u.TaskUsecase.FetchByUserID(c, userID) 49 | if err != nil { 50 | c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) 51 | return 52 | } 53 | 54 | c.JSON(http.StatusOK, tasks) 55 | } 56 | -------------------------------------------------------------------------------- /NineSong/docker-compose-local-develop-init.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | frontend: 5 | image: nsmusics 6 | container_name: ${APP_CONTAINER_NAME} 7 | restart: unless-stopped 8 | env_file: .env 9 | environment: 10 | - NGINX_PORT=${APP_PORT} 11 | - BACKEND_SERVICE=${WEB_CONTAINER_NAME}:${SERVER_PORT} 12 | ports: 13 | - "${APP_PORT}:${APP_PORT}" 14 | volumes: 15 | - ./.env:/app/.env 16 | depends_on: 17 | - backend 18 | - mongodb 19 | 20 | backend: 21 | build: 22 | context: . 23 | dockerfile: Dockerfile 24 | image: ninesong 25 | container_name: ${WEB_CONTAINER_NAME} 26 | restart: unless-stopped 27 | env_file: .env 28 | ports: 29 | - "${SERVER_PORT}:${SERVER_PORT}" 30 | volumes: 31 | - ./.env:/app/.env 32 | - "E:/0_Music:/data/library" 33 | depends_on: 34 | - mongodb 35 | 36 | mongodb: 37 | image: mongo:6.0 38 | container_name: ${MONGO_CONTAINER_NAME} 39 | restart: unless-stopped 40 | env_file: .env 41 | environment: 42 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 43 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASS} 44 | ports: 45 | - "${DB_PORT}:27017" 46 | volumes: 47 | - C:/Users/Public/Documents/NineSong/MongoDB:/data/db 48 | healthcheck: 49 | test: echo 'db.runCommand("ping").ok' | mongosh --quiet -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --authenticationDatabase admin | grep 1 50 | interval: 10s 51 | timeout: 5s 52 | retries: 5 53 | 54 | volumes: 55 | library: 56 | DB_DATA: -------------------------------------------------------------------------------- /NineSong/api/controller/controller_app/controller_app_config/library_config_controller.go: -------------------------------------------------------------------------------- 1 | package controller_app_config 2 | 3 | import ( 4 | "errors" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | type AppLibraryConfigController struct { 12 | usecase domain_app_config.AppLibraryConfigUsecase 13 | } 14 | 15 | func NewAppLibraryConfigController(uc domain_app_config.AppLibraryConfigUsecase) *AppLibraryConfigController { 16 | return &AppLibraryConfigController{usecase: uc} 17 | } 18 | 19 | func (ctrl *AppLibraryConfigController) ReplaceAll(c *gin.Context) { 20 | var req []*domain_app_config.AppLibraryConfig 21 | if err := c.ShouldBindJSON(&req); err != nil { 22 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"}) 23 | return 24 | } 25 | 26 | if err := ctrl.usecase.ReplaceAll(c.Request.Context(), req); err != nil { 27 | c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"}) 28 | return 29 | } 30 | c.JSON(http.StatusOK, gin.H{"message": "app config updated"}) 31 | } 32 | 33 | func (ctrl *AppLibraryConfigController) GetAll(c *gin.Context) { 34 | configs, err := ctrl.usecase.GetAll(c.Request.Context()) 35 | if err != nil { 36 | if errors.Is(err, domain.ErrEmptyCollection) { 37 | c.JSON(http.StatusOK, []interface{}{}) 38 | return 39 | } 40 | c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) 41 | return 42 | } 43 | c.JSON(http.StatusOK, configs) 44 | } 45 | -------------------------------------------------------------------------------- /NineSong/domain/mocks/ProfileUsecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.16.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | domain "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 8 | 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // ProfileUsecase is an autogenerated mock type for the ProfileUsecase type 13 | type ProfileUsecase struct { 14 | mock.Mock 15 | } 16 | 17 | // GetProfileByID provides a mock function with given fields: c, userID 18 | func (_m *ProfileUsecase) GetProfileByID(c context.Context, userID string) (*domain.Profile, error) { 19 | ret := _m.Called(c, userID) 20 | 21 | var r0 *domain.Profile 22 | if rf, ok := ret.Get(0).(func(context.Context, string) *domain.Profile); ok { 23 | r0 = rf(c, userID) 24 | } else { 25 | if ret.Get(0) != nil { 26 | r0 = ret.Get(0).(*domain.Profile) 27 | } 28 | } 29 | 30 | var r1 error 31 | if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { 32 | r1 = rf(c, userID) 33 | } else { 34 | r1 = ret.Error(1) 35 | } 36 | 37 | return r0, r1 38 | } 39 | 40 | type mockConstructorTestingTNewProfileUsecase interface { 41 | mock.TestingT 42 | Cleanup(func()) 43 | } 44 | 45 | // NewProfileUsecase creates a new instance of ProfileUsecase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 46 | func NewProfileUsecase(t mockConstructorTestingTNewProfileUsecase) *ProfileUsecase { 47 | mock := &ProfileUsecase{} 48 | mock.Mock.Test(t) 49 | 50 | t.Cleanup(func() { mock.AssertExpectations(t) }) 51 | 52 | return mock 53 | } 54 | -------------------------------------------------------------------------------- /NineSong/mongo/mocks/Database.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.16.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | mongo "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | mock "github.com/stretchr/testify/mock" 8 | ) 9 | 10 | // Database is an autogenerated mock type for the Database type 11 | type Database struct { 12 | mock.Mock 13 | } 14 | 15 | // Client provides a mock function with given fields: 16 | func (_m *Database) Client() mongo.Client { 17 | ret := _m.Called() 18 | 19 | var r0 mongo.Client 20 | if rf, ok := ret.Get(0).(func() mongo.Client); ok { 21 | r0 = rf() 22 | } else { 23 | if ret.Get(0) != nil { 24 | r0 = ret.Get(0).(mongo.Client) 25 | } 26 | } 27 | 28 | return r0 29 | } 30 | 31 | // Collection provides a mock function with given fields: _a0 32 | func (_m *Database) Collection(_a0 string) mongo.Collection { 33 | ret := _m.Called(_a0) 34 | 35 | var r0 mongo.Collection 36 | if rf, ok := ret.Get(0).(func(string) mongo.Collection); ok { 37 | r0 = rf(_a0) 38 | } else { 39 | if ret.Get(0) != nil { 40 | r0 = ret.Get(0).(mongo.Collection) 41 | } 42 | } 43 | 44 | return r0 45 | } 46 | 47 | type mockConstructorTestingTNewDatabase interface { 48 | mock.TestingT 49 | Cleanup(func()) 50 | } 51 | 52 | // NewDatabase creates a new instance of Database. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 53 | func NewDatabase(t mockConstructorTestingTNewDatabase) *Database { 54 | mock := &Database{} 55 | mock.Mock.Test(t) 56 | 57 | t.Cleanup(func() { mock.AssertExpectations(t) }) 58 | 59 | return mock 60 | } 61 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_app/controller_app_config/playlist_id_config_controller.go: -------------------------------------------------------------------------------- 1 | package controller_app_config 2 | 3 | import ( 4 | "errors" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_config" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | type AppPlaylistIDConfigController struct { 12 | usecase domain_app_config.AppPlaylistIDConfigUsecase 13 | } 14 | 15 | func NewAppPlaylistIDConfigController(uc domain_app_config.AppPlaylistIDConfigUsecase) *AppPlaylistIDConfigController { 16 | return &AppPlaylistIDConfigController{usecase: uc} 17 | } 18 | 19 | func (ctrl *AppPlaylistIDConfigController) ReplaceAll(c *gin.Context) { 20 | var req []*domain_app_config.AppPlaylistIDConfig 21 | if err := c.ShouldBindJSON(&req); err != nil { 22 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"}) 23 | return 24 | } 25 | 26 | if err := ctrl.usecase.ReplaceAll(c.Request.Context(), req); err != nil { 27 | c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"}) 28 | return 29 | } 30 | c.JSON(http.StatusOK, gin.H{"message": "app config updated"}) 31 | } 32 | 33 | func (ctrl *AppPlaylistIDConfigController) GetAll(c *gin.Context) { 34 | configs, err := ctrl.usecase.GetAll(c.Request.Context()) 35 | if err != nil { 36 | if errors.Is(err, domain.ErrEmptyCollection) { 37 | c.JSON(http.StatusOK, []interface{}{}) 38 | return 39 | } 40 | c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) 41 | return 42 | } 43 | c.JSON(http.StatusOK, configs) 44 | } 45 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_playlist_track_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewPlaylistTrackRouter( 14 | timeout time.Duration, 15 | db mongo.Database, 16 | group *gin.RouterGroup, 17 | ) { 18 | repo := scene_audio_route_repository.NewPlaylistTrackRepository(db, domain.CollectionFileEntityAudioScenePlaylistTrack) 19 | usecase := scene_audio_route_usecase.NewPlaylistTrackUsecase(repo, timeout) 20 | ctrl := scene_audio_route_api_controller.NewPlaylistTrackController(usecase) 21 | 22 | playlistTrackGroup := group.Group("/playlists/tracks") 23 | { 24 | playlistTrackGroup.GET("", ctrl.GetPlaylistTracks) 25 | playlistTrackGroup.GET("/sort", ctrl.GetPlaylistTracksMultipleSorting) 26 | playlistTrackGroup.GET("/filter_counts", ctrl.GetPlaylistFilterCounts) 27 | playlistTrackGroup.POST("/add", ctrl.AddPlaylistTracks) 28 | playlistTrackGroup.POST("/remove", ctrl.RemovePlaylistTracks) 29 | playlistTrackGroup.PUT("/sort", ctrl.SortPlaylistTracks) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_app/controller_app_library/media_file_library_controller.go: -------------------------------------------------------------------------------- 1 | package controller_app_library 2 | 3 | import ( 4 | "errors" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_app/domain_app_library" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | type AppMediaFileLibraryController struct { 12 | usecase domain_app_library.AppMediaFileLibraryUsecase 13 | } 14 | 15 | func NewAppMediaFileLibraryController(uc domain_app_library.AppMediaFileLibraryUsecase) *AppMediaFileLibraryController { 16 | return &AppMediaFileLibraryController{usecase: uc} 17 | } 18 | 19 | func (ctrl *AppMediaFileLibraryController) ReplaceAll(c *gin.Context) { 20 | var req []*domain_app_library.AppMediaFileLibrary 21 | if err := c.ShouldBindJSON(&req); err != nil { 22 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"}) 23 | return 24 | } 25 | 26 | if err := ctrl.usecase.ReplaceAll(c.Request.Context(), req); err != nil { 27 | c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"}) 28 | return 29 | } 30 | c.JSON(http.StatusOK, gin.H{"message": "app config updated"}) 31 | } 32 | 33 | func (ctrl *AppMediaFileLibraryController) GetAll(c *gin.Context) { 34 | configs, err := ctrl.usecase.GetAll(c.Request.Context()) 35 | if err != nil { 36 | if errors.Is(err, domain.ErrEmptyCollection) { 37 | c.JSON(http.StatusOK, []interface{}{}) 38 | return 39 | } 40 | c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) 41 | return 42 | } 43 | c.JSON(http.StatusOK, configs) 44 | } 45 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_word_cloud_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_db_repository" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 9 | "github.com/gin-gonic/gin" 10 | "time" 11 | ) 12 | 13 | func NewWordCloudRouter( 14 | timeout time.Duration, 15 | db mongo.Database, 16 | group *gin.RouterGroup, 17 | ) { 18 | repoMediaFileDB := scene_audio_db_repository.NewMediaFileRepository(db, domain.CollectionFileEntityAudioSceneMediaFile) 19 | repoMediaFileWordCloud := scene_audio_db_repository.NewWordCloudRepository(db, domain.CollectionFileEntityAudioSceneMediaFileWordCloud) 20 | usecase := scene_audio_route_usecase.NewWordCloudUsecase(repoMediaFileDB, repoMediaFileWordCloud, timeout) 21 | ctrl := scene_audio_route_api_controller.NewWordCloudController(usecase) 22 | 23 | wordCloudGroup := group.Group("/word_cloud") 24 | { 25 | wordCloudGroup.GET("", ctrl.GetAllWordCloudHandler) 26 | wordCloudGroup.GET("genre", ctrl.GetAllGenreHandler) 27 | wordCloudGroup.GET("high", ctrl.GetHighFrequencyWordCloudHandler) 28 | wordCloudGroup.POST("recommend", ctrl.GetRecommendedWordCloudHandler) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models/model_retrieval.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_models 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type RetrievalStreamMetadata struct { 10 | ID primitive.ObjectID `bson:"_id"` 11 | Name string `bson:"name"` 12 | Duration float64 `bson:"duration"` 13 | CreatedAt time.Time `bson:"created_at"` 14 | UpdatedAt time.Time `bson:"updated_at"` 15 | Path string `bson:"path"` 16 | Sync bool `bson:"sync"` 17 | Size int `bson:"size"` 18 | } 19 | type RetrievalCoverArtMetadata struct { 20 | ID primitive.ObjectID `bson:"_id"` 21 | FileName string `bson:"file_name"` 22 | Format string `bson:"format"` 23 | CreatedAt time.Time `bson:"created_at"` 24 | UpdatedAt time.Time `bson:"updated_at"` 25 | Path string `bson:"path"` 26 | Sync bool `bson:"sync"` 27 | Size int `bson:"size"` 28 | Width int `bson:"width"` 29 | Height int `bson:"height"` 30 | } 31 | type RetrievalLyricsMetadata struct { 32 | ID primitive.ObjectID `bson:"_id"` 33 | MediaFileID primitive.ObjectID `bson:"media_file_id"` 34 | Lyrics string `bson:"lyrics"` // 直接获取媒体文件元数据内的歌词 35 | Name string `bson:"name"` 36 | CreatedAt time.Time `bson:"created_at"` 37 | UpdatedAt time.Time `bson:"updated_at"` 38 | Path string `bson:"path"` // 多歌词文件管理 39 | } 40 | -------------------------------------------------------------------------------- /NineSong/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | frontend: 5 | image: xiangch007/nsmusics:latest 6 | container_name: ${APP_CONTAINER_NAME} 7 | restart: unless-stopped 8 | env_file: .env 9 | environment: 10 | - NGINX_PORT=${APP_PORT} 11 | - BACKEND_SERVICE=${WEB_CONTAINER_NAME}:${SERVER_PORT} 12 | ports: 13 | - "${APP_PORT}:${APP_PORT}" 14 | networks: 15 | - public_net 16 | - app_net 17 | volumes: 18 | - ./.env:/app/.env:ro 19 | depends_on: 20 | - backend 21 | 22 | backend: 23 | image: xiangch007/ninesong:latest 24 | container_name: ${WEB_CONTAINER_NAME} 25 | restart: unless-stopped 26 | env_file: .env 27 | networks: 28 | - app_net 29 | - data_net 30 | volumes: 31 | - ./.env:/app/.env:ro 32 | - ${MEDIA_DATA_HOST_PATH}:/data/library 33 | - metavolume:/app/MetaData # 新增:持久化挂载 MetaData 目录 [6,8](@ref) 34 | depends_on: 35 | - mongodb 36 | security_opt: 37 | - no-new-privileges:true 38 | 39 | mongodb: 40 | image: mongo:6.0 41 | container_name: ${MONGO_CONTAINER_NAME} 42 | restart: unless-stopped 43 | env_file: .env 44 | environment: 45 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 46 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASS} 47 | networks: 48 | - data_net 49 | volumes: 50 | - dbdata:/data/db 51 | 52 | volumes: 53 | dbdata: # MongoDB 的持久化卷 54 | metavolume: # backend 的 MetaData 目录持久化卷 55 | 56 | # 容器网络架构 57 | networks: 58 | public_net: # 外网入口层 59 | driver: bridge 60 | 61 | app_net: # 应用通信层 62 | driver: bridge 63 | 64 | data_net: # 数据服务层 65 | driver: bridge -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_playlist_track.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_util" 6 | 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models" 8 | ) 9 | 10 | type PlaylistTrackRepository interface { 11 | GetPlaylistTrackItems( 12 | ctx context.Context, 13 | start, end, sort, order, 14 | search, starred, 15 | albumId, artistId, 16 | year, 17 | playlistId string, 18 | suffix, minBitrate, maxBitrate, folderPath, folderPathSubFilter string, 19 | ) ([]scene_audio_route_models.MediaFileMetadata, error) 20 | 21 | GetPlaylistTrackItemsMultipleSorting( 22 | ctx context.Context, 23 | start, end string, 24 | sortOrder []domain_util.SortOrder, 25 | search, starred, 26 | albumId, artistId, 27 | year, 28 | playlistId string, 29 | suffix, minBitrate, maxBitrate, folderPath, folderPathSubFilter string, 30 | ) ([]scene_audio_route_models.MediaFileMetadata, error) 31 | 32 | GetPlaylistTrackFilterItemsCount( 33 | ctx context.Context, 34 | ) (*scene_audio_route_models.MediaFileFilterCounts, error) 35 | 36 | AddPlaylistTrackItems( 37 | ctx context.Context, 38 | playlistId string, 39 | mediaFileIds string, 40 | ) (bool, error) 41 | 42 | RemovePlaylistTrackItems( 43 | ctx context.Context, 44 | playlistId string, 45 | mediaFileIds string, 46 | ) (bool, error) 47 | 48 | SortPlaylistTrackItems( 49 | ctx context.Context, 50 | playlistId string, 51 | mediaFileIds string, 52 | ) (bool, error) 53 | } 54 | -------------------------------------------------------------------------------- /NineSong/api/middleware/middleware_system/jwt_auth_middleware.go: -------------------------------------------------------------------------------- 1 | package middleware_system 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/internal/internal_system/token_util" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func JwtAuthMiddleware(secret string) gin.HandlerFunc { 13 | return func(c *gin.Context) { 14 | // 优先从URL参数获取access_token 15 | authToken := c.Query("access_token") 16 | 17 | // 如果URL参数没有,尝试从Header获取 18 | if authToken == "" { 19 | authHeader := c.GetHeader("Authorization") 20 | if authHeader == "" { 21 | c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Not authorized"}) 22 | c.Abort() 23 | return 24 | } 25 | 26 | // 解析Bearer Token 27 | tokenParts := strings.Split(authHeader, " ") 28 | if len(tokenParts) != 2 || strings.ToLower(tokenParts[0]) != "bearer" { 29 | c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Invalid authorization format"}) 30 | c.Abort() 31 | return 32 | } 33 | authToken = tokenParts[1] 34 | } 35 | 36 | // 验证令牌 37 | authorized, err := token_util.IsAuthorized(authToken, secret) 38 | if !authorized || err != nil { 39 | c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Invalid token"}) 40 | c.Abort() 41 | return 42 | } 43 | 44 | // 提取用户信息 45 | userID, err := token_util.ExtractIDFromToken(authToken, secret) 46 | if err != nil { 47 | c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: err.Error()}) 48 | c.Abort() 49 | return 50 | } 51 | 52 | // 设置上下文信息 53 | c.Set("x-user-id", userID) 54 | c.Next() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /NineSong/repository/repository_auth/user_update_repository.go: -------------------------------------------------------------------------------- 1 | package repository_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 7 | "go.mongodb.org/mongo-driver/bson" 8 | "go.mongodb.org/mongo-driver/bson/primitive" 9 | ) 10 | 11 | type updateRepository struct { 12 | collection mongo.Collection 13 | } 14 | 15 | func NewUpdateUserRepository(db mongo.Database, collection string) domain_auth.UpdateUserRepository { 16 | return &updateRepository{ 17 | collection: db.Collection(collection), 18 | } 19 | } 20 | 21 | func (r *updateRepository) UpdateName(ctx context.Context, id primitive.ObjectID, name string) error { 22 | _, err := r.collection.UpdateByID( 23 | ctx, 24 | id, 25 | bson.M{"$set": bson.M{"name": name}}, 26 | ) 27 | return err 28 | } 29 | 30 | func (r *updateRepository) UpdateEmail(ctx context.Context, id primitive.ObjectID, email string) error { 31 | _, err := r.collection.UpdateByID( 32 | ctx, 33 | id, 34 | bson.M{"$set": bson.M{"email": email}}, 35 | ) 36 | return err 37 | } 38 | 39 | func (r *updateRepository) UpdatePassword(ctx context.Context, id primitive.ObjectID, password string) error { 40 | _, err := r.collection.UpdateByID( 41 | ctx, 42 | id, 43 | bson.M{"$set": bson.M{"password": password}}, 44 | ) 45 | return err 46 | } 47 | 48 | func (r *updateRepository) IsEmailTaken(ctx context.Context, email string, excludeID primitive.ObjectID) (bool, error) { 49 | filter := bson.M{ 50 | "email": email, 51 | "_id": bson.M{"$ne": excludeID}, 52 | } 53 | count, err := r.collection.CountDocuments(ctx, filter) 54 | return count > 0, err 55 | } 56 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_interface/interface_media_file.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_util" 8 | ) 9 | 10 | type MediaFileRepository interface { 11 | GetMediaFileItems( 12 | ctx context.Context, 13 | start, end, sort, order, 14 | search, starred, 15 | albumId, artistId, 16 | year, 17 | suffix, minBitrate, maxBitrate, folderPath, folderPathSubFilter string, 18 | ) ([]scene_audio_route_models.MediaFileMetadata, error) 19 | 20 | GetMediaFileMetadataItems( 21 | ctx context.Context, 22 | start, end, sort, order, 23 | search, starred, 24 | albumId, artistId, 25 | year, 26 | suffix, minBitrate, maxBitrate, folderPath, folderPathSubFilter string, 27 | ) ([]scene_audio_db_models.MediaFileMetadata, error) 28 | 29 | GetMediaFileItemsMultipleSorting( 30 | ctx context.Context, 31 | start, end string, 32 | sortOrder []domain_util.SortOrder, 33 | search, starred, 34 | albumId, artistId, 35 | year, 36 | suffix, minBitrate, maxBitrate, folderPath, folderPathSubFilter string, 37 | ) ([]scene_audio_route_models.MediaFileMetadata, error) 38 | 39 | GetMediaFileFilterItemsCount( 40 | ctx context.Context, 41 | ) (*scene_audio_route_models.MediaFileFilterCounts, error) 42 | 43 | GetMediaFileItemsIds( 44 | ctx context.Context, ids []string, 45 | ) ([]scene_audio_route_models.MediaFileMetadata, error) 46 | } 47 | -------------------------------------------------------------------------------- /NineSong/usecase/usecase_auth/task_usecase_test.go: -------------------------------------------------------------------------------- 1 | package usecase_auth_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_auth" 8 | "testing" 9 | "time" 10 | 11 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/mocks" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/mock" 14 | "go.mongodb.org/mongo-driver/bson/primitive" 15 | ) 16 | 17 | func TestFetchByUserID(t *testing.T) { 18 | mockTaskRepository := new(mocks.TaskRepository) 19 | userObjectID := primitive.NewObjectID() 20 | userID := userObjectID.Hex() 21 | 22 | t.Run("success", func(t *testing.T) { 23 | 24 | mockTask := domain_auth.Task{ 25 | ID: primitive.NewObjectID(), 26 | Title: "Test Title", 27 | UserID: userObjectID, 28 | } 29 | 30 | mockListTask := make([]domain_auth.Task, 0) 31 | mockListTask = append(mockListTask, mockTask) 32 | 33 | mockTaskRepository.On("FetchByUserID", mock.Anything, userID).Return(mockListTask, nil).Once() 34 | 35 | u := usecase_auth.NewTaskUsecase(mockTaskRepository, time.Second*2) 36 | 37 | list, err := u.FetchByUserID(context.Background(), userID) 38 | 39 | assert.NoError(t, err) 40 | assert.NotNil(t, list) 41 | assert.Len(t, list, len(mockListTask)) 42 | 43 | mockTaskRepository.AssertExpectations(t) 44 | }) 45 | 46 | t.Run("error", func(t *testing.T) { 47 | mockTaskRepository.On("FetchByUserID", mock.Anything, userID).Return(nil, errors.New("Unexpected")).Once() 48 | 49 | u := usecase_auth.NewTaskUsecase(mockTaskRepository, time.Second*2) 50 | 51 | list, err := u.FetchByUserID(context.Background(), userID) 52 | 53 | assert.Error(t, err) 54 | assert.Nil(t, list) 55 | 56 | mockTaskRepository.AssertExpectations(t) 57 | }) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models/model_annotation.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_models 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type AnnotationMetadata struct { 10 | ID primitive.ObjectID `bson:"_id"` // 文档唯一标识符 11 | UserID string `bson:"user_id"` // 用户唯一标识符,标识创建此注释的用户 12 | ItemID string `bson:"item_id"` // 媒体项目唯一标识符,标识被注释的媒体项目 13 | ItemType string `bson:"item_type"` // 媒体项目类型(如音乐、视频、图片等) 14 | PlayCount int `bson:"play_count"` // 播放次数,记录该媒体项目被播放的次数 15 | PlayCompleteCount int `bson:"play_complete_count"` 16 | PlayDate time.Time `bson:"play_date"` // 播放日期,最近一次播放此媒体项目的日期和时间 17 | Rating int `bson:"rating"` // 评分,用户对此媒体项目的评分(如1-5分) 18 | Starred bool `bson:"starred"` // 是否收藏,标识该媒体项目是否被用户收藏 19 | StarredAt time.Time `bson:"starred_at"` // 收藏时间,媒体项目被收藏的日期和时间 20 | UpdatedAt time.Time `bson:"updated_at"` // 词云最后更新时间 21 | 22 | WordCloudTags []TagSource `bson:"word_cloud_tags"` // 标签及来源 23 | WeightedTags []WeightedTag `bson:"weighted_tags"` // 带权重的标签(用于推荐) 24 | } 25 | 26 | // TagSource 标签来源分类 27 | type TagSource struct { 28 | Tag string `bson:"tag" json:"tag"` // 标签内容(如:"摇滚"、"运动") 29 | Sources []string `bson:"sources" json:"sources"` // 来源字段列表 30 | TagType string `bson:"tag_type" json:"tag_type"` // 关联的ItemType 31 | Frequency int `bson:"frequency" json:"frequency"` // 原始出现频次 32 | } 33 | 34 | // WeightedTag 带权重的标签(用于推荐排序) 35 | type WeightedTag struct { 36 | Tag string `bson:"tag" json:"tag"` // 标签内容 37 | Weight float64 `bson:"weight" json:"weight"` // 综合权重值 38 | TagType string `bson:"tag_type" json:"tag_type"` // 关联类型 39 | } 40 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_auth/login_controller.go: -------------------------------------------------------------------------------- 1 | package controller_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "net/http" 7 | 8 | "golang.org/x/crypto/bcrypt" 9 | 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | type LoginController struct { 15 | LoginUsecase domain_auth.LoginUsecase 16 | Env *bootstrap.Env 17 | } 18 | 19 | func (lc *LoginController) Login(c *gin.Context) { 20 | var request domain_auth.LoginRequest 21 | 22 | err := c.ShouldBind(&request) 23 | if err != nil { 24 | c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) 25 | return 26 | } 27 | 28 | user, err := lc.LoginUsecase.GetUserByEmail(c, request.Email) 29 | if err != nil { 30 | c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "User not found with the given email"}) 31 | return 32 | } 33 | 34 | if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password)) != nil { 35 | c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Invalid credentials"}) 36 | return 37 | } 38 | 39 | accessToken, err := lc.LoginUsecase.CreateAccessToken(&user, lc.Env.AccessTokenSecret, lc.Env.AccessTokenExpiryHour) 40 | if err != nil { 41 | c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) 42 | return 43 | } 44 | 45 | refreshToken, err := lc.LoginUsecase.CreateRefreshToken(&user, lc.Env.RefreshTokenSecret, lc.Env.RefreshTokenExpiryHour) 46 | if err != nil { 47 | c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) 48 | return 49 | } 50 | 51 | loginResponse := domain_auth.LoginResponse{ 52 | AccessToken: accessToken, 53 | RefreshToken: refreshToken, 54 | } 55 | 56 | c.JSON(http.StatusOK, loginResponse) 57 | } 58 | -------------------------------------------------------------------------------- /NineSong/domain/mocks/TaskUsecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.16.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | domain "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 8 | 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // TaskUsecase is an autogenerated mock type for the TaskUsecase type 13 | type TaskUsecase struct { 14 | mock.Mock 15 | } 16 | 17 | // Create provides a mock function with given fields: c, task 18 | func (_m *TaskUsecase) Create(c context.Context, task *domain.Task) error { 19 | ret := _m.Called(c, task) 20 | 21 | var r0 error 22 | if rf, ok := ret.Get(0).(func(context.Context, *domain.Task) error); ok { 23 | r0 = rf(c, task) 24 | } else { 25 | r0 = ret.Error(0) 26 | } 27 | 28 | return r0 29 | } 30 | 31 | // FetchByUserID provides a mock function with given fields: c, userID 32 | func (_m *TaskUsecase) FetchByUserID(c context.Context, userID string) ([]domain.Task, error) { 33 | ret := _m.Called(c, userID) 34 | 35 | var r0 []domain.Task 36 | if rf, ok := ret.Get(0).(func(context.Context, string) []domain.Task); ok { 37 | r0 = rf(c, userID) 38 | } else { 39 | if ret.Get(0) != nil { 40 | r0 = ret.Get(0).([]domain.Task) 41 | } 42 | } 43 | 44 | var r1 error 45 | if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { 46 | r1 = rf(c, userID) 47 | } else { 48 | r1 = ret.Error(1) 49 | } 50 | 51 | return r0, r1 52 | } 53 | 54 | type mockConstructorTestingTNewTaskUsecase interface { 55 | mock.TestingT 56 | Cleanup(func()) 57 | } 58 | 59 | // NewTaskUsecase creates a new instance of TaskUsecase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 60 | func NewTaskUsecase(t mockConstructorTestingTNewTaskUsecase) *TaskUsecase { 61 | mock := &TaskUsecase{} 62 | mock.Mock.Test(t) 63 | 64 | t.Cleanup(func() { mock.AssertExpectations(t) }) 65 | 66 | return mock 67 | } 68 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_interface/interface_media_file_cue.go: -------------------------------------------------------------------------------- 1 | package scene_audio_db_interface 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_file_entity/scene_audio/scene_audio_db/scene_audio_db_models" 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/bson/primitive" 8 | ) 9 | 10 | // MediaFileCueRepository 基础CRUD接口 11 | type MediaFileCueRepository interface { 12 | // 创建/更新 13 | Upsert(ctx context.Context, file *scene_audio_db_models.MediaFileCueMetadata) (*scene_audio_db_models.MediaFileCueMetadata, error) 14 | BulkUpsert(ctx context.Context, files []*scene_audio_db_models.MediaFileCueMetadata) (int, error) 15 | UpdateByID(ctx context.Context, id primitive.ObjectID, update bson.M) (bool, error) 16 | 17 | // 删除 18 | DeleteByID(ctx context.Context, id primitive.ObjectID) error 19 | DeleteByPath(ctx context.Context, path string) error 20 | DeleteAllInvalid(ctx context.Context, filePaths []string) (int64, []struct { 21 | ArtistID primitive.ObjectID 22 | Count int64 23 | }, error) 24 | DeleteByFolder(ctx context.Context, folderPath string) (int64, error) 25 | 26 | // 查询 27 | GetByID(ctx context.Context, id primitive.ObjectID) (*scene_audio_db_models.MediaFileCueMetadata, error) 28 | GetByPath(ctx context.Context, path string) (*scene_audio_db_models.MediaFileCueMetadata, error) 29 | GetByFolder(ctx context.Context, folderPath string) ([]string, error) 30 | 31 | MediaCueCountByArtist(ctx context.Context, artistID string) (int64, error) 32 | GuestMediaCueCountByArtist(ctx context.Context, artistID string) (int64, error) 33 | MediaCountByAlbum(ctx context.Context, albumID string) (int64, error) 34 | 35 | InspectMediaCueCountByArtist(ctx context.Context, artistID string, filePaths []string) (int, error) 36 | InspectGuestMediaCueCountByArtist(ctx context.Context, artistID string, filePaths []string) (int, error) 37 | } 38 | -------------------------------------------------------------------------------- /NineSong/test_mediamtx_ffmepg.example: -------------------------------------------------------------------------------- 1 | MEDIAMTX_CONTAINER_NAME=nsmusics-mediamtx-ffmpeg # MediaMTX容器名称(可修改) 2 | # MediaMTX container name (modifiable) 3 | # ===== MediaMTX 端口配置 ===== 4 | RTSP_PORT=8554 # RTSP 协议端口(TCP) 5 | RTMP_PORT=1935 # RTMP 协议端口(TCP) 6 | HTTP_PORT=8888 # HTTP 服务器端口(TCP) 7 | HTTPS_PORT=8889 # HTTPS 服务器端口(TCP) 8 | SRT_PORT=8890 # SRT 协议端口(UDP) 9 | WEBRTC_PORT=8189 # WebRTC 协议端口(UDP) 10 | 11 | 12 | 13 | 发布到服务器: FFmpeg 14 | 15 | ffmpeg -re -i "G.E.M.邓紫棋 - 多远都要在一起.mp3" -c copy -f rtsp rtsp://localhost:8554/mystream 16 | 17 | ffmpeg -re -i "王力宏 - 依然爱你.flac" -map 0:a -c:a aac -ac 2 -f rtsp rtsp://localhost:8554/mystream 18 | 19 | start_time 20 | ffmpeg -re -i "王力宏 - 依然爱你.flac" ` 21 | -metadata:s:a start_time=0s ` 22 | -map 0:a -c:a aac -ac 2 -f rtsp rtsp://localhost:8554/mystream 23 | 24 | duration 25 | ffmpeg -re -i "王力宏 - 依然爱你.flac" ` 26 | -metadata duration="00:00:30" ` 27 | -map 0:a -c:a aac -ac 2 ` 28 | -f rtsp rtsp://localhost:8554/mystream 29 | 30 | 31 | 32 | 33 | 从服务器读取: FFmpeg 34 | ffmpeg -i rtsp://localhost:8554/mystream -c copy output.mp4 35 | 36 | 从服务器读取: GStreamer 37 | gst-launch-1.0 whepsrc whep-endpoint="http://127.0.0.1:8889/stream/whep" use-link-headers=true \ 38 | audio-caps="application/x-rtp,media=audio,encoding-name=OPUS,payload=111,clock-rate=48000,encoding-params=(string)2" \ 39 | ! rtpopusdepay ! decodebin ! autoaudiosink 40 | 41 | ffmpeg -re -i "G.E.M.邓紫棋 - 多远都要在一起.mp3" -c:a aac -f rtsp rtsp://localhost:8554/mystream 42 | ffmpeg -i rtsp://localhost:8554/mystream -c:a libmp3lame output.mp3 43 | 44 | 45 | ​1. 推流端调整 46 | ​改用AAC编码:MP3在RTSP协议中兼容性较差(参考网页4的音频编码支持列表),建议转码为AAC: 47 | ffmpeg -re -i "G.E.M.邓紫棋 - 多远都要在一起.mp3" -c:a aac -f rtsp rtsp://localhost:8554/mystream 48 | 2.拉流端调整 49 | ​转码为MP3:若需输出MP3文件,需显式转码(避免直接复制): 50 | ffmpeg -i rtsp://localhost:8554/mystream -c:a libmp3lame output.mp3 -------------------------------------------------------------------------------- /NineSong/domain/mocks/TaskRepository.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.16.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | domain "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 8 | 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // TaskRepository is an autogenerated mock type for the TaskRepository type 13 | type TaskRepository struct { 14 | mock.Mock 15 | } 16 | 17 | // Create provides a mock function with given fields: c, task 18 | func (_m *TaskRepository) Create(c context.Context, task *domain.Task) error { 19 | ret := _m.Called(c, task) 20 | 21 | var r0 error 22 | if rf, ok := ret.Get(0).(func(context.Context, *domain.Task) error); ok { 23 | r0 = rf(c, task) 24 | } else { 25 | r0 = ret.Error(0) 26 | } 27 | 28 | return r0 29 | } 30 | 31 | // FetchByUserID provides a mock function with given fields: c, userID 32 | func (_m *TaskRepository) FetchByUserID(c context.Context, userID string) ([]domain.Task, error) { 33 | ret := _m.Called(c, userID) 34 | 35 | var r0 []domain.Task 36 | if rf, ok := ret.Get(0).(func(context.Context, string) []domain.Task); ok { 37 | r0 = rf(c, userID) 38 | } else { 39 | if ret.Get(0) != nil { 40 | r0 = ret.Get(0).([]domain.Task) 41 | } 42 | } 43 | 44 | var r1 error 45 | if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { 46 | r1 = rf(c, userID) 47 | } else { 48 | r1 = ret.Error(1) 49 | } 50 | 51 | return r0, r1 52 | } 53 | 54 | type mockConstructorTestingTNewTaskRepository interface { 55 | mock.TestingT 56 | Cleanup(func()) 57 | } 58 | 59 | // NewTaskRepository creates a new instance of TaskRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 60 | func NewTaskRepository(t mockConstructorTestingTNewTaskRepository) *TaskRepository { 61 | mock := &TaskRepository{} 62 | mock.Mock.Test(t) 63 | 64 | t.Cleanup(func() { mock.AssertExpectations(t) }) 65 | 66 | return mock 67 | } 68 | -------------------------------------------------------------------------------- /NineSong/api/controller/controller_auth/refresh_token_controller.go: -------------------------------------------------------------------------------- 1 | package controller_auth 2 | 3 | import ( 4 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | "net/http" 7 | 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type RefreshTokenController struct { 13 | RefreshTokenUsecase domain_auth.RefreshTokenUsecase 14 | Env *bootstrap.Env 15 | } 16 | 17 | func (rtc *RefreshTokenController) RefreshToken(c *gin.Context) { 18 | var request domain_auth.RefreshTokenRequest 19 | 20 | err := c.ShouldBind(&request) 21 | if err != nil { 22 | c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()}) 23 | return 24 | } 25 | 26 | id, err := rtc.RefreshTokenUsecase.ExtractIDFromToken(request.RefreshToken, rtc.Env.RefreshTokenSecret) 27 | if err != nil { 28 | c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "User not found"}) 29 | return 30 | } 31 | 32 | user, err := rtc.RefreshTokenUsecase.GetUserByID(c, id) 33 | if err != nil { 34 | c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "User not found"}) 35 | return 36 | } 37 | 38 | accessToken, err := rtc.RefreshTokenUsecase.CreateAccessToken(&user, rtc.Env.AccessTokenSecret, rtc.Env.AccessTokenExpiryHour) 39 | if err != nil { 40 | c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) 41 | return 42 | } 43 | 44 | refreshToken, err := rtc.RefreshTokenUsecase.CreateRefreshToken(&user, rtc.Env.RefreshTokenSecret, rtc.Env.RefreshTokenExpiryHour) 45 | if err != nil { 46 | c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()}) 47 | return 48 | } 49 | 50 | refreshTokenResponse := domain_auth.RefreshTokenResponse{ 51 | AccessToken: accessToken, 52 | RefreshToken: refreshToken, 53 | } 54 | 55 | c.JSON(http.StatusOK, refreshTokenResponse) 56 | } 57 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models/model_album.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_models 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type AlbumMetadata struct { 10 | ID primitive.ObjectID `bson:"_id"` 11 | Name string `bson:"name"` 12 | ArtistID string `bson:"artist_id"` 13 | Artist string `bson:"artist"` 14 | AlbumArtist string `bson:"album_artist"` 15 | HasCoverArt bool `bson:"has_cover_art"` 16 | 17 | MinYear int `bson:"min_year"` 18 | MaxYear int `bson:"max_year"` 19 | SongCount int `bson:"song_count"` 20 | Duration float64 `bson:"duration"` 21 | Size int `bson:"size"` 22 | Genre string `bson:"genre"` 23 | CreatedAt time.Time `bson:"created_at"` 24 | UpdatedAt time.Time `bson:"updated_at"` 25 | AlbumArtistID string `bson:"album_artist_id"` 26 | Comment string `bson:"comment"` 27 | ImageFiles string `bson:"image_files"` // 为空则不存在cover封面,从媒体文件中提取 28 | 29 | Compilation bool `bson:"compilation"` // 是否为合辑(多艺术家作品合集) 30 | AllArtistIDs []ArtistIDPair `bson:"all_artist_ids"` // 所有参与艺术家的唯一标识符列表 31 | AllAlbumArtistIDs []ArtistIDPair `bson:"all_album_artist_ids"` // 所有参与专辑艺术家的唯一标识符列表 32 | 33 | PlayCount int `bson:"play_count"` 34 | PlayCompleteCount int `bson:"play_complete_count"` 35 | PlayDate time.Time `bson:"play_date"` 36 | Rating int `bson:"rating"` 37 | Starred bool `bson:"starred"` 38 | StarredAt time.Time `bson:"starred_at"` 39 | } 40 | 41 | type AlbumFilterCounts struct { 42 | Total int `json:"total"` 43 | Starred int `json:"starred"` 44 | RecentPlay int `json:"recent_play"` 45 | } 46 | 47 | type ArtistIDPair struct { 48 | ArtistName string `bson:"artist_name"` // 艺术家名称 49 | ArtistID string `bson:"artist_id"` // 艺术家唯一 ID 50 | } 51 | -------------------------------------------------------------------------------- /NineSong/domain/domain_file_entity/scene_audio/scene_audio_route/scene_audio_route_models/model_artist.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_models 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type ArtistMetadata struct { 10 | ID primitive.ObjectID `bson:"_id"` 11 | Name string `bson:"name"` 12 | AlbumCount int `bson:"album_count"` 13 | GuestAlbumCount int `bson:"guest_album_count"` 14 | SongCount int `bson:"song_count"` 15 | GuestSongCount int `bson:"guest_song_count"` 16 | CueCount int `bson:"cue_count"` 17 | GuestCueCount int `bson:"guest_cue_count"` 18 | Size int `bson:"size"` 19 | HasCoverArt bool `bson:"has_cover_art"` 20 | 21 | CreatedAt time.Time `bson:"created_at"` 22 | UpdatedAt time.Time `bson:"updated_at"` 23 | 24 | Compilation bool `bson:"compilation"` // 是否为合辑(多艺术家作品合集) 25 | AllArtistIDs []ArtistIDPair `bson:"all_artist_ids"` // 所有参与艺术家的唯一标识符列表 26 | AllAlbumArtistIDs []ArtistIDPair `bson:"all_album_artist_ids"` // 所有参与专辑艺术家的唯一标识符列表 27 | 28 | ImageFiles string `bson:"image_files"` // 为空则不存在cover封面,从媒体文件中提取 29 | 30 | PlayCount int `bson:"play_count"` 31 | PlayCompleteCount int `bson:"play_complete_count"` 32 | PlayDate time.Time `bson:"play_date"` 33 | Rating int `bson:"rating"` 34 | Starred bool `bson:"starred"` 35 | StarredAt time.Time `bson:"starred_at"` 36 | } 37 | 38 | type ArtistTreeMetadata struct { 39 | Artist ArtistMetadata `bson:"artist"` 40 | Albums []AlbumTreeMetadata `bson:"albums"` 41 | } 42 | type AlbumTreeMetadata struct { 43 | Album AlbumMetadata `bson:"album"` 44 | MediaFiles []MediaFileMetadata `bson:"media_files"` 45 | } 46 | 47 | type ArtistFilterCounts struct { 48 | Total int `json:"total"` 49 | Starred int `json:"starred"` 50 | RecentPlay int `json:"recent_play"` 51 | } 52 | -------------------------------------------------------------------------------- /NineSong/mongo/mocks/Cursor.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.16.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | ) 10 | 11 | // Cursor is an autogenerated mock type for the Cursor type 12 | type Cursor struct { 13 | mock.Mock 14 | } 15 | 16 | // All provides a mock function with given fields: _a0, _a1 17 | func (_m *Cursor) All(_a0 context.Context, _a1 interface{}) error { 18 | ret := _m.Called(_a0, _a1) 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func(context.Context, interface{}) error); ok { 22 | r0 = rf(_a0, _a1) 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // Close provides a mock function with given fields: _a0 31 | func (_m *Cursor) Close(_a0 context.Context) error { 32 | ret := _m.Called(_a0) 33 | 34 | var r0 error 35 | if rf, ok := ret.Get(0).(func(context.Context) error); ok { 36 | r0 = rf(_a0) 37 | } else { 38 | r0 = ret.Error(0) 39 | } 40 | 41 | return r0 42 | } 43 | 44 | // Decode provides a mock function with given fields: _a0 45 | func (_m *Cursor) Decode(_a0 interface{}) error { 46 | ret := _m.Called(_a0) 47 | 48 | var r0 error 49 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 50 | r0 = rf(_a0) 51 | } else { 52 | r0 = ret.Error(0) 53 | } 54 | 55 | return r0 56 | } 57 | 58 | // Next provides a mock function with given fields: _a0 59 | func (_m *Cursor) Next(_a0 context.Context) bool { 60 | ret := _m.Called(_a0) 61 | 62 | var r0 bool 63 | if rf, ok := ret.Get(0).(func(context.Context) bool); ok { 64 | r0 = rf(_a0) 65 | } else { 66 | r0 = ret.Get(0).(bool) 67 | } 68 | 69 | return r0 70 | } 71 | 72 | type mockConstructorTestingTNewCursor interface { 73 | mock.TestingT 74 | Cleanup(func()) 75 | } 76 | 77 | // NewCursor creates a new instance of Cursor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 78 | func NewCursor(t mockConstructorTestingTNewCursor) *Cursor { 79 | mock := &Cursor{} 80 | mock.Mock.Test(t) 81 | 82 | t.Cleanup(func() { mock.AssertExpectations(t) }) 83 | 84 | return mock 85 | } 86 | -------------------------------------------------------------------------------- /NineSong/repository/repository_auth/user_repository.go: -------------------------------------------------------------------------------- 1 | package repository_auth 2 | 3 | import ( 4 | "context" 5 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/domain_auth" 6 | 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 8 | "go.mongodb.org/mongo-driver/bson" 9 | "go.mongodb.org/mongo-driver/bson/primitive" 10 | "go.mongodb.org/mongo-driver/mongo/options" 11 | ) 12 | 13 | type userRepository struct { 14 | database mongo.Database 15 | collection string 16 | } 17 | 18 | func NewUserRepository(db mongo.Database, collection string) domain_auth.UserRepository { 19 | return &userRepository{ 20 | database: db, 21 | collection: collection, 22 | } 23 | } 24 | 25 | func (ur *userRepository) Create(c context.Context, user *domain_auth.User) error { 26 | collection := ur.database.Collection(ur.collection) 27 | 28 | _, err := collection.InsertOne(c, user) 29 | 30 | return err 31 | } 32 | 33 | func (ur *userRepository) Fetch(c context.Context) ([]domain_auth.User, error) { 34 | collection := ur.database.Collection(ur.collection) 35 | 36 | opts := options.Find().SetProjection(bson.D{{Key: "password", Value: 0}}) 37 | cursor, err := collection.Find(c, bson.D{}, opts) 38 | 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | var users []domain_auth.User 44 | 45 | err = cursor.All(c, &users) 46 | if users == nil { 47 | return []domain_auth.User{}, err 48 | } 49 | 50 | return users, err 51 | } 52 | 53 | func (ur *userRepository) GetByEmail(c context.Context, email string) (domain_auth.User, error) { 54 | collection := ur.database.Collection(ur.collection) 55 | var user domain_auth.User 56 | err := collection.FindOne(c, bson.M{"email": email}).Decode(&user) 57 | return user, err 58 | } 59 | 60 | func (ur *userRepository) GetByID(c context.Context, id string) (domain_auth.User, error) { 61 | collection := ur.database.Collection(ur.collection) 62 | 63 | var user domain_auth.User 64 | 65 | idHex, err := primitive.ObjectIDFromHex(id) 66 | if err != nil { 67 | return user, err 68 | } 69 | 70 | err = collection.FindOne(c, bson.M{"_id": idHex}).Decode(&user) 71 | return user, err 72 | } 73 | -------------------------------------------------------------------------------- /NineSong/api/route/route_file_entity/scene_audio_route_api_route/scene_audio_recommend_route.go: -------------------------------------------------------------------------------- 1 | package scene_audio_route_api_route 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/controller/controller_file_entity/scene_audio_route_api_controller" 7 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/domain" 8 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_db_repository" 9 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/repository/repository_file_entity/scene_audio/scene_audio_route_repository" 10 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase/usecase_file_entity/scene_audio/scene_audio_route_usecase" 11 | 12 | "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | func NewRecommendRouter( 17 | timeout time.Duration, 18 | db mongo.Database, 19 | group *gin.RouterGroup, 20 | ) { 21 | // 初始化repository 22 | annotationRepo := scene_audio_route_repository.NewAnnotationRepository(db) 23 | wordCloudRepo := scene_audio_db_repository.NewWordCloudRepository(db, domain.CollectionFileEntityAudioSceneMediaFileWordCloud) 24 | recommendRepo := scene_audio_db_repository.NewRecommendRepository(db, domain.CollectionFileEntityAudioSceneAnnotation) 25 | 26 | // 初始化usecase 27 | recommendUsecase := scene_audio_route_usecase.NewRecommendUsecase(annotationRepo, wordCloudRepo, recommendRepo, db, timeout) 28 | 29 | // 初始化controller 30 | recommendCtrl := scene_audio_route_api_controller.NewRecommendController(recommendUsecase) 31 | 32 | // 注册路由 33 | recommendGroup := group.Group("/recommend") 34 | { 35 | // 通用推荐 36 | // GET /recommend/general?recommend_type=media&limit=30&random_seed=666&recommend_offset=0 37 | recommendGroup.GET("/general", recommendCtrl.GetGeneralRecommendations) 38 | 39 | // 个性化推荐 40 | // GET /recommend/personalized?recommend_type=media&limit=30[&user_id=xxx] 41 | recommendGroup.GET("/personalized", recommendCtrl.GetPersonalizedRecommendations) 42 | 43 | // 热门推荐 44 | // GET /recommend/popular?recommend_type=media&limit=30 45 | recommendGroup.GET("/popular", recommendCtrl.GetPopularRecommendations) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /NineSong/test_srs.example: -------------------------------------------------------------------------------- 1 | docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 -p 8000:8000/udp -p 10080:10080/udp ossrs/srs:5 2 | 3 | ffmpeg -re -i ex10.mp4 -c copy -f flv rtmp://localhost/live/livestream 4 | 5 | ffmpeg -re -i "[DM]퀸다미 - Party Train - 🔞교차편집 - 4K_60FPS Enhanced - AfreecaTV VOD_cut.mp4" -c copy -f flv -y rtmp://localhost/live/livestream 6 | 7 | 8 | 9 | ffmpeg -re -i "G.E.M.邓紫棋 - 多远都要在一起.mp3" -c:a aac -f flv rtmp://localhost/live/livestream_mp3 10 | ​警告信息:deprecated pixel format used 是由于视频流从mjpeg转码为flv1时像素格式不兼容。 11 | ​关键错误:Failed to update header 和 Qavg: 638.685 表明FLV封装过程中数据包写入异常,可能是视频流无效导致。 12 | 13 | ffmpeg -re -i "G.E.M.邓紫棋 - 多远都要在一起.mp3" -map 0:a -c:a aac -f flv rtmp://localhost/live/livestream_mp3 14 | 15 | 16 | docker run -d ` 17 | -p 1935:1935 -p 8080:8080 ` 18 | -v E:\0_XiangCheng_WorkSpace\NSMusicS-WorkSpqce\NineSong\srs_audio.conf:/usr/local/srs/conf/srs.conf ` 19 | --name srs_audio ` 20 | ossrs/srs:6 ` 21 | ./objs/srs -c conf/srs.conf 22 | 23 | # 推流原始MP3文件(保留原始音质) 24 | ffmpeg -re -i "G.E.M.邓紫棋 - 多远都要在一起.mp3" -vn -c:a copy -f flv rtmp://localhost/live/livestream_mp3 25 | # 动态转码推流(适合不同码率需求) 26 | ffmpeg -re -i "G.E.M.邓紫棋 - 多远都要在一起.mp3" -vn -c:a libmp3lame -b:a 192k -f flv rtmp://localhost/live/livestream_mp3 27 | 28 | 29 | 30 | 31 | Howler.js 基于 Web Audio API 和 HTML5