Skip to content

Commit 18ba58d

Browse files
authored
Merge pull request #10 from mbaraa/feat/the-long-awaited-playlists
Feat: The long awaited playlists
2 parents 02912e3 + 13d3cd3 commit 18ba58d

38 files changed

+1759
-202
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ init:
2121
templ generate && \
2222
go run main.go migrate
2323

24+
seed:
25+
go run main.go seed
26+
2427
# dev runs the development server where it builds the tailwind css sheet,
2528
# and compiles the project whenever a file is changed.
2629
dev:

cmd/migrator/migrator.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,27 @@ func Migrate() error {
1111
return err
1212
}
1313

14-
return dbConn.Debug().AutoMigrate(
14+
err = dbConn.Debug().AutoMigrate(
1515
new(models.Account),
1616
new(models.Profile),
1717
new(models.EmailVerificationCode),
1818
new(models.Song),
19+
new(models.Playlist),
20+
new(models.PlaylistSong),
21+
new(models.PlaylistOwner),
1922
)
23+
if err != nil {
24+
return err
25+
}
26+
27+
for _, tableName := range []string{
28+
"profiles", "songs", "playlists",
29+
} {
30+
err = dbConn.Exec("ALTER TABLE " + tableName + " CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci").Error
31+
if err != nil {
32+
return err
33+
}
34+
}
35+
36+
return nil
2037
}

cmd/seeder/seeder.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package seeder
2+
3+
import (
4+
"dankmuzikk/db"
5+
"dankmuzikk/entities"
6+
"dankmuzikk/log"
7+
"dankmuzikk/models"
8+
playlistspkg "dankmuzikk/services/playlists"
9+
"dankmuzikk/tests"
10+
"math/rand"
11+
"time"
12+
13+
"gorm.io/gorm"
14+
)
15+
16+
var (
17+
dbConn *gorm.DB
18+
accountRepo db.UnsafeCRUDRepo[models.Account]
19+
profileRepo db.UnsafeCRUDRepo[models.Profile]
20+
songRepo db.UnsafeCRUDRepo[models.Song]
21+
playlistRepo db.UnsafeCRUDRepo[models.Playlist]
22+
playlistSongsRepo db.UnsafeCRUDRepo[models.PlaylistSong]
23+
playlistOwnerRepo db.UnsafeCRUDRepo[models.PlaylistOwner]
24+
25+
profiles = tests.Profiles()
26+
songs = tests.Songs()
27+
playlists = tests.Playlists()
28+
29+
random = rand.New(rand.NewSource(time.Now().UnixMicro()))
30+
)
31+
32+
func SeedDb() error {
33+
var err error
34+
35+
dbConn, err = db.Connector()
36+
if err != nil {
37+
return err
38+
}
39+
40+
accountRepo = db.NewBaseDB[models.Account](dbConn)
41+
profileRepo = db.NewBaseDB[models.Profile](dbConn)
42+
songRepo = db.NewBaseDB[models.Song](dbConn)
43+
playlistRepo = db.NewBaseDB[models.Playlist](dbConn)
44+
playlistSongsRepo = db.NewBaseDB[models.PlaylistSong](dbConn)
45+
playlistOwnerRepo = db.NewBaseDB[models.PlaylistOwner](dbConn)
46+
47+
playlistService := playlistspkg.New(playlistRepo, playlistOwnerRepo, nil)
48+
49+
pl, err := playlistService.GetAll(400)
50+
if err != nil {
51+
return err
52+
}
53+
log.Infof("%+v\n", pl)
54+
55+
err = playlistService.CreatePlaylist(entities.Playlist{
56+
Title: "Danki Muzikki",
57+
}, 400)
58+
if err != nil {
59+
return err
60+
}
61+
62+
err = playlistService.DeletePlaylist("a1a4b25f6eac4fb08222d14cadcfc7cd", 400)
63+
if err != nil {
64+
return err
65+
}
66+
67+
return nil
68+
69+
err = seedProfiles()
70+
if err != nil {
71+
return err
72+
}
73+
74+
err = seedPlaylists()
75+
if err != nil {
76+
return err
77+
}
78+
79+
err = addPlaylistsToProfiles()
80+
if err != nil {
81+
return err
82+
}
83+
84+
return nil
85+
}
86+
87+
func seedProfiles() error {
88+
for i := range profiles {
89+
err := profileRepo.Add(&profiles[i])
90+
if err != nil {
91+
return err
92+
}
93+
}
94+
return nil
95+
}
96+
97+
func seedPlaylists() error {
98+
for i := range playlists {
99+
for _, song := range playlists[i].Songs {
100+
err := songRepo.Add(song)
101+
if err != nil {
102+
return err
103+
}
104+
}
105+
err := playlistRepo.Add(&playlists[i])
106+
if err != nil {
107+
return err
108+
}
109+
}
110+
return nil
111+
}
112+
113+
func addPlaylistsToProfiles() error {
114+
for i := 0; i < len(profiles)*3; i++ {
115+
// ignore errors because there will be duplicates lol
116+
_ = playlistOwnerRepo.Add(&models.PlaylistOwner{
117+
ProfileId: profiles[i%len(profiles)].Id,
118+
PlaylistId: playlists[random.Intn(len(playlists))].Id,
119+
Permissions: models.WritePermission,
120+
})
121+
random.Seed(time.Now().UnixNano())
122+
}
123+
return nil
124+
}

cmd/server/server.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package server
33
import (
44
"dankmuzikk/config"
55
"dankmuzikk/db"
6+
"dankmuzikk/handlers"
67
"dankmuzikk/handlers/apis"
78
"dankmuzikk/handlers/pages"
89
"dankmuzikk/log"
910
"dankmuzikk/models"
1011
"dankmuzikk/services/jwt"
1112
"dankmuzikk/services/login"
13+
"dankmuzikk/services/playlists"
14+
"dankmuzikk/services/playlists/songs"
1215
"dankmuzikk/services/youtube/download"
1316
"dankmuzikk/services/youtube/search"
1417
"embed"
@@ -25,30 +28,40 @@ func StartServer(staticFS embed.FS) error {
2528
profileRepo := db.NewBaseDB[models.Profile](dbConn)
2629
otpRepo := db.NewBaseDB[models.EmailVerificationCode](dbConn)
2730
songRepo := db.NewBaseDB[models.Song](dbConn)
28-
// playlistRepo := db.NewBaseDB[models.Playlist](dbConn)
31+
playlistRepo := db.NewBaseDB[models.Playlist](dbConn)
32+
playlistOwnersRepo := db.NewBaseDB[models.PlaylistOwner](dbConn)
33+
playlistSongssRepo := db.NewBaseDB[models.PlaylistSong](dbConn)
34+
35+
downloadService := download.New(songRepo)
36+
playlistsService := playlists.New(playlistRepo, playlistOwnersRepo, downloadService)
37+
songsService := songs.New(playlistSongssRepo, songRepo, playlistRepo)
2938

3039
jwtUtil := jwt.NewJWTImpl()
3140

41+
gHandler := handlers.NewHandler(profileRepo, jwtUtil)
42+
3243
///////////// Pages and files /////////////
3344
pagesHandler := http.NewServeMux()
3445
pagesHandler.Handle("/static/", http.FileServer(http.FS(staticFS)))
3546
pagesHandler.Handle("/music/", http.StripPrefix("/music", http.FileServer(http.Dir(config.Env().YouTube.MusicDir))))
3647

37-
pagesRouter := pages.NewPagesHandler(profileRepo, jwtUtil)
38-
pagesHandler.HandleFunc("/", pagesRouter.Handler(pagesRouter.HandleHomePage))
39-
pagesHandler.HandleFunc("/signup", pagesRouter.AuthHandler(pagesRouter.HandleSignupPage))
40-
pagesHandler.HandleFunc("/login", pagesRouter.AuthHandler(pagesRouter.HandleLoginPage))
41-
pagesHandler.HandleFunc("/profile", pagesRouter.AuthHandler(pagesRouter.HandleProfilePage))
42-
pagesHandler.HandleFunc("/about", pagesRouter.Handler(pagesRouter.HandleAboutPage))
43-
pagesHandler.HandleFunc("/playlists", pagesRouter.AuthHandler(pagesRouter.HandlePlaylistsPage))
44-
pagesHandler.HandleFunc("/privacy", pagesRouter.Handler(pagesRouter.HandlePrivacyPage))
45-
pagesHandler.HandleFunc("/search", pagesRouter.Handler(pagesRouter.HandleSearchResultsPage(&search.ScraperSearch{})))
48+
pagesRouter := pages.NewPagesHandler(profileRepo, playlistsService, jwtUtil)
49+
pagesHandler.HandleFunc("/", gHandler.OptionalAuthPage(pagesRouter.HandleHomePage))
50+
pagesHandler.HandleFunc("/signup", gHandler.AuthPage(pagesRouter.HandleSignupPage))
51+
pagesHandler.HandleFunc("/login", gHandler.AuthPage(pagesRouter.HandleLoginPage))
52+
pagesHandler.HandleFunc("/profile", gHandler.AuthPage(pagesRouter.HandleProfilePage))
53+
pagesHandler.HandleFunc("/about", gHandler.NoAuthPage(pagesRouter.HandleAboutPage))
54+
pagesHandler.HandleFunc("/playlists", gHandler.AuthPage(pagesRouter.HandlePlaylistsPage))
55+
pagesHandler.HandleFunc("/playlist/{playlist_id}", gHandler.AuthPage(pagesRouter.HandleSinglePlaylistPage))
56+
pagesHandler.HandleFunc("/privacy", gHandler.NoAuthPage(pagesRouter.HandlePrivacyPage))
57+
pagesHandler.HandleFunc("/search", gHandler.OptionalAuthPage(pagesRouter.HandleSearchResultsPage(&search.ScraperSearch{})))
4658

4759
///////////// APIs /////////////
4860

4961
emailLoginApi := apis.NewEmailLoginApi(login.NewEmailLoginService(accountRepo, profileRepo, otpRepo, jwtUtil))
5062
googleLoginApi := apis.NewGoogleLoginApi(login.NewGoogleLoginService(accountRepo, profileRepo, otpRepo, jwtUtil))
51-
songDownloadApi := apis.NewDownloadHandler(*download.New(songRepo))
63+
songDownloadApi := apis.NewDownloadHandler(downloadService)
64+
playlistsApi := apis.NewPlaylistApi(playlistsService, songsService)
5265

5366
apisHandler := http.NewServeMux()
5467
apisHandler.HandleFunc("POST /login/email", emailLoginApi.HandleEmailLogin)
@@ -60,6 +73,9 @@ func StartServer(staticFS embed.FS) error {
6073
apisHandler.HandleFunc("GET /logout", apis.HandleLogout)
6174
apisHandler.HandleFunc("GET /search-suggestion", apis.HandleSearchSuggestions)
6275
apisHandler.HandleFunc("GET /song/download", songDownloadApi.HandleDownloadSong)
76+
apisHandler.HandleFunc("GET /song/download/queue", songDownloadApi.HandleDownloadSongToQueue)
77+
apisHandler.HandleFunc("POST /playlist", gHandler.AuthApi(playlistsApi.HandleCreatePlaylist))
78+
apisHandler.HandleFunc("PUT /toggle-song-in-playlist", gHandler.AuthApi(playlistsApi.HandleToggleSongInPlaylist))
6379

6480
applicationHandler := http.NewServeMux()
6581
applicationHandler.Handle("/", pagesHandler)

db/allowed_models.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import "dankmuzikk/models"
44

55
type AllowedModels interface {
66
models.Account | models.Profile | models.EmailVerificationCode |
7-
models.Song
7+
models.Song | models.Playlist | models.PlaylistSong | models.PlaylistOwner
88
GetId() uint
99
}

db/base_db.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (b *BaseDB[T]) Add(obj *T) error {
2828
Error
2929

3030
if err != nil {
31-
return tryWrapMySqlError(err)
31+
return TryWrapMySqlError(err)
3232
}
3333

3434
return nil
@@ -46,19 +46,25 @@ func (b *BaseDB[T]) AddMany(objs []*T) error {
4646
Error
4747

4848
if err != nil {
49-
return tryWrapMySqlError(err)
49+
return TryWrapMySqlError(err)
5050
}
5151

5252
return nil
5353
}
5454

5555
// Exists checks the existence of the given record's id
56-
func (b *BaseDB[T]) Exists(id uint) bool {
56+
func (b *BaseDB[T]) Exists(id uint) error {
5757
if id == 0 { // better to check this before, fetching eh?
58-
return false
58+
return ErrRecordNotFound
5959
}
60-
_, err := b.Get(id)
61-
return err == nil
60+
61+
var obj T
62+
return b.
63+
db.
64+
Select("id").
65+
Where("id = ?", id).
66+
First(&obj).
67+
Error
6268
}
6369

6470
// Get retrieves the object which has the given id

db/crud_repo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type CreatorRepo[T any] interface {
1515
// GetterRepo is a safe wrapper for records retrieval for a certain repo
1616
type GetterRepo[T any] interface {
1717
// Exists checks the existence of the given record's id
18-
Exists(id uint) bool
18+
Exists(id uint) error
1919

2020
// Get retrieves the object which has the given id
2121
Get(id uint) (T, error)

db/db.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func getDBConnector(dbName string) (*gorm.DB, error) {
2929
return nil, err
3030
}
3131
instance, err := gorm.Open(mysql.Open(
32-
fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=True&loc=Local",
32+
fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=True&loc=Local&charset=utf8mb4",
3333
config.Env().DB.Username,
3434
config.Env().DB.Password,
3535
config.Env().DB.Host,

db/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var (
1414
ErrRecordExists = errors.New("db: record exists in table")
1515
)
1616

17-
func tryWrapMySqlError(err error) error {
17+
func TryWrapMySqlError(err error) error {
1818
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
1919
switch mysqlErr.Number {
2020
case 1062:

entities/playlist.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package entities
2+
3+
type Playlist struct {
4+
PublicId string `json:"public_id"`
5+
Title string `json:"title"`
6+
SongsCount int `json:"songs_count"`
7+
Songs []Song `json:"songs"`
8+
}

entities/song.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package entities
2+
3+
type Song struct {
4+
YtId string `json:"yt_id"`
5+
Title string `json:"title"`
6+
Artist string `json:"artist"`
7+
ThumbnailUrl string `json:"thumbnail_url"`
8+
Duration string `json:"duration"`
9+
}

handlers/apis/play_song.go

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)