feat: expose playlist sharing over HTTP
This commit is contained in:
1 parent
eed25578c7
commit
8b941764b1
13 files changed
+743
-19
No files matched your search
@@ -165,6 +165,10 @@ func New(s Services, metrics prometheus.Registerer, tracer trace.TracerProvider,
|
||||
srv.handle(mux, protocol.RouteClearMyListens, srv.clearMyListens)
|
||||
srv.handle(mux, protocol.RouteEnableListenRecording, srv.enableListenRecording)
|
||||
srv.handle(mux, protocol.RouteDisableListenRecording, srv.disableListenRecording)
|
||||
srv.handle(mux, protocol.RouteShareMyPlaylist, srv.shareMyPlaylist)
|
||||
srv.handle(mux, protocol.RouteUnshareMyPlaylist, srv.unshareMyPlaylist)
|
||||
srv.handle(mux, protocol.RouteGetSharedPlaylist, srv.getSharedPlaylist)
|
||||
srv.handle(mux, protocol.RouteCopySharedPlaylist, srv.copySharedPlaylist)
|
||||
mux.HandleFunc("GET /ws", srv.websocket)
|
||||
mux.HandleFunc("GET /api/room/events", srv.roomEvents)
|
||||
mux.HandleFunc("GET /media/{source}/{id}", srv.redirectMedia)
|
||||
@@ -352,6 +356,7 @@ var knownErrors = []struct {
|
||||
{catalog.ErrRateLimited, protocol.ErrorCodeRateLimited},
|
||||
{errClientOutdated, protocol.ErrorCodeClientOutdated},
|
||||
{errPanicked, protocol.ErrorCodeInternal},
|
||||
{port.ErrShareNotFound, protocol.ErrorCodeNotFound},
|
||||
}
|
||||
|
||||
func errorInfo(err error) (info protocol.ErrorInfo, expected bool) {
|
||||
|
||||
@@ -24,6 +24,7 @@ func (s *server) playlistDTO(p port.PlaylistRecord) protocol.MyPlaylist {
|
||||
CreatedAtMs: p.CreatedAt.UnixMilli(),
|
||||
UpdatedAtMs: p.UpdatedAt.UnixMilli(),
|
||||
CoverURL: cover,
|
||||
ShareID: optional(p.ShareID),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,3 +195,48 @@ func (s *server) importMyPlatformPlaylist(ctx context.Context, c call) (any, err
|
||||
}
|
||||
return protocol.ImportMyPlatformPlaylistResponse{Playlist: s.playlistDTO(p), Imported: int64(n)}, nil
|
||||
}
|
||||
|
||||
func (s *server) shareMyPlaylist(ctx context.Context, c call) (any, error) {
|
||||
if _, err := protocol.DecodeShareMyPlaylistRequest(c.body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shareID, err := s.Playlists.Share(ctx, c.member.ID, c.r.PathValue("id"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return protocol.ShareMyPlaylistResponse{ShareID: shareID}, nil
|
||||
}
|
||||
|
||||
func (s *server) unshareMyPlaylist(ctx context.Context, c call) (any, error) {
|
||||
return nil, s.Playlists.Unshare(ctx, c.member.ID, c.r.PathValue("id"))
|
||||
}
|
||||
|
||||
func (s *server) getSharedPlaylist(ctx context.Context, c call) (any, error) {
|
||||
p, err := s.Playlists.Shared(ctx, c.r.PathValue("shareId"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]protocol.MyPlaylistItem, len(p.Items))
|
||||
for i, item := range p.Items {
|
||||
items[i] = s.playlistItemDTO(item)
|
||||
}
|
||||
return protocol.SharedPlaylist{
|
||||
Name: p.Name,
|
||||
Favorites: p.Favorites,
|
||||
OwnerName: p.OwnerName,
|
||||
OwnerAvatarHash: p.OwnerAvatarHash,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) copySharedPlaylist(ctx context.Context, c call) (any, error) {
|
||||
req, err := protocol.DecodeCopySharedPlaylistRequest(c.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := s.Playlists.CopyShared(ctx, c.member.ID, c.r.PathValue("shareId"), req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.playlistDTO(p), nil
|
||||
}
|
||||
+3
-3
@@ -119,7 +119,7 @@ Authorization: Bearer $alice
|
||||
]}
|
||||
-- response --
|
||||
201
|
||||
{"playlist": {"id": "$imported", "name": "文件里的歌单", "favorites": false, "trackCount": 3, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/example.jpg"}, "imported": 3}
|
||||
{"playlist": {"id": "$imported", "name": "文件里的歌单", "favorites": false, "trackCount": 3, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/example.jpg", "shareId": null}, "imported": 3}
|
||||
|
||||
-- request --
|
||||
GET /api/me/playlists/$imported/items
|
||||
@@ -139,7 +139,7 @@ Authorization: Bearer $alice
|
||||
{"name": " ", "tracks": []}
|
||||
-- response --
|
||||
201
|
||||
{"playlist": {"id": "$empty", "name": "导入的歌单", "favorites": false, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null}, "imported": 0}
|
||||
{"playlist": {"id": "$empty", "name": "导入的歌单", "favorites": false, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null, "shareId": null}, "imported": 0}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlist-links
|
||||
@@ -166,7 +166,7 @@ Authorization: Bearer $alice
|
||||
{"name": "周杰伦精选", "source": "netease", "playlistId": "3778678"}
|
||||
-- response --
|
||||
201
|
||||
{"playlist": {"id": "$platform", "name": "周杰伦精选", "favorites": false, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/example.jpg"}, "imported": 2}
|
||||
{"playlist": {"id": "$platform", "name": "周杰伦精选", "favorites": false, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/example.jpg", "shareId": null}, "imported": 2}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/platform-imports
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
歌单分享:已注册的账号可以分享自己的任何歌单,包括“我喜欢”;每次开启都生成新的链接,旧链接随即失效;不需要身份就能读取分享,响应里没有所有者的成员 ID 和用户名;已注册的成员可以把分享的歌单复制成自己的新歌单;停止分享、删除歌单之后分享失效。
|
||||
|
||||
-- request --
|
||||
POST /api/members
|
||||
|
||||
{}
|
||||
-- response --
|
||||
201
|
||||
{"memberToken": "$alice", "member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}}
|
||||
|
||||
-- request --
|
||||
POST /api/account
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{"username": "alice", "password": "correct horse"}
|
||||
-- response --
|
||||
201
|
||||
{"account": {"username": "alice"}}
|
||||
|
||||
-- request --
|
||||
PATCH /api/me
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{"email": "alice@example.com"}
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": "c160f8cc69a4f0bf2b0362752353d060"}}
|
||||
|
||||
-- request --
|
||||
GET /api/me/playlists
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null, "shareId": null}]}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/items
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{"track": {"source": "netease", "id": "186016"}}
|
||||
-- response --
|
||||
201
|
||||
{"item": {"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000}}}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/items
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{"track": {"source": "netease", "id": "185811"}}
|
||||
-- response --
|
||||
201
|
||||
{"item": {"track": {"source": "netease", "id": "185811"}, "meta": {"title": "七里香", "artist": "周杰伦", "album": "七里香", "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "pageUrl": "https://music.163.com/#/song?id=185811", "durationMs": 299000}}}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/share
|
||||
|
||||
{}
|
||||
-- response --
|
||||
401
|
||||
{"error": {"code": "UNAUTHORIZED", "message": "成员 token 无效"}}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/share
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{}
|
||||
-- response --
|
||||
200
|
||||
{"shareId": "$share"}
|
||||
|
||||
-- request --
|
||||
GET /api/me/playlists
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "shareId": "$share"}]}
|
||||
|
||||
-- request --
|
||||
GET /api/shared-playlists/$share
|
||||
-- response --
|
||||
200
|
||||
{"name": "我喜欢", "favorites": true, "ownerName": "$aliceName", "ownerAvatarHash": "c160f8cc69a4f0bf2b0362752353d060", "items": [
|
||||
{"track": {"source": "netease", "id": "185811"}, "meta": {"title": "七里香", "artist": "周杰伦", "album": "七里香", "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "pageUrl": "https://music.163.com/#/song?id=185811", "durationMs": 299000}},
|
||||
{"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000}}
|
||||
]}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/share
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{}
|
||||
-- response --
|
||||
200
|
||||
{"shareId": "$newShare"}
|
||||
|
||||
-- request --
|
||||
GET /api/shared-playlists/$share
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "分享已失效"}}
|
||||
|
||||
-- request --
|
||||
GET /api/shared-playlists/$newShare
|
||||
-- response --
|
||||
200
|
||||
{"name": "我喜欢", "favorites": true, "ownerName": "$aliceName", "ownerAvatarHash": "c160f8cc69a4f0bf2b0362752353d060", "items": [
|
||||
{"track": {"source": "netease", "id": "185811"}, "meta": {"title": "七里香", "artist": "周杰伦", "album": "七里香", "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "pageUrl": "https://music.163.com/#/song?id=185811", "durationMs": 299000}},
|
||||
{"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000}}
|
||||
]}
|
||||
|
||||
-- request --
|
||||
GET /api/shared-playlists/not-a-share
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "分享已失效"}}
|
||||
|
||||
-- request --
|
||||
POST /api/members
|
||||
|
||||
{}
|
||||
-- response --
|
||||
201
|
||||
{"memberToken": "$bob", "member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""}}
|
||||
|
||||
-- request --
|
||||
GET /api/shared-playlists/$newShare
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"name": "我喜欢", "favorites": true, "ownerName": "$aliceName", "ownerAvatarHash": "c160f8cc69a4f0bf2b0362752353d060", "items": [
|
||||
{"track": {"source": "netease", "id": "185811"}, "meta": {"title": "七里香", "artist": "周杰伦", "album": "七里香", "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "pageUrl": "https://music.163.com/#/song?id=185811", "durationMs": 299000}},
|
||||
{"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000}}
|
||||
]}
|
||||
|
||||
-- request --
|
||||
POST /api/shared-playlists/$newShare/copies
|
||||
|
||||
{"name": "抄来的"}
|
||||
-- response --
|
||||
401
|
||||
{"error": {"code": "UNAUTHORIZED", "message": "成员 token 无效"}}
|
||||
|
||||
-- request --
|
||||
POST /api/shared-playlists/$newShare/copies
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{"name": "抄来的"}
|
||||
-- response --
|
||||
403
|
||||
{"error": {"code": "FORBIDDEN", "message": "注册账号之后才能使用歌单"}}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/share
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{}
|
||||
-- response --
|
||||
403
|
||||
{"error": {"code": "FORBIDDEN", "message": "注册账号之后才能使用歌单"}}
|
||||
|
||||
-- request --
|
||||
POST /api/account
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{"username": "bob", "password": "correct horse"}
|
||||
-- response --
|
||||
201
|
||||
{"account": {"username": "bob"}}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/share
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{}
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "歌单不存在"}}
|
||||
|
||||
-- request --
|
||||
DELETE /api/me/playlists/$favorites/share
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "歌单不存在"}}
|
||||
|
||||
-- request --
|
||||
POST /api/shared-playlists/$newShare/copies
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{"name": " "}
|
||||
-- response --
|
||||
400
|
||||
{"error": {"code": "INVALID_ARGUMENT", "message": "name:去掉首尾空白后应有 1 到 50 个字符,其中至少一个看得见,不能包含控制字符"}}
|
||||
|
||||
-- request --
|
||||
POST /api/shared-playlists/$newShare/copies
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{"name": " 抄来的 "}
|
||||
-- response --
|
||||
201
|
||||
{"id": "$copy", "name": "抄来的", "favorites": false, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "shareId": null}
|
||||
|
||||
-- request --
|
||||
GET /api/me/playlists/$copy/items
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"items": [
|
||||
{"track": {"source": "netease", "id": "185811"}, "meta": {"title": "七里香", "artist": "周杰伦", "album": "七里香", "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "pageUrl": "https://music.163.com/#/song?id=185811", "durationMs": 299000}},
|
||||
{"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000}}
|
||||
]}
|
||||
|
||||
-- request --
|
||||
POST /api/shared-playlists/not-a-share/copies
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{"name": "抄来的"}
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "分享已失效"}}
|
||||
|
||||
-- request --
|
||||
DELETE /api/me/playlists/$favorites/share
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
DELETE /api/me/playlists/$favorites/share
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
GET /api/shared-playlists/$newShare
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "分享已失效"}}
|
||||
|
||||
-- request --
|
||||
POST /api/shared-playlists/$newShare/copies
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{"name": "抄来的"}
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "分享已失效"}}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$copy/share
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{}
|
||||
-- response --
|
||||
200
|
||||
{"shareId": "$copyShare"}
|
||||
|
||||
-- request --
|
||||
DELETE /api/me/playlists/$copy
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
GET /api/shared-playlists/$copyShare
|
||||
-- response --
|
||||
404
|
||||
{"error": {"code": "NOT_FOUND", "message": "分享已失效"}}
|
||||
+5
-5
@@ -38,7 +38,7 @@ GET /api/me/playlists
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null}]}
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null, "shareId": null}]}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$favorites/items
|
||||
@@ -93,7 +93,7 @@ Authorization: Bearer $alice
|
||||
{"name": " 通勤 "}
|
||||
-- response --
|
||||
201
|
||||
{"id": "$commute", "name": "通勤", "favorites": false, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null}
|
||||
{"id": "$commute", "name": "通勤", "favorites": false, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null, "shareId": null}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists
|
||||
@@ -224,7 +224,7 @@ GET /api/me/playlists
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/qilixiang.jpg"}, {"id": "$commute", "name": "上班路上", "favorites": false, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/example.jpg"}]}
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "shareId": null}, {"id": "$commute", "name": "上班路上", "favorites": false, "trackCount": 2, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/example.jpg", "shareId": null}]}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlist-memberships
|
||||
@@ -289,7 +289,7 @@ GET /api/me/playlists
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"id": "$bobFavorites", "name": "我喜欢", "favorites": true, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null}]}
|
||||
{"items": [{"id": "$bobFavorites", "name": "我喜欢", "favorites": true, "trackCount": 0, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": null, "shareId": null}]}
|
||||
|
||||
-- request --
|
||||
POST /api/me/playlists/$bobFavorites/copies
|
||||
@@ -377,4 +377,4 @@ GET /api/me/playlists
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 1, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/qilixiang.jpg"}]}
|
||||
{"items": [{"id": "$favorites", "name": "我喜欢", "favorites": true, "trackCount": 1, "createdAtMs": 1, "updatedAtMs": 1, "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "shareId": null}]}
|
||||
Reference in new issue
Block a user