From 8b3c2c77213a79aca81ebd912fff123c1c76599c Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Tue, 5 Sep 2023 15:53:31 -0400 Subject: [PATCH 01/19] Better File Browser names --- gamescollection/collection.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 77d3f2d..4c6f491 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -11,6 +11,7 @@ import ( "io" "log" "os" + "path" "strings" "github.com/DblK/tinshop/repository" @@ -221,7 +222,7 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { for _, file := range newGames { game := repository.GameFileType{ - URL: c.config.RootShop() + "/games/" + file.GameID + "#" + file.GameInfo, + URL: c.config.RootShop() + "/games/" + file.GameID + "#" + path.Base(file.Path), Size: file.Size, } From f74182d623da6e4f24b4c60a94fb75b4204c4e5f Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Tue, 5 Sep 2023 22:04:23 -0400 Subject: [PATCH 02/19] File Browser now displays more relevant info --- gamescollection/collection.go | 61 +++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 4c6f491..432fccd 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -8,10 +8,11 @@ package gamescollection import ( "encoding/json" "errors" + "fmt" "io" "log" + "math/big" "os" - "path" "strings" "github.com/DblK/tinshop/repository" @@ -221,8 +222,26 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { var gameList = make([]repository.GameFileType, 0) for _, file := range newGames { + + baseID, update, dlc := GetTitleMeta(file.GameID) + baseTitle := c.Library()[baseID] + title := c.Library()[file.GameID] + + var extra = " [BASE]" + + if dlc { + extra = " - " + title.Name + " [DLC]" + } + + if update { + extra = fmt.Sprintf(" [v%d]", title.Version) + } + + + log.Println(baseTitle.Name + extra) + game := repository.GameFileType{ - URL: c.config.RootShop() + "/games/" + file.GameID + "#" + path.Base(file.Path), + URL: c.config.RootShop() + "/games/" + file.GameID + "#" + baseTitle.Name + extra, Size: file.Size, } @@ -259,3 +278,41 @@ func (c *collect) GetKey(gameID string) (string, error) { } return string(key), nil } + +// GetTtitleMeta returns the BaseID of the content, as well as Update / DLC flags +func GetTitleMeta(titleID string) (string, bool, bool) { + var lastDigit = titleID[len(titleID)-1:] + var baseID = strings.Join([]string{titleID[:len(titleID)-3], "000"}, "") + var update = false + var dlc = false + + if titleID != baseID { + update = true + } + + if lastDigit != "0" { + dlc = true + update = false + + // Parse the hexadecimal string into a big integer + intValue, success := new(big.Int).SetString(baseID, 16) + if !success { + return "", false, false + } + + // Parse the subtraction value (in hexadecimal) + subtractionValue := new(big.Int) + subtractionValue, success = subtractionValue.SetString("1000", 16) + if !success { + return "", false, false + } + + // Subtract the values + intValue.Sub(intValue, subtractionValue) + + // Convert the resulting integer back to a hexadecimal string, left padded with 0 to 16 chars + baseID = fmt.Sprintf("0000000000000000%X", intValue) + baseID = baseID[len(baseID)-16:] + } + return baseID, update, dlc +} \ No newline at end of file From 590b047e4220a90f458395013b6ade3c141361a4 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Tue, 5 Sep 2023 22:06:02 -0400 Subject: [PATCH 03/19] Fix formatting (fix lint) --- gamescollection/collection.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 432fccd..274ff31 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -222,7 +222,6 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { var gameList = make([]repository.GameFileType, 0) for _, file := range newGames { - baseID, update, dlc := GetTitleMeta(file.GameID) baseTitle := c.Library()[baseID] title := c.Library()[file.GameID] From 4b07f4daa0e955266851b9889ae9c61d00dd0a02 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Tue, 5 Sep 2023 22:08:43 -0400 Subject: [PATCH 04/19] gofmt --- gamescollection/collection.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 274ff31..7432cd3 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -230,15 +230,14 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { if dlc { extra = " - " + title.Name + " [DLC]" - } + } if update { extra = fmt.Sprintf(" [v%d]", title.Version) } - log.Println(baseTitle.Name + extra) - + game := repository.GameFileType{ URL: c.config.RootShop() + "/games/" + file.GameID + "#" + baseTitle.Name + extra, Size: file.Size, @@ -314,4 +313,4 @@ func GetTitleMeta(titleID string) (string, bool, bool) { baseID = baseID[len(baseID)-16:] } return baseID, update, dlc -} \ No newline at end of file +} From 0dbfcd49b8ed969e71e2ef97b222ffc5874a9500 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Tue, 5 Sep 2023 22:17:19 -0400 Subject: [PATCH 05/19] Add extension to File Browser so title can install --- gamescollection/collection.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 7432cd3..086200e 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -13,6 +13,7 @@ import ( "log" "math/big" "os" + "path/filepath" "strings" "github.com/DblK/tinshop/repository" @@ -236,10 +237,8 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { extra = fmt.Sprintf(" [v%d]", title.Version) } - log.Println(baseTitle.Name + extra) - game := repository.GameFileType{ - URL: c.config.RootShop() + "/games/" + file.GameID + "#" + baseTitle.Name + extra, + URL: c.config.RootShop() + "/games/" + file.GameID + "#" + baseTitle.Name + extra + filepath.Ext(file.Path), Size: file.Size, } From ad5581760a632f8b1490d1fbc613b6ccf4be0811 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Tue, 5 Sep 2023 22:37:16 -0400 Subject: [PATCH 06/19] Better formatting for code --- gamescollection/collection.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 086200e..0cd8018 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -226,19 +226,26 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { baseID, update, dlc := GetTitleMeta(file.GameID) baseTitle := c.Library()[baseID] title := c.Library()[file.GameID] + extension := filepath.Ext(file.Path) - var extra = " [BASE]" + // Default extra for Base title + var extra = "[BASE]" + // Append DLC Name and tag when dlc if dlc { - extra = " - " + title.Name + " [DLC]" + extra = fmt.Sprintf("- %s [DLC]", title.Name) } + // Append version when update if update { - extra = fmt.Sprintf(" [v%d]", title.Version) + extra = fmt.Sprintf("[v%d]", title.Version) } + // Build the friendly name for Tinfoil + friendlyName := fmt.Sprintf("%s (%s) %s%s", baseTitle.Name, baseTitle.Region, extra, extension) + game := repository.GameFileType{ - URL: c.config.RootShop() + "/games/" + file.GameID + "#" + baseTitle.Name + extra + filepath.Ext(file.Path), + URL: c.config.RootShop() + "/games/" + file.GameID + "#" + friendlyName, Size: file.Size, } From 5d699bc6c3e6d4947e3f5d62898eea6768a1e66e Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Wed, 6 Sep 2023 08:48:09 -0400 Subject: [PATCH 07/19] Add [UPD] for Updates --- gamescollection/collection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 0cd8018..e58da67 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -238,7 +238,7 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { // Append version when update if update { - extra = fmt.Sprintf("[v%d]", title.Version) + extra = fmt.Sprintf("[v%d][UPD]", title.Version) } // Build the friendly name for Tinfoil From d1a7db23056f02993f07e9fc95d9d60d2cad1bc9 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Wed, 6 Sep 2023 09:36:57 -0400 Subject: [PATCH 08/19] Add ID to Friendly Name --- gamescollection/collection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index e58da67..6432f90 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -242,7 +242,7 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { } // Build the friendly name for Tinfoil - friendlyName := fmt.Sprintf("%s (%s) %s%s", baseTitle.Name, baseTitle.Region, extra, extension) + friendlyName := fmt.Sprintf("[%s] %s (%s) %s%s", file.GameID, baseTitle.Name, baseTitle.Region, extra, extension) game := repository.GameFileType{ URL: c.config.RootShop() + "/games/" + file.GameID + "#" + friendlyName, From 6bdc71bf5684e44d08d53866ab4750f9722bd4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Boulanouar?= Date: Wed, 6 Sep 2023 17:05:52 +0200 Subject: [PATCH 09/19] chore: move GetTitleMeta to utils --- gamescollection/collection.go | 41 +---------------------------------- utils/utils.go | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 6432f90..035acd8 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -11,7 +11,6 @@ import ( "fmt" "io" "log" - "math/big" "os" "path/filepath" "strings" @@ -223,7 +222,7 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { var gameList = make([]repository.GameFileType, 0) for _, file := range newGames { - baseID, update, dlc := GetTitleMeta(file.GameID) + baseID, update, dlc := utils.GetTitleMeta(file.GameID) baseTitle := c.Library()[baseID] title := c.Library()[file.GameID] extension := filepath.Ext(file.Path) @@ -282,41 +281,3 @@ func (c *collect) GetKey(gameID string) (string, error) { } return string(key), nil } - -// GetTtitleMeta returns the BaseID of the content, as well as Update / DLC flags -func GetTitleMeta(titleID string) (string, bool, bool) { - var lastDigit = titleID[len(titleID)-1:] - var baseID = strings.Join([]string{titleID[:len(titleID)-3], "000"}, "") - var update = false - var dlc = false - - if titleID != baseID { - update = true - } - - if lastDigit != "0" { - dlc = true - update = false - - // Parse the hexadecimal string into a big integer - intValue, success := new(big.Int).SetString(baseID, 16) - if !success { - return "", false, false - } - - // Parse the subtraction value (in hexadecimal) - subtractionValue := new(big.Int) - subtractionValue, success = subtractionValue.SetString("1000", 16) - if !success { - return "", false, false - } - - // Subtract the values - intValue.Sub(intValue, subtractionValue) - - // Convert the resulting integer back to a hexadecimal string, left padded with 0 to 16 chars - baseID = fmt.Sprintf("0000000000000000%X", intValue) - baseID = baseID[len(baseID)-16:] - } - return baseID, update, dlc -} diff --git a/utils/utils.go b/utils/utils.go index 29e941d..d585f2f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,6 +6,8 @@ package utils import ( + "fmt" + "math/big" "net/http" "reflect" "regexp" @@ -43,6 +45,44 @@ func ExtractGameID(fileName string) repository.GameID { return gameid.New(strings.ToUpper(matches[1]), "["+strings.ToUpper(matches[1])+"]["+matches[2]+"]."+ext[len(ext)-1], ext[len(ext)-1]) } +// GetTitleMeta returns the BaseID of the content, as well as Update / DLC flags +func GetTitleMeta(titleID string) (string, bool, bool) { + var lastDigit = titleID[len(titleID)-1:] + var baseID = strings.Join([]string{titleID[:len(titleID)-3], "000"}, "") + var update = false + var dlc = false + + if titleID != baseID { + update = true + } + + if lastDigit != "0" { + dlc = true + update = false + + // Parse the hexadecimal string into a big integer + intValue, success := new(big.Int).SetString(baseID, 16) + if !success { + return "", false, false + } + + // Parse the subtraction value (in hexadecimal) + subtractionValue := new(big.Int) + subtractionValue, success = subtractionValue.SetString("1000", 16) + if !success { + return "", false, false + } + + // Subtract the values + intValue.Sub(intValue, subtractionValue) + + // Convert the resulting integer back to a hexadecimal string, left padded with 0 to 16 chars + baseID = fmt.Sprintf("0000000000000000%X", intValue) + baseID = baseID[len(baseID)-16:] + } + return baseID, update, dlc +} + // Search returns the index in an object func Search(length int, f func(index int) bool) int { for index := 0; index < length; index++ { From 31151a1b6023d95ca056d37ba71e00e8ed10d830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Boulanouar?= Date: Wed, 6 Sep 2023 17:34:55 +0200 Subject: [PATCH 10/19] chore: add tests and refactor code --- gamescollection/collection.go | 57 +++--- gamescollection/collection_test.go | 293 ++++++++++++++++++++--------- repository/interfaces.go | 11 +- sources/directory/private.go | 1 + sources/nfs/private.go | 1 + 5 files changed, 241 insertions(+), 122 deletions(-) diff --git a/gamescollection/collection.go b/gamescollection/collection.go index 035acd8..a22e64d 100644 --- a/gamescollection/collection.go +++ b/gamescollection/collection.go @@ -12,7 +12,6 @@ import ( "io" "log" "os" - "path/filepath" "strings" "github.com/DblK/tinshop/repository" @@ -222,29 +221,8 @@ func (c *collect) AddNewGames(newGames []repository.FileDesc) { var gameList = make([]repository.GameFileType, 0) for _, file := range newGames { - baseID, update, dlc := utils.GetTitleMeta(file.GameID) - baseTitle := c.Library()[baseID] - title := c.Library()[file.GameID] - extension := filepath.Ext(file.Path) - - // Default extra for Base title - var extra = "[BASE]" - - // Append DLC Name and tag when dlc - if dlc { - extra = fmt.Sprintf("- %s [DLC]", title.Name) - } - - // Append version when update - if update { - extra = fmt.Sprintf("[v%d][UPD]", title.Version) - } - - // Build the friendly name for Tinfoil - friendlyName := fmt.Sprintf("[%s] %s (%s) %s%s", file.GameID, baseTitle.Name, baseTitle.Region, extra, extension) - game := repository.GameFileType{ - URL: c.config.RootShop() + "/games/" + file.GameID + "#" + friendlyName, + URL: c.config.RootShop() + "/games/" + file.GameID + "#" + c.getFriendlyName(file), Size: file.Size, } @@ -281,3 +259,36 @@ func (c *collect) GetKey(gameID string) (string, error) { } return string(key), nil } + +func (c *collect) getFriendlyName(file repository.FileDesc) string { + baseID, update, dlc := utils.GetTitleMeta(file.GameID) + baseTitle := c.Library()[baseID] + title := c.Library()[file.GameID] + + // Default extra for Base title + var extra = " [BASE]" + + // Append DLC Name and tag when dlc + if dlc { + extra = " - " + title.Name + " [DLC]" + } + + // Append version when update + if update { + extra = fmt.Sprintf(" [v%d][UPD]", title.Version) + } + + name := "" + if baseTitle.Name != "" { + name = " " + baseTitle.Name + } + + region := "" + if baseTitle.Region != "" { + region = " (" + baseTitle.Region + ")" + } + + // Build the friendly name for Tinfoil + reg := []string{"[" + file.GameID + "]", name, region, extra, "." + file.Extension} + return strings.Join(reg[:], "") +} diff --git a/gamescollection/collection_test.go b/gamescollection/collection_test.go index e96fac4..c5dec7e 100644 --- a/gamescollection/collection_test.go +++ b/gamescollection/collection_test.go @@ -55,104 +55,209 @@ var _ = Describe("Collection", func() { Expect(games.Titledb).To(HaveLen(0)) }) Context("No TitleDB", func() { - Context("With TitleDB", func() { - JustBeforeEach(func() { - customDB := make(map[string]repository.TitleDBEntry) - custom1 := repository.TitleDBEntry{ - ID: "0000000000000001", - Languages: []string{"FR", "EN", "US"}, - NumberOfPlayers: 1, - IconURL: "http://fake.icon.url", - } - customDB["0000000000000001"] = custom1 - myMockConfig = mock_repository.NewMockConfig(ctrl) - myMockConfig.EXPECT(). - CustomDB(). - Return(customDB). - AnyTimes() - myMockConfig.EXPECT(). - BannedTheme(). - Return(nil). - AnyTimes() - myMockConfig.EXPECT(). - RootShop(). - Return("http://tinshop.example.com"). - AnyTimes() - myMockConfig.EXPECT(). - WelcomeMessage(). - Return("Welcome to testing shop!"). - AnyTimes() - myMockConfig.EXPECT(). - NoWelcomeMessage(). - Return(false). - AnyTimes() + // TODO: Need to add tests here! + }) + Context("With TitleDB", func() { + JustBeforeEach(func() { + customDB := make(map[string]repository.TitleDBEntry) + custom1 := repository.TitleDBEntry{ // Base + ID: "010034500641A000", + Languages: []string{"FR", "EN", "US"}, + Name: "Attack on Titan 2", + Region: "US", + NumberOfPlayers: 1, + IconURL: "http://fake.icon.url", + } + custom2 := repository.TitleDBEntry{ // Update + ID: "010034500641A800", + Version: 917504, + } + custom3 := repository.TitleDBEntry{ // DLC + ID: "010034500641B001", + Name: "Additional Episode, \"A Sudden Rain\"", + Version: 131072, + } + custom4 := repository.TitleDBEntry{ // Base (No Region) + ID: "0100574002AF4000", + Languages: []string{"FR", "EN", "US"}, + Name: "ONE PIECE: Unlimited World Red Deluxe Edition", + NumberOfPlayers: 1, + } + custom5 := repository.TitleDBEntry{ // Base (No info) + ID: "010034501225C000", + } + customDB["010034500641A000"] = custom1 + customDB["010034500641A800"] = custom2 + customDB["010034500641B001"] = custom3 + customDB["0100574002AF4000"] = custom4 + customDB["010034501225C000"] = custom5 + myMockConfig = mock_repository.NewMockConfig(ctrl) + myMockConfig.EXPECT(). + CustomDB(). + Return(customDB). + AnyTimes() + myMockConfig.EXPECT(). + BannedTheme(). + Return(nil). + AnyTimes() + myMockConfig.EXPECT(). + RootShop(). + Return("http://tinshop.example.com"). + AnyTimes() + myMockConfig.EXPECT(). + WelcomeMessage(). + Return("Welcome to testing shop!"). + AnyTimes() + myMockConfig.EXPECT(). + NoWelcomeMessage(). + Return(false). + AnyTimes() + + testCollection.OnConfigUpdate(myMockConfig) + }) + It("Add a base game", func() { + newGames := make([]repository.FileDesc, 0) + newFile := repository.FileDesc{ + Size: 42, + Path: "/here/is/my/game", + GameID: "010034500641A000", + GameInfo: "[010034500641A000][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newGames = append(newGames, newFile) + testCollection.AddNewGames(newGames) + + games := testCollection.Games() + Expect(games.Files).To(HaveLen(1)) + Expect(games.Titledb).To(HaveLen(1)) + Expect(games.Files[0].URL).To(Equal("http://tinshop.example.com/games/010034500641A000#[010034500641A000] Attack on Titan 2 (US) [BASE].nsp")) + }) + It("Add a base game (without Region)", func() { + newGames := make([]repository.FileDesc, 0) + newFile := repository.FileDesc{ + Size: 42, + Path: "/here/is/my/game", + GameID: "0100574002AF4000", + GameInfo: "[0100574002AF4000][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newGames = append(newGames, newFile) + testCollection.AddNewGames(newGames) + + games := testCollection.Games() + Expect(games.Files).To(HaveLen(1)) + Expect(games.Titledb).To(HaveLen(1)) + Expect(games.Files[0].URL).To(Equal("http://tinshop.example.com/games/0100574002AF4000#[0100574002AF4000] ONE PIECE: Unlimited World Red Deluxe Edition [BASE].nsp")) + }) + It("Add a base game (without any information)", func() { + newGames := make([]repository.FileDesc, 0) + newFile := repository.FileDesc{ + Size: 42, + Path: "/here/is/my/game", + GameID: "010034501225C000", + GameInfo: "[010034501225C000][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newGames = append(newGames, newFile) + testCollection.AddNewGames(newGames) + + games := testCollection.Games() + Expect(games.Files).To(HaveLen(1)) + Expect(games.Titledb).To(HaveLen(1)) + Expect(games.Files[0].URL).To(Equal("http://tinshop.example.com/games/010034501225C000#[010034501225C000] [BASE].nsp")) + }) + It("Add a DLC game", func() { + newGames := make([]repository.FileDesc, 0) + newFile := repository.FileDesc{ + Size: 42, + Path: "/here/is/my/game", + GameID: "010034500641B001", + GameInfo: "[010034500641B001][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newGames = append(newGames, newFile) + testCollection.AddNewGames(newGames) - testCollection.OnConfigUpdate(myMockConfig) - }) - It("Add a game", func() { - newGames := make([]repository.FileDesc, 0) - newFile := repository.FileDesc{ - Size: 42, - Path: "/here/is/my/game", - GameID: "0000000000000001", - GameInfo: "[0000000000000001][v0].nsp", - HostType: repository.LocalFile, - } - newGames = append(newGames, newFile) - testCollection.AddNewGames(newGames) + games := testCollection.Games() + Expect(games.Files).To(HaveLen(1)) + Expect(games.Titledb).To(HaveLen(1)) + Expect(games.Files[0].URL).To(Equal("http://tinshop.example.com/games/010034500641B001#[010034500641B001] Attack on Titan 2 (US) - Additional Episode, \"A Sudden Rain\" [DLC].nsp")) + }) + It("Add an UPDATE game", func() { + newGames := make([]repository.FileDesc, 0) + newFile := repository.FileDesc{ + Size: 42, + Path: "/here/is/my/game", + GameID: "010034500641A800", + GameInfo: "[010034500641A800][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newGames = append(newGames, newFile) + testCollection.AddNewGames(newGames) - games := testCollection.Games() - Expect(games.Files).To(HaveLen(1)) - Expect(games.Titledb).To(HaveLen(1)) - }) - It("Add a duplicate game", func() { - newGames := make([]repository.FileDesc, 0) - newFile1 := repository.FileDesc{ - Size: 42, - Path: "/here/is/my/game", - GameID: "0000000000000001", - GameInfo: "[0000000000000001][v0].nsp", - HostType: repository.LocalFile, - } - newFile2 := repository.FileDesc{ - Size: 43, - Path: "/here/is/my/game", - GameID: "0000000000000001", - GameInfo: "[0000000000000001][v0].nsp", - HostType: repository.LocalFile, - } - newGames = append(newGames, newFile1) - newGames = append(newGames, newFile2) - testCollection.AddNewGames(newGames) + games := testCollection.Games() + Expect(games.Files).To(HaveLen(1)) + Expect(games.Titledb).To(HaveLen(1)) + Expect(games.Files[0].URL).To(Equal("http://tinshop.example.com/games/010034500641A800#[010034500641A800] Attack on Titan 2 (US) [v917504][UPD].nsp")) + }) + It("Add a duplicate game", func() { + newGames := make([]repository.FileDesc, 0) + newFile1 := repository.FileDesc{ + Size: 42, + Path: "/here/is/my/game", + GameID: "010034500641A000", + GameInfo: "[010034500641A000][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newFile2 := repository.FileDesc{ + Size: 43, + Path: "/here/is/my/game", + GameID: "010034500641A000", + GameInfo: "[010034500641A000][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newGames = append(newGames, newFile1) + newGames = append(newGames, newFile2) + testCollection.AddNewGames(newGames) - games := testCollection.Games() - Expect(games.Files).To(HaveLen(1)) - Expect(games.Titledb).To(HaveLen(1)) - }) - It("Add a duplicate game (with different path)", func() { - newGames := make([]repository.FileDesc, 0) - newFile1 := repository.FileDesc{ - Size: 42, - Path: "/here/is/my/game1", - GameID: "0000000000000001", - GameInfo: "[0000000000000001][v0].nsp", - HostType: repository.LocalFile, - } - newFile2 := repository.FileDesc{ - Size: 43, - Path: "/here/is/my/game2", - GameID: "0000000000000001", - GameInfo: "[0000000000000001][v0].nsp", - HostType: repository.LocalFile, - } - newGames = append(newGames, newFile1) - newGames = append(newGames, newFile2) - testCollection.AddNewGames(newGames) + games := testCollection.Games() + Expect(games.Files).To(HaveLen(1)) + Expect(games.Titledb).To(HaveLen(1)) + Expect(games.Files[0].URL).To(Equal("http://tinshop.example.com/games/010034500641A000#[010034500641A000] Attack on Titan 2 (US) [BASE].nsp")) + }) + It("Add a duplicate game (with different path)", func() { + newGames := make([]repository.FileDesc, 0) + newFile1 := repository.FileDesc{ + Size: 42, + Path: "/here/is/my/game1", + GameID: "010034500641A000", + GameInfo: "[010034500641A000][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newFile2 := repository.FileDesc{ + Size: 43, + Path: "/here/is/my/game2", + GameID: "010034500641A000", + GameInfo: "[010034500641A000][v0].nsp", + Extension: "nsp", + HostType: repository.LocalFile, + } + newGames = append(newGames, newFile1) + newGames = append(newGames, newFile2) + testCollection.AddNewGames(newGames) - games := testCollection.Games() - Expect(games.Files).To(HaveLen(1)) - Expect(games.Titledb).To(HaveLen(1)) - }) + games := testCollection.Games() + Expect(games.Files).To(HaveLen(1)) + Expect(games.Titledb).To(HaveLen(1)) + Expect(games.Files[0].URL).To(Equal("http://tinshop.example.com/games/010034500641A000#[010034500641A000] Attack on Titan 2 (US) [BASE].nsp")) }) }) }) diff --git a/repository/interfaces.go b/repository/interfaces.go index ff4fb0a..4135df1 100644 --- a/repository/interfaces.go +++ b/repository/interfaces.go @@ -78,11 +78,12 @@ const ( // FileDesc structure type FileDesc struct { - GameID string - Size int64 - GameInfo string - Path string - HostType HostType + GameID string + Size int64 + GameInfo string + Path string + Extension string + HostType HostType } // GameType structure diff --git a/sources/directory/private.go b/sources/directory/private.go index 1706df6..7a2540d 100644 --- a/sources/directory/private.go +++ b/sources/directory/private.go @@ -49,6 +49,7 @@ func (src *directorySource) addDirectoryGame(gameFiles []repository.FileDesc, ex newFile.GameID = names.ShortID() newFile.GameInfo = names.FullID() newFile.HostType = repository.LocalFile + newFile.Extension = names.Extension() if src.config.VerifyNSP() { valid, errTicket := src.nspCheck(newFile) diff --git a/sources/nfs/private.go b/sources/nfs/private.go index de1c550..d63afab 100644 --- a/sources/nfs/private.go +++ b/sources/nfs/private.go @@ -106,6 +106,7 @@ func (src *nfsSource) lookIntoNfsDirectory(v *nfs.Target, share, path string) [] newFile.GameID = names.ShortID() newFile.GameInfo = names.FullID() newFile.HostType = repository.NFSShare + newFile.Extension = names.Extension() var valid = true var errTicket error From b90afa9531773935c8b5b08e71b3c7f9a1360d3e Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Fri, 8 Sep 2023 20:44:39 -0400 Subject: [PATCH 11/19] Accept ENV Vars (Docker compatilibity) --- config/config.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 46fb8d2..51fabe2 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ import ( "net" "os" "strconv" + "strings" "github.com/DblK/tinshop/repository" "github.com/DblK/tinshop/utils" @@ -69,6 +70,9 @@ func (cfg *File) LoadConfig() { viper.SetDefault("sources.directories", "./games") viper.SetDefault("welcomeMessage", "Welcome to your own TinShop!") viper.SetDefault("noWelcomeMessage", false) + viper.SetEnvPrefix("TINSHOP") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { @@ -169,7 +173,7 @@ func ComputeDefaultValues(config repository.Config) repository.Config { rootShop += ":" + strconv.Itoa(config.Port()) } } - log.Println((rootShop)) + log.Println(rootShop) config.SetRootShop(rootShop) config.SetShopTemplateData(repository.ShopTemplate{ From 00d2c45eac7e04dae87ed824ce64dd93408b9de9 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Fri, 8 Sep 2023 21:27:18 -0400 Subject: [PATCH 12/19] Initial Docker README --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/README.md b/README.md index 22230e2..7c3ee73 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,48 @@ To proper use this software, here is the checklist: Now simply run it and add a shop inside tinfoil with the address setup in `config` (or `http://localIp:3000` if not specified). +# 🐋 Docker + +To run with [Docker](https://docs.docker.com/engine/install/), you can use this as a starting `cli` example: + +`docker run -d --restart=always -e TINSHOP_SOURCES_DIRECTORIES=/games -e TINSHOP_WELCOMEMESSAGE="Welcome to my Tinshop!" -v /local/game/backups:/games -p 3000:3000` + +This will run Tinshop on `http://localhost:3000` and persist across reboots! + +If `docker compose` is your thing, then start with this example: + +```yaml +version: '3.9' +services: + tinshop: + container_name: tinshop + image: helvio/tinshop:latest + restart: always + ports: + - 3000:3000 + environment: + - TINSHOP_SOURCES_DIRECTORIES=/games + - TINSHOP_WELCOMEMESSAGE=Welcome to my Tinshop! + volumes: + - /media/switch:/games +``` +All of the settings in the `config.yaml` file are valid Environment Variables. They must be `UPPERCASE` and prefixed by `TINSHOP_`. Nested properties should be prefixed by `_`. Here are a few examples: + +| ENV_VAR | `config.yaml` entry | Default Value | Example Value | +|-----------------------------|---------------------|--------------------------------|--------------------------------| +| TINSHOP_HOST | host | `0.0.0.0` | `127.0.0.` | +| TINSHOP_PROTOCOL | protocol | `http` | `https` | +| TINSHOP_NAME | name | `TinShop` | `MyShop` | +| TINSHOP_REVERSEPROXY | reverseProxy | `false` | `true` | +| TINSHOP_WELCOMEMESSAGE | welcomeMessage | `Welcome to your own TinShop!` | `Welcome to my shop!` | +| TINSHOP_NOWELCOMEMESSAGE | noWelcomeMessage | `false` | `true` | +| TINSHOP_DEBUG_NFS | debug.nfs | `false` | `true` | +| TINSHOP_DEBUG_NOSECURITY | debug.nosecurity | `false` | `true` | +| TINSHOP_DEBUG_TICKET | debug.ticket | `false` | `true` | +| TINSHOP_NSP_CHECKVERIFIED | nsp.checkVerified | `true` | `false` | +| TINSHOP_SOURCES_DIRECTORIES | sources.directories | `./games` | `/games` | +| TINSHOP_SOURCES_NSF | sources.nfs | `null` | `192.168.1.100:/path/to/games` | + # 🎉 Features Here is the list of all main features so far: @@ -150,6 +192,38 @@ reverseProxy: true If you want to have HTTPS, ensure `caddy` handle it (it will with Let's Encrypt) and change `https` in the config and remove `:80` in the `Caddyfile` example. +### Example for traefik + +To work with [`traefik`](https://traefik.io/), you need to put in your Dynamic Configuration something similar to this: + +```yaml +http: + routers: + service: tinshop + rule: Host(`tinshop.example.com`) + entryPoints: websecure # Could be web if not using https + + services: + tinshop: + loadBalancer: + servers: + - url: http://192.168.1.2:3000 +``` + +and your `config.yaml` as follow: + +```yaml +host: tinshop.example.com +protocol: http +port: 3000 +reverseProxy: true +``` + +If you want to have HTTPS, ensure `traefik` handle it (it will with Let's Encrypt) and change `https` in the config and remove `:80` in the `Caddyfile` example. + +For more details on Traefik + Let's Encrypt, [click here](https://doc.traefik.io/traefik/https/acme/). + + ## How can I add a `basic auth` to protect my shop?
From d5ef73b655ed4eeb2c032bfb3d909d7b3946e463 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Fri, 8 Sep 2023 21:28:20 -0400 Subject: [PATCH 13/19] Fix Traefik inside Details --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7c3ee73..487812f 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,6 @@ reverseProxy: true ``` If you want to have HTTPS, ensure `caddy` handle it (it will with Let's Encrypt) and change `https` in the config and remove `:80` in the `Caddyfile` example. -
### Example for traefik From 9a0ccc3f9cec78d3ad2251134419acf3585d8646 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Fri, 8 Sep 2023 23:08:35 -0400 Subject: [PATCH 14/19] Add all settings from ENV except Titles --- README.md | 32 ++++++++++++++++++-------------- config/config.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 487812f..73d7f43 100644 --- a/README.md +++ b/README.md @@ -59,20 +59,24 @@ services: ``` All of the settings in the `config.yaml` file are valid Environment Variables. They must be `UPPERCASE` and prefixed by `TINSHOP_`. Nested properties should be prefixed by `_`. Here are a few examples: -| ENV_VAR | `config.yaml` entry | Default Value | Example Value | -|-----------------------------|---------------------|--------------------------------|--------------------------------| -| TINSHOP_HOST | host | `0.0.0.0` | `127.0.0.` | -| TINSHOP_PROTOCOL | protocol | `http` | `https` | -| TINSHOP_NAME | name | `TinShop` | `MyShop` | -| TINSHOP_REVERSEPROXY | reverseProxy | `false` | `true` | -| TINSHOP_WELCOMEMESSAGE | welcomeMessage | `Welcome to your own TinShop!` | `Welcome to my shop!` | -| TINSHOP_NOWELCOMEMESSAGE | noWelcomeMessage | `false` | `true` | -| TINSHOP_DEBUG_NFS | debug.nfs | `false` | `true` | -| TINSHOP_DEBUG_NOSECURITY | debug.nosecurity | `false` | `true` | -| TINSHOP_DEBUG_TICKET | debug.ticket | `false` | `true` | -| TINSHOP_NSP_CHECKVERIFIED | nsp.checkVerified | `true` | `false` | -| TINSHOP_SOURCES_DIRECTORIES | sources.directories | `./games` | `/games` | -| TINSHOP_SOURCES_NSF | sources.nfs | `null` | `192.168.1.100:/path/to/games` | +| ENV_VAR | `config.yaml` entry | Default Value | Example Value | +|------------------------------|---------------------|--------------------------------|-----------------------------------| +| TINSHOP_HOST | host | `0.0.0.0` | `127.0.0.` | +| TINSHOP_PROTOCOL | protocol | `http` | `https` | +| TINSHOP_NAME | name | `TinShop` | `MyShop` | +| TINSHOP_REVERSEPROXY | reverseProxy | `false` | `true` | +| TINSHOP_WELCOMEMESSAGE | welcomeMessage | `Welcome to your own TinShop!` | `Welcome to my shop!` | +| TINSHOP_NOWELCOMEMESSAGE | noWelcomeMessage | `false` | `true` | +| TINSHOP_DEBUG_NFS | debug.nfs | `false` | `true` | +| TINSHOP_DEBUG_NOSECURITY | debug.nosecurity | `false` | `true` | +| TINSHOP_DEBUG_TICKET | debug.ticket | `false` | `true` | +| TINSHOP_NSP_CHECKVERIFIED | nsp.checkVerified | `true` | `false` | +| TINSHOP_SOURCES_DIRECTORIES | sources.directories | `./games` | `/games /path/two /path/three` | +| TINSHOP_SOURCES_NSF | sources.nfs | `null` | `192.168.1.100:/path/to/games` | +| TINSHOP_SECURITY_BANNEDTHEME | sources.bannedTheme | `null` | `THEME1 THEME2 THEME3` | +| TINSHOP_SECURITY_WHITELIST | sources.whitelist | `null` | `NSWID1 NSWID2 NSWID3` | +| TINSHOP_SECURITY_BLACKLIST | sources.blacklist | `null` | `NSWID4 NSWID5 NSWID6` | +| TINSHOP_SECURITY_FORWARDAUTH | sources.forwardAuth | `null` | `https://auth.tinshop.com/switch` | # 🎉 Features diff --git a/config/config.go b/config/config.go index 51fabe2..0dabd50 100644 --- a/config/config.go +++ b/config/config.go @@ -64,16 +64,38 @@ func New() repository.Config { // LoadConfig handles viper under the hood func (cfg *File) LoadConfig() { - viper.SetConfigName("config") // name of config file (without extension) - viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name - viper.AddConfigPath(".") // optionally look for config in the working directory - viper.SetDefault("sources.directories", "./games") + viper.SetConfigName("config") // name of config file (without extension) + viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name + viper.AddConfigPath(".") // optionally look for config in the working directory + viper.SetTypeByDefaultValue(true) // Allows []string to be parsed from Env Vars + + viper.SetDefault("host", "tinshop.example.com") + viper.SetDefault("protocol", "http") + viper.SetDefault("name", "TinShop") + viper.SetDefault("reverseProxy", false) viper.SetDefault("welcomeMessage", "Welcome to your own TinShop!") viper.SetDefault("noWelcomeMessage", false) + + viper.SetDefault("debug.nfs", false) + viper.SetDefault("debug.noSecurity", false) + viper.SetDefault("debug.ticket", false) + + viper.SetDefault("nsp.checkVerified", true) + + viper.SetDefault("sources.directories", []string{"./games"}) + viper.SetDefault("sources.nfs", []string{}) + + viper.SetDefault("security.bannedTheme", []string{}) + viper.SetDefault("security.whitelist", []string{}) + viper.SetDefault("security.blacklist", []string{}) + viper.SetDefault("security.forwardAuth", "") + viper.SetEnvPrefix("TINSHOP") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() + viper.SafeWriteConfig() + if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // Config file not found; ignore error if desired From ba98dd769cbcb54331a532208175ab9dd9882398 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Fri, 8 Sep 2023 23:12:20 -0400 Subject: [PATCH 15/19] remove SafeWriteConfig --- config/config.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/config.go b/config/config.go index 0dabd50..40b1c39 100644 --- a/config/config.go +++ b/config/config.go @@ -94,8 +94,6 @@ func (cfg *File) LoadConfig() { viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() - viper.SafeWriteConfig() - if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // Config file not found; ignore error if desired From cd8d6c4333f44bc042ed7018664d39b84075c37d Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Fri, 8 Sep 2023 23:18:23 -0400 Subject: [PATCH 16/19] Updated compose example --- docker-compose.example.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docker-compose.example.yml b/docker-compose.example.yml index d092e55..f289953 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -5,10 +5,23 @@ services: build: . container_name: app restart: unless-stopped + environment: + - TINSHOP_HOST=tinshop.example.com + - TINSHOP_PROTOCOL=https + - TINSHOP_NAME=TinShop + - TINSHOP_REVERSEPROXY=true + - TINSHOP_WELCOMEMESSAGE=Welcome to TinShop! + - TINSHOP_NOWELCOMEMESSAGE=false + - TINSHOP_DEBUG_NFS=false + - TINSHOP_DEBUG_NOSECURITY=false + - TINSHOP_DEBUG_TICKET=false + - TINSHOP_NSP_CHECKVERIFIED=true + - TINSHOP_SOURCES_DIRECTORIES=/games + - TINSHOP_SECURITY_WHITELIST=0000000000000000000000000000000000000000000000000000000000000000 1111111111111111111111111111111111111111111111111111111111111111 ports: - 3000:3000 volumes: - - ./config.example.yaml:/config.yaml + - /media/switch:/games nginx: From 99e651554bb4dbc275f093df50b223de4c7bde38 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Fri, 8 Sep 2023 23:33:49 -0400 Subject: [PATCH 17/19] Config File wouldn't make sense on Docker. Renamed --- config/config.go | 70 +++++++++++++++++++++---------------------- config/config_test.go | 52 ++++++++++++++++---------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/config/config.go b/config/config.go index 40b1c39..9a406d5 100644 --- a/config/config.go +++ b/config/config.go @@ -36,8 +36,8 @@ type nsp struct { CheckVerified bool `mapstructure:"checkVerified"` } -// File holds all config information -type File struct { +// Configuration holds all config information +type Configuration struct { rootShop string ShopHost string `mapstructure:"host"` ShopProtocol string `mapstructure:"protocol"` @@ -59,11 +59,11 @@ type File struct { // New returns a new configuration func New() repository.Config { - return &File{} + return &Configuration{} } // LoadConfig handles viper under the hood -func (cfg *File) LoadConfig() { +func (cfg *Configuration) LoadConfig() { viper.SetConfigName("config") // name of config file (without extension) viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name viper.AddConfigPath(".") // optionally look for config in the working directory @@ -113,7 +113,7 @@ func (cfg *File) LoadConfig() { cfg.configChange() } -func (cfg *File) configChange() { +func (cfg *Configuration) configChange() { // Call all before hooks for _, hook := range cfg.beforeAllHooks { hook(cfg) @@ -145,8 +145,8 @@ func (cfg *File) configChange() { } } -func loadAndCompute() *File { - var loadedConfig = &File{} +func loadAndCompute() *Configuration { + var loadedConfig = &Configuration{} err := viper.Unmarshal(&loadedConfig) if err != nil { @@ -204,117 +204,117 @@ func ComputeDefaultValues(config repository.Config) repository.Config { } // AddHook Add hook function on change config -func (cfg *File) AddHook(f func(repository.Config)) { +func (cfg *Configuration) AddHook(f func(repository.Config)) { cfg.allHooks = append(cfg.allHooks, f) } // AddBeforeHook Add hook function before on change config -func (cfg *File) AddBeforeHook(f func(repository.Config)) { +func (cfg *Configuration) AddBeforeHook(f func(repository.Config)) { cfg.beforeAllHooks = append(cfg.beforeAllHooks, f) } // SetRootShop allow to change the root url of the shop -func (cfg *File) SetRootShop(root string) { +func (cfg *Configuration) SetRootShop(root string) { cfg.rootShop = root } // RootShop returns the RootShop url -func (cfg *File) RootShop() string { +func (cfg *Configuration) RootShop() string { return cfg.rootShop } // ReverseProxy returns the ReverseProxy setting -func (cfg *File) ReverseProxy() bool { +func (cfg *Configuration) ReverseProxy() bool { return cfg.Proxy } // WelcomeMessage returns the WelcomeMessage -func (cfg *File) WelcomeMessage() string { +func (cfg *Configuration) WelcomeMessage() string { return cfg.ShopWelcomeMessage } // NoWelcomeMessage returns the NoWelcomeMessage -func (cfg *File) NoWelcomeMessage() bool { +func (cfg *Configuration) NoWelcomeMessage() bool { return cfg.ShopNoWelcomeMessage } // Protocol returns the protocol scheme (http or https) -func (cfg *File) Protocol() string { +func (cfg *Configuration) Protocol() string { return cfg.ShopProtocol } // Host returns the host of the shop -func (cfg *File) Host() string { +func (cfg *Configuration) Host() string { return cfg.ShopHost } // Port returns the port number for outside access -func (cfg *File) Port() int { +func (cfg *Configuration) Port() int { return cfg.ShopPort } // DebugTicket tells if we should display additional log for ticket verification -func (cfg *File) DebugTicket() bool { +func (cfg *Configuration) DebugTicket() bool { return cfg.Debug.Ticket } // DebugNfs tells if we should display additional log for nfs -func (cfg *File) DebugNfs() bool { +func (cfg *Configuration) DebugNfs() bool { return cfg.Debug.Nfs } // DebugNoSecurity returns if we should disable security or not -func (cfg *File) DebugNoSecurity() bool { +func (cfg *Configuration) DebugNoSecurity() bool { return cfg.Debug.NoSecurity } // Directories returns the list of directories sources -func (cfg *File) Directories() []string { +func (cfg *Configuration) Directories() []string { return cfg.AllSources.Directories } // CustomDB returns the list of custom title db -func (cfg *File) CustomDB() map[string]repository.TitleDBEntry { +func (cfg *Configuration) CustomDB() map[string]repository.TitleDBEntry { return cfg.CustomTitleDB } // NfsShares returns the list of nfs sources -func (cfg *File) NfsShares() []string { +func (cfg *Configuration) NfsShares() []string { return cfg.AllSources.Nfs } // Sources returns all available sources -func (cfg *File) Sources() repository.ConfigSources { +func (cfg *Configuration) Sources() repository.ConfigSources { return cfg.AllSources } // ShopTemplateData returns the data needed to render template -func (cfg *File) ShopTemplateData() repository.ShopTemplate { +func (cfg *Configuration) ShopTemplateData() repository.ShopTemplate { return cfg.shopTemplateData } // SetShopTemplateData sets the data for template -func (cfg *File) SetShopTemplateData(data repository.ShopTemplate) { +func (cfg *Configuration) SetShopTemplateData(data repository.ShopTemplate) { cfg.shopTemplateData = data } // ShopTitle returns the name of the shop -func (cfg *File) ShopTitle() string { +func (cfg *Configuration) ShopTitle() string { return cfg.Name } // VerifyNSP tells if we need to verify NSP -func (cfg *File) VerifyNSP() bool { +func (cfg *Configuration) VerifyNSP() bool { return cfg.NSP.CheckVerified } // ForwardAuthURL returns the url of the forward auth -func (cfg *File) ForwardAuthURL() string { +func (cfg *Configuration) ForwardAuthURL() string { return cfg.Security.ForwardAuth } // IsBlacklisted tells if the uid is blacklisted or not -func (cfg *File) IsBlacklisted(uid string) bool { +func (cfg *Configuration) IsBlacklisted(uid string) bool { if len(cfg.Security.Whitelist) != 0 { return !cfg.isInWhiteList(uid) } @@ -322,20 +322,20 @@ func (cfg *File) IsBlacklisted(uid string) bool { } // IsWhitelisted tells if the uid is whitelisted or not -func (cfg *File) IsWhitelisted(uid string) bool { +func (cfg *Configuration) IsWhitelisted(uid string) bool { if len(cfg.Security.Whitelist) == 0 { return !cfg.isInBlackList(uid) } return cfg.isInWhiteList(uid) } -func (cfg *File) isInBlackList(uid string) bool { +func (cfg *Configuration) isInBlackList(uid string) bool { idxBlackList := utils.Search(len(cfg.Security.Blacklist), func(index int) bool { return cfg.Security.Blacklist[index] == uid }) return idxBlackList != -1 } -func (cfg *File) isInWhiteList(uid string) bool { +func (cfg *Configuration) isInWhiteList(uid string) bool { idxWhiteList := utils.Search(len(cfg.Security.Whitelist), func(index int) bool { return cfg.Security.Whitelist[index] == uid }) @@ -343,7 +343,7 @@ func (cfg *File) isInWhiteList(uid string) bool { } // IsBannedTheme tells if the theme is banned or not -func (cfg *File) IsBannedTheme(theme string) bool { +func (cfg *Configuration) IsBannedTheme(theme string) bool { idxBannedTheme := utils.Search(len(cfg.Security.BannedTheme), func(index int) bool { return cfg.Security.BannedTheme[index] == theme }) @@ -351,6 +351,6 @@ func (cfg *File) IsBannedTheme(theme string) bool { } // BannedTheme returns all banned theme -func (cfg *File) BannedTheme() []string { +func (cfg *Configuration) BannedTheme() []string { return cfg.Security.BannedTheme } diff --git a/config/config_test.go b/config/config_test.go index 86a10de..c529e1b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -175,10 +175,10 @@ var _ = Describe("Config", func() { }) }) Context("Security for Blacklist/Whitelist tests", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) Describe("Blacklist tests", func() { //nolint:dupl @@ -251,10 +251,10 @@ var _ = Describe("Config", func() { }) }) Context("Security for theme", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) Describe("IsBannedTheme", func() { @@ -276,10 +276,10 @@ var _ = Describe("Config", func() { }) }) Describe("Protocol", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -291,10 +291,10 @@ var _ = Describe("Config", func() { }) }) Describe("Host", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -306,10 +306,10 @@ var _ = Describe("Config", func() { }) }) Describe("WelcomeMessage", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -325,10 +325,10 @@ var _ = Describe("Config", func() { }) }) Describe("Port", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -340,10 +340,10 @@ var _ = Describe("Config", func() { }) }) Describe("ReverseProxy", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -355,10 +355,10 @@ var _ = Describe("Config", func() { }) }) Describe("ShopTitle", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -370,10 +370,10 @@ var _ = Describe("Config", func() { }) }) Describe("DebugNfs", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -385,10 +385,10 @@ var _ = Describe("Config", func() { }) }) Describe("VerifyNSP", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -400,10 +400,10 @@ var _ = Describe("Config", func() { }) }) Describe("DebugNoSecurity", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -415,10 +415,10 @@ var _ = Describe("Config", func() { }) }) Describe("DebugTicket", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { @@ -430,10 +430,10 @@ var _ = Describe("Config", func() { }) }) Describe("BannedTheme", func() { - var myConfig config.File + var myConfig config.Configuration BeforeEach(func() { - myConfig = config.File{} + myConfig = config.Configuration{} }) It("Test with empty object", func() { From e8a850e52721d29698ab2229d6510b6464af79b4 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Sat, 9 Sep 2023 10:22:35 -0400 Subject: [PATCH 18/19] A few tweaks on defaults and docs before merge --- README.md | 6 +++--- config/config.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 73d7f43..fdf6704 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ All of the settings in the `config.yaml` file are valid Environment Variables. T | ENV_VAR | `config.yaml` entry | Default Value | Example Value | |------------------------------|---------------------|--------------------------------|-----------------------------------| -| TINSHOP_HOST | host | `0.0.0.0` | `127.0.0.` | +| TINSHOP_HOST | host | `` | `tinshop.example.com` | | TINSHOP_PROTOCOL | protocol | `http` | `https` | | TINSHOP_NAME | name | `TinShop` | `MyShop` | | TINSHOP_REVERSEPROXY | reverseProxy | `false` | `true` | @@ -70,7 +70,7 @@ All of the settings in the `config.yaml` file are valid Environment Variables. T | TINSHOP_DEBUG_NFS | debug.nfs | `false` | `true` | | TINSHOP_DEBUG_NOSECURITY | debug.nosecurity | `false` | `true` | | TINSHOP_DEBUG_TICKET | debug.ticket | `false` | `true` | -| TINSHOP_NSP_CHECKVERIFIED | nsp.checkVerified | `true` | `false` | +| TINSHOP_NSP_CHECKVERIFIED | nsp.checkVerified | `false` | `true` | | TINSHOP_SOURCES_DIRECTORIES | sources.directories | `./games` | `/games /path/two /path/three` | | TINSHOP_SOURCES_NSF | sources.nfs | `null` | `192.168.1.100:/path/to/games` | | TINSHOP_SECURITY_BANNEDTHEME | sources.bannedTheme | `null` | `THEME1 THEME2 THEME3` | @@ -222,7 +222,7 @@ port: 3000 reverseProxy: true ``` -If you want to have HTTPS, ensure `traefik` handle it (it will with Let's Encrypt) and change `https` in the config and remove `:80` in the `Caddyfile` example. +If you want to have HTTPS, ensure `traefik` can handle it (it will with Let's Encrypt) and use protocol `https` in the config. For more details on Traefik + Let's Encrypt, [click here](https://doc.traefik.io/traefik/https/acme/). diff --git a/config/config.go b/config/config.go index 9a406d5..ac9c279 100644 --- a/config/config.go +++ b/config/config.go @@ -69,7 +69,7 @@ func (cfg *Configuration) LoadConfig() { viper.AddConfigPath(".") // optionally look for config in the working directory viper.SetTypeByDefaultValue(true) // Allows []string to be parsed from Env Vars - viper.SetDefault("host", "tinshop.example.com") + viper.SetDefault("host", "") viper.SetDefault("protocol", "http") viper.SetDefault("name", "TinShop") viper.SetDefault("reverseProxy", false) @@ -80,7 +80,7 @@ func (cfg *Configuration) LoadConfig() { viper.SetDefault("debug.noSecurity", false) viper.SetDefault("debug.ticket", false) - viper.SetDefault("nsp.checkVerified", true) + viper.SetDefault("nsp.checkVerified", false) viper.SetDefault("sources.directories", []string{"./games"}) viper.SetDefault("sources.nfs", []string{}) From 1264d8cee5b2cf97b9a29e8589dab9b0e975fbd6 Mon Sep 17 00:00:00 2001 From: Helvio Pedreschi Date: Sat, 9 Sep 2023 10:28:44 -0400 Subject: [PATCH 19/19] Moved docker below `Dev or build from source` --- README.md | 70 +++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index fdf6704..30a5a29 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,41 @@ To proper use this software, here is the checklist: Now simply run it and add a shop inside tinfoil with the address setup in `config` (or `http://localIp:3000` if not specified). +# 🎉 Features + +Here is the list of all main features so far: +- [X] Automatically download `titles.US.en.json` if missing at startup +- [X] Basic protection from forged queries (should allow only tinfoil to use the shop) +- [X] Serve from several mounted directories +- [X] Serve from several network directories (Using NFS) +- [X] Display a webpage for forbidden devices +- [X] Auto-refresh configuration on file change +- [X] Add the possibility to whitelist or blacklist a switch +- [X] Add the possibility to ban theme +- [X] You can specify custom titledb to be merged with official one +- [X] Auto-watch for mounted directories +- [X] Add filters path for shop +- [X] Simple ticket check in NSP/NSZ (based on titledb file) +- [X] Collect basic statistics +- [X] An API to query information about your shop +- [X] Handle Basic Auth from Tinfoil through Forward Auth Endpoint + +## 🏳️ Filtering + +When you setup your shop inside `tinfoil` you can now add the following path: +- `multi` : Filter only multiplayer games +- `fr`, `en`, ... : Filter by languages +- `world` : All games without any filter (equivalent without path) + +# 🧱 Dev or build from source + +I suggest to use a tiny executable [gow](https://github.com/mitranim/gow) to help you during the process (hot reload, etc..). +For example I use the following command to develop `gow -c run .`. + +If you want to build `TinShop` from source, please run `go build`. + +And then, simply run `./tinshop`. + # 🐋 Docker To run with [Docker](https://docs.docker.com/engine/install/), you can use this as a starting `cli` example: @@ -78,41 +113,6 @@ All of the settings in the `config.yaml` file are valid Environment Variables. T | TINSHOP_SECURITY_BLACKLIST | sources.blacklist | `null` | `NSWID4 NSWID5 NSWID6` | | TINSHOP_SECURITY_FORWARDAUTH | sources.forwardAuth | `null` | `https://auth.tinshop.com/switch` | -# 🎉 Features - -Here is the list of all main features so far: -- [X] Automatically download `titles.US.en.json` if missing at startup -- [X] Basic protection from forged queries (should allow only tinfoil to use the shop) -- [X] Serve from several mounted directories -- [X] Serve from several network directories (Using NFS) -- [X] Display a webpage for forbidden devices -- [X] Auto-refresh configuration on file change -- [X] Add the possibility to whitelist or blacklist a switch -- [X] Add the possibility to ban theme -- [X] You can specify custom titledb to be merged with official one -- [X] Auto-watch for mounted directories -- [X] Add filters path for shop -- [X] Simple ticket check in NSP/NSZ (based on titledb file) -- [X] Collect basic statistics -- [X] An API to query information about your shop -- [X] Handle Basic Auth from Tinfoil through Forward Auth Endpoint - -## 🏳️ Filtering - -When you setup your shop inside `tinfoil` you can now add the following path: -- `multi` : Filter only multiplayer games -- `fr`, `en`, ... : Filter by languages -- `world` : All games without any filter (equivalent without path) - -# 🧱 Dev or build from source - -I suggest to use a tiny executable [gow](https://github.com/mitranim/gow) to help you during the process (hot reload, etc..). -For example I use the following command to develop `gow -c run .`. - -If you want to build `TinShop` from source, please run `go build`. - -And then, simply run `./tinshop`. - ## 🥍 Want to do cross-build generation? Wanting to generate all possible os binaries (macOS, linux, windows) with all architectures (arm, amd64)?