forked from Plemya-x/ALR
		
	add --upgradable option for list
				
					
				
			This commit is contained in:
		
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							| @@ -76,7 +76,9 @@ test-coverage: | |||||||
| update-deps-cve: | update-deps-cve: | ||||||
| 	bash scripts/update-deps-cve.sh | 	bash scripts/update-deps-cve.sh | ||||||
|  |  | ||||||
| e2e-test: clean build | prepare-for-e2e-test: clean build | ||||||
| 	rm -f ./e2e-tests/alr | 	rm -f ./e2e-tests/alr | ||||||
| 	cp alr e2e-tests | 	cp alr e2e-tests | ||||||
|  |  | ||||||
|  | e2e-test: prepare-for-e2e-test | ||||||
| 	go test -tags=e2e ./... | 	go test -tags=e2e ./... | ||||||
| @@ -11,7 +11,7 @@ | |||||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> |         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||||
|         <text x="33.5" y="14">coverage</text> |         <text x="33.5" y="14">coverage</text> | ||||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">17.2%</text> |         <text x="86" y="15" fill="#010101" fill-opacity=".3">17.1%</text> | ||||||
|         <text x="86" y="14">17.2%</text> |         <text x="86" y="14">17.1%</text> | ||||||
|     </g> |     </g> | ||||||
| </svg> | </svg> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
| @@ -175,6 +175,11 @@ func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testin | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func simpleExec(t *testing.T, r e2e.Runnable, cmd string, args ...string) { | ||||||
|  | 	err := r.Exec(e2e.NewCommand(cmd, args...)) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | } | ||||||
|  |  | ||||||
| func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) { | func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) { | ||||||
| 	exp, _, err, _ := e2eSpawn( | 	exp, _, err, _ := e2eSpawn( | ||||||
| 		r, | 		r, | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								e2e-tests/issue_74_upgradable_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								e2e-tests/issue_74_upgradable_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue74Upgradable(t *testing.T) { | ||||||
|  | 	dockerMultipleRun( | ||||||
|  | 		t, | ||||||
|  | 		"issue-74-upgradable", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r e2e.Runnable) { | ||||||
|  | 			simpleExec(t, r, "sudo", | ||||||
|  | 				"alr", | ||||||
|  | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				REPO_FOR_E2E_TESTS, | ||||||
|  | 			) | ||||||
|  | 			simpleExec(t, r, "sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"bd26236cd7\"/' /etc/alr/alr.toml") | ||||||
|  | 			simpleExec(t, r, "alr", "ref") | ||||||
|  | 			simpleExec(t, r, "sudo", "alr", "in", "bar-pkg") | ||||||
|  | 			simpleExec(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | ||||||
|  | 			simpleExec(t, r, "sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"d9a3541561\"/' /etc/alr/alr.toml") | ||||||
|  | 			simpleExec(t, r, "sudo", "alr", "ref") | ||||||
|  | 			simpleExec(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -327,10 +327,30 @@ msgstr "" | |||||||
| msgid "You need to be root to perform this action" | msgid "You need to be root to perform this action" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:41 | #: list.go:43 | ||||||
| msgid "List ALR repo packages" | msgid "List ALR repo packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: list.go:57 | ||||||
|  | msgid "Format output using a Go template" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: list.go:89 | ||||||
|  | msgid "Error getting packages for upgrade" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: list.go:92 | ||||||
|  | msgid "No packages for upgrade" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: list.go:102 list.go:187 | ||||||
|  | msgid "Error parsing format template" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: list.go:108 list.go:191 | ||||||
|  | msgid "Error executing template" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: main.go:45 | #: main.go:45 | ||||||
| msgid "Print the current ALR version and exit" | msgid "Print the current ALR version and exit" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -495,22 +515,10 @@ msgstr "" | |||||||
| msgid "Search by provides" | msgid "Search by provides" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: search.go:71 |  | ||||||
| msgid "Format output using a Go template" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: search.go:130 | #: search.go:130 | ||||||
| msgid "Error while executing search" | msgid "Error while executing search" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: search.go:138 |  | ||||||
| msgid "Error parsing format template" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: search.go:153 |  | ||||||
| msgid "Error executing template" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: upgrade.go:47 | #: upgrade.go:47 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|   | |||||||
| @@ -5,15 +5,15 @@ | |||||||
| msgid "" | msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: unnamed project\n" | "Project-Id-Version: unnamed project\n" | ||||||
| "PO-Revision-Date: 2025-04-27 18:27+0300\n" | "PO-Revision-Date: 2025-05-13 23:24+0300\n" | ||||||
| "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | ||||||
| "Language-Team: Russian\n" | "Language-Team: Russian\n" | ||||||
| "Language: ru\n" | "Language: ru\n" | ||||||
| "MIME-Version: 1.0\n" | "MIME-Version: 1.0\n" | ||||||
| "Content-Type: text/plain; charset=UTF-8\n" | "Content-Type: text/plain; charset=UTF-8\n" | ||||||
| "Content-Transfer-Encoding: 8bit\n" | "Content-Transfer-Encoding: 8bit\n" | ||||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " | ||||||
| "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||||
| "X-Generator: Gtranslator 48.0\n" | "X-Generator: Gtranslator 48.0\n" | ||||||
|  |  | ||||||
| #: build.go:42 | #: build.go:42 | ||||||
| @@ -335,10 +335,30 @@ msgstr "Вы должны быть членом %s чтобы выполнить | |||||||
| msgid "You need to be root to perform this action" | msgid "You need to be root to perform this action" | ||||||
| msgstr "Вы должны быть root чтобы выполнить это" | msgstr "Вы должны быть root чтобы выполнить это" | ||||||
|  |  | ||||||
| #: list.go:41 | #: list.go:43 | ||||||
| msgid "List ALR repo packages" | msgid "List ALR repo packages" | ||||||
| msgstr "Список пакетов репозитория ALR" | msgstr "Список пакетов репозитория ALR" | ||||||
|  |  | ||||||
|  | #: list.go:57 | ||||||
|  | msgid "Format output using a Go template" | ||||||
|  | msgstr "Формат выходных данных с использованием шаблона Go" | ||||||
|  |  | ||||||
|  | #: list.go:89 | ||||||
|  | msgid "Error getting packages for upgrade" | ||||||
|  | msgstr "Ошибка при получении пакетов для обновления" | ||||||
|  |  | ||||||
|  | #: list.go:92 | ||||||
|  | msgid "No packages for upgrade" | ||||||
|  | msgstr "Нет пакетов к обновлению" | ||||||
|  |  | ||||||
|  | #: list.go:102 list.go:187 | ||||||
|  | msgid "Error parsing format template" | ||||||
|  | msgstr "Ошибка при разборе шаблона" | ||||||
|  |  | ||||||
|  | #: list.go:108 list.go:191 | ||||||
|  | msgid "Error executing template" | ||||||
|  | msgstr "Ошибка при выполнении шаблона" | ||||||
|  |  | ||||||
| #: main.go:45 | #: main.go:45 | ||||||
| msgid "Print the current ALR version and exit" | msgid "Print the current ALR version and exit" | ||||||
| msgstr "Показать текущую версию ALR и выйти" | msgstr "Показать текущую версию ALR и выйти" | ||||||
| @@ -509,22 +529,10 @@ msgstr "Искать по репозиторию" | |||||||
| msgid "Search by provides" | msgid "Search by provides" | ||||||
| msgstr "Иcкать по provides" | msgstr "Иcкать по provides" | ||||||
|  |  | ||||||
| #: search.go:71 |  | ||||||
| msgid "Format output using a Go template" |  | ||||||
| msgstr "Формат выходных данных с использованием шаблона Go" |  | ||||||
|  |  | ||||||
| #: search.go:130 | #: search.go:130 | ||||||
| msgid "Error while executing search" | msgid "Error while executing search" | ||||||
| msgstr "Ошибка при выполнении поиска" | msgstr "Ошибка при выполнении поиска" | ||||||
|  |  | ||||||
| #: search.go:138 |  | ||||||
| msgid "Error parsing format template" |  | ||||||
| msgstr "Ошибка при разборе шаблона" |  | ||||||
|  |  | ||||||
| #: search.go:153 |  | ||||||
| msgid "Error executing template" |  | ||||||
| msgstr "Ошибка при выполнении шаблона" |  | ||||||
|  |  | ||||||
| #: upgrade.go:47 | #: upgrade.go:47 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "Обновить все установленные пакеты" | msgstr "Обновить все установленные пакеты" | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								list.go
									
									
									
									
									
								
							| @@ -22,10 +22,12 @@ package main | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"slices" | ||||||
|  | 	"text/template" | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"golang.org/x/exp/slices" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| @@ -45,6 +47,15 @@ func ListCmd() *cli.Command { | |||||||
| 				Name:    "installed", | 				Name:    "installed", | ||||||
| 				Aliases: []string{"I"}, | 				Aliases: []string{"I"}, | ||||||
| 			}, | 			}, | ||||||
|  | 			&cli.BoolFlag{ | ||||||
|  | 				Name:    "upgradable", | ||||||
|  | 				Aliases: []string{"U"}, | ||||||
|  | 			}, | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "format", | ||||||
|  | 				Aliases: []string{"f"}, | ||||||
|  | 				Usage:   gotext.Get("Format output using a Go template"), | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||||
| @@ -57,8 +68,10 @@ func ListCmd() *cli.Command { | |||||||
| 				New(ctx). | 				New(ctx). | ||||||
| 				WithConfig(). | 				WithConfig(). | ||||||
| 				WithDB(). | 				WithDB(). | ||||||
|  | 				WithManager(). | ||||||
| 				// autoPull only | 				// autoPull only | ||||||
| 				WithRepos(). | 				WithRepos(). | ||||||
|  | 				WithDistroInfo(). | ||||||
| 				Build() | 				Build() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| @@ -67,6 +80,39 @@ func ListCmd() *cli.Command { | |||||||
|  |  | ||||||
| 			cfg := deps.Cfg | 			cfg := deps.Cfg | ||||||
| 			db := deps.DB | 			db := deps.DB | ||||||
|  | 			mgr := deps.Manager | ||||||
|  | 			info := deps.Info | ||||||
|  |  | ||||||
|  | 			if c.Bool("upgradable") { | ||||||
|  | 				updates, err := checkForUpdates(ctx, mgr, db, info) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error getting packages for upgrade"), err) | ||||||
|  | 				} | ||||||
|  | 				if len(updates) == 0 { | ||||||
|  | 					slog.Info(gotext.Get("No packages for upgrade")) | ||||||
|  | 					return nil | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				format := c.String("format") | ||||||
|  | 				if format == "" { | ||||||
|  | 					format = "{{.Package.Repository}}/{{.Package.Name}} {{.FromVersion}} -> {{.ToVersion}}\n" | ||||||
|  | 				} | ||||||
|  | 				tmpl, err := template.New("format").Parse(format) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				for _, updateInfo := range updates { | ||||||
|  | 					err = tmpl.Execute(os.Stdout, updateInfo) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return cliutils.FormatCliExit(gotext.Get("Error executing template"), err) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// TODO: refactor code below | ||||||
|  |  | ||||||
| 			where := "true" | 			where := "true" | ||||||
| 			args := []any(nil) | 			args := []any(nil) | ||||||
| @@ -115,17 +161,35 @@ func ListCmd() *cli.Command { | |||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				version := pkg.Version | 				type packageInfo struct { | ||||||
|  | 					Package *database.Package | ||||||
|  | 					Version string | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				pkgInfo := &packageInfo{} | ||||||
|  | 				pkgInfo.Package = &pkg | ||||||
|  | 				pkgInfo.Version = pkg.Version | ||||||
| 				if c.Bool("installed") { | 				if c.Bool("installed") { | ||||||
| 					instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | 					instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | ||||||
| 					if !ok { | 					if !ok { | ||||||
| 						continue | 						continue | ||||||
| 					} else { | 					} else { | ||||||
| 						version = instVersion | 						pkgInfo.Version = instVersion | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version) | 				format := c.String("format") | ||||||
|  | 				if format == "" { | ||||||
|  | 					format = "{{.Package.Repository}}/{{.Package.Name}} {{.Version}}\n" | ||||||
|  | 				} | ||||||
|  | 				tmpl, err := template.New("format").Parse(format) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err) | ||||||
|  | 				} | ||||||
|  | 				err = tmpl.Execute(os.Stdout, pkg) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error executing template"), err) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								upgrade.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								upgrade.go
									
									
									
									
									
								
							| @@ -116,7 +116,7 @@ func UpgradeCmd() *cli.Command { | |||||||
| 						Info:       deps.Info, | 						Info:       deps.Info, | ||||||
| 						PkgFormat_: build.GetPkgFormat(deps.Manager), | 						PkgFormat_: build.GetPkgFormat(deps.Manager), | ||||||
| 					}, | 					}, | ||||||
| 					updates, | 					mapUptatesInfoToPackages(updates), | ||||||
| 				) | 				) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) | 					return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) | ||||||
| @@ -130,12 +130,27 @@ func UpgradeCmd() *cli.Command { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func mapUptatesInfoToPackages(updates []UpdateInfo) []database.Package { | ||||||
|  | 	var pkgs []database.Package | ||||||
|  | 	for _, info := range updates { | ||||||
|  | 		pkgs = append(pkgs, *info.Package) | ||||||
|  | 	} | ||||||
|  | 	return pkgs | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type UpdateInfo struct { | ||||||
|  | 	Package *database.Package | ||||||
|  |  | ||||||
|  | 	FromVersion string | ||||||
|  | 	ToVersion   string | ||||||
|  | } | ||||||
|  |  | ||||||
| func checkForUpdates( | func checkForUpdates( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	mgr manager.Manager, | 	mgr manager.Manager, | ||||||
| 	db *database.Database, | 	db *database.Database, | ||||||
| 	info *distro.OSRelease, | 	info *distro.OSRelease, | ||||||
| ) ([]database.Package, error) { | ) ([]UpdateInfo, error) { | ||||||
| 	installed, err := mgr.ListInstalled(nil) | 	installed, err := mgr.ListInstalled(nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -145,7 +160,7 @@ func checkForUpdates( | |||||||
|  |  | ||||||
| 	s := search.New(db) | 	s := search.New(db) | ||||||
|  |  | ||||||
| 	var out []database.Package | 	var out []UpdateInfo | ||||||
| 	for _, pkgName := range pkgNames { | 	for _, pkgName := range pkgNames { | ||||||
| 		matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName) | 		matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName) | ||||||
| 		if matches != nil { | 		if matches != nil { | ||||||
| @@ -179,10 +194,13 @@ func checkForUpdates( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c := vercmp.Compare(repoVer, installed[pkgName]) | 			c := vercmp.Compare(repoVer, installed[pkgName]) | ||||||
| 			if c == 0 || c == -1 { |  | ||||||
| 				continue | 			if c == 1 { | ||||||
| 			} else if c == 1 { | 				out = append(out, UpdateInfo{ | ||||||
| 				out = append(out, pkg) | 					Package:     &pkg, | ||||||
|  | 					FromVersion: installed[pkgName], | ||||||
|  | 					ToVersion:   repoVer, | ||||||
|  | 				}) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user