Compare commits
3 Commits
main
...
refactorin
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a54e21ed3 | |||
| 83b3a23fe4 | |||
| 2c109b945e |
@@ -1,40 +0,0 @@
|
|||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>DownloadHub - {{.Name}}</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
|
||||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<nav class="navbar navbar-light bg-light">
|
|
||||||
<a style="margin-left: 1rem;" class="navbar-brand mb-0 h1" href="/">DownloadHub</a>
|
|
||||||
</nav>
|
|
||||||
<div class="container" style="margin-top: 1rem; margin-bottom: 1rem;">
|
|
||||||
<h2>{{.Name}} ({{.Version}})</h2>
|
|
||||||
<img width="100%" src="{{index .ScreenshotURLs 0}}" />
|
|
||||||
<p>{{.Description}}</p>
|
|
||||||
<hr />
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
Download
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
{{range .DownloadLinks}}
|
|
||||||
<a href="{{.URL}}">
|
|
||||||
<li class="list-group-item">
|
|
||||||
{{.OS}} ({{.Arch}})
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
|
|
||||||
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
85
cmd/cli/commands/add/add.go
Normal file
85
cmd/cli/commands/add/add.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package add
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
customflag "downloadhub/cmd/cli/flag"
|
||||||
|
"downloadhub/pkg/data"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
AddCmd struct {
|
||||||
|
slug string
|
||||||
|
description string
|
||||||
|
version string
|
||||||
|
iconURL string
|
||||||
|
out string
|
||||||
|
screenshotURLs customflag.Array
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*AddCmd) Name() string { return "add" }
|
||||||
|
func (*AddCmd) Synopsis() string { return "add an entry" }
|
||||||
|
func (*AddCmd) Usage() string {
|
||||||
|
return `Usage: ./cli add [OPTIONS] NAME
|
||||||
|
|
||||||
|
Options:
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AddCmd) SetFlags(f *flag.FlagSet) {
|
||||||
|
f.StringVar(&p.slug, "slug", "", "")
|
||||||
|
f.StringVar(&p.description, "description", "", "")
|
||||||
|
f.StringVar(&p.version, "version", "0.0.0", "")
|
||||||
|
f.StringVar(&p.iconURL, "icon", "", "an url or a path to the icon")
|
||||||
|
f.StringVar(&p.out, "out", "./config.json", "path to the configuration file")
|
||||||
|
f.Var(&p.screenshotURLs, "screenshot", "an url or a path to a screenshot file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AddCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
|
if len(p.slug) == 0 {
|
||||||
|
p.slug = uuid.NewString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.screenshotURLs) == 0 {
|
||||||
|
p.screenshotURLs = make(customflag.Array, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.NArg() == 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: name cannot be empty")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.NArg() > 1 {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: this command cannot take more than 1 argument")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := data.Load(p.out)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Softwares = append(d.Softwares, data.Software{
|
||||||
|
Slug: p.slug,
|
||||||
|
Name: f.Arg(0),
|
||||||
|
Description: p.description,
|
||||||
|
Version: p.version,
|
||||||
|
IconURL: p.iconURL,
|
||||||
|
ScreenshotURLs: p.screenshotURLs,
|
||||||
|
DownloadLinks: make([]data.DownloadLink, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := data.Save(d, p.out); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
99
cmd/cli/commands/edit/edit.go
Normal file
99
cmd/cli/commands/edit/edit.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"downloadhub/pkg/data"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
EditCmd struct {
|
||||||
|
slug string
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
version string
|
||||||
|
iconURL string
|
||||||
|
out string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*EditCmd) Name() string { return "edit" }
|
||||||
|
func (*EditCmd) Synopsis() string { return "edit an entry" }
|
||||||
|
func (*EditCmd) Usage() string {
|
||||||
|
return `Usage: ./cli edit [OPTIONS] SLUG
|
||||||
|
|
||||||
|
Options:
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *EditCmd) SetFlags(f *flag.FlagSet) {
|
||||||
|
f.StringVar(&p.slug, "slug", "", "")
|
||||||
|
f.StringVar(&p.name, "name", "", "")
|
||||||
|
f.StringVar(&p.description, "description", "", "")
|
||||||
|
f.StringVar(&p.version, "version", "0.0.0", "")
|
||||||
|
f.StringVar(&p.iconURL, "icon", "", "an url or a path to the icon")
|
||||||
|
f.StringVar(&p.out, "out", "./config.json", "path to the configuration file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *EditCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
|
if f.NArg() == 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: slug cannot be empty")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.NArg() > 1 {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: this command cannot take more than 1 argument")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := data.Load(p.out)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
for i, soft := range d.Softwares {
|
||||||
|
if soft.Slug == f.Arg(0) {
|
||||||
|
if len(p.slug) > 0 {
|
||||||
|
soft.Slug = p.slug
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.name) > 0 {
|
||||||
|
soft.Name = p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.description) > 0 {
|
||||||
|
soft.Description = p.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.version) > 0 {
|
||||||
|
soft.Version = p.version
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.iconURL) > 0 {
|
||||||
|
soft.IconURL = p.iconURL
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Softwares[i] = soft
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: slug '%s' cannot be found\n", f.Arg(0))
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.Save(d, p.out); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
203
cmd/cli/commands/link/link.go
Normal file
203
cmd/cli/commands/link/link.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package link
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"downloadhub/pkg/data"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
LinkCmd struct {
|
||||||
|
os string
|
||||||
|
arch string
|
||||||
|
name string
|
||||||
|
out string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*LinkCmd) Name() string { return "link" }
|
||||||
|
func (*LinkCmd) Synopsis() string { return "add/edit/remove a download link" }
|
||||||
|
func (*LinkCmd) Usage() string {
|
||||||
|
return `Usage: ./cli link ACTION LINK SLUG
|
||||||
|
|
||||||
|
Actions: add, edit, rm
|
||||||
|
|
||||||
|
Options:
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinkCmd) SetFlags(f *flag.FlagSet) {
|
||||||
|
f.StringVar(&p.os, "os", "", "set the operating system")
|
||||||
|
f.StringVar(&p.arch, "architecture", "", "set the instruction set")
|
||||||
|
f.StringVar(&p.name, "name", "", "set the name or label of the link")
|
||||||
|
f.StringVar(&p.out, "out", "./config.json", "path to the configuration file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinkCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
|
if f.NArg() != 3 {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: bad usage")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(f.Arg(0)) {
|
||||||
|
case "add":
|
||||||
|
return p.add(f.Arg(1), f.Arg(2))
|
||||||
|
case "edit":
|
||||||
|
return p.edit(f.Arg(1), f.Arg(2))
|
||||||
|
case "rm":
|
||||||
|
return p.rm(f.Arg(1), f.Arg(2))
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
fmt.Fprintln(os.Stderr, "error: unknown command")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinkCmd) add(link, slug string) subcommands.ExitStatus {
|
||||||
|
d, err := data.Load(p.out)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
for i, soft := range d.Softwares {
|
||||||
|
if soft.Slug == slug {
|
||||||
|
if exists(link, soft) {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: link already exists")
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
soft.DownloadLinks = append(soft.DownloadLinks, data.DownloadLink{
|
||||||
|
OS: p.os,
|
||||||
|
Arch: p.arch,
|
||||||
|
Name: p.name,
|
||||||
|
URL: link,
|
||||||
|
})
|
||||||
|
d.Softwares[i] = soft
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: slug '%s' cannot be found\n", slug)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.Save(d, p.out); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinkCmd) edit(link, slug string) subcommands.ExitStatus {
|
||||||
|
d, err := data.Load(p.out)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundSlug, foundLink bool
|
||||||
|
for i, soft := range d.Softwares {
|
||||||
|
if soft.Slug == slug {
|
||||||
|
for y, l := range soft.DownloadLinks {
|
||||||
|
if l.URL == link {
|
||||||
|
if len(p.os) > 0 {
|
||||||
|
l.OS = p.os
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.os) > 0 {
|
||||||
|
l.Arch = p.arch
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.name) > 0 {
|
||||||
|
l.Name = p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
soft.DownloadLinks[y] = l
|
||||||
|
foundLink = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.Softwares[i] = soft
|
||||||
|
foundSlug = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundSlug {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: slug '%s' cannot be found\n", slug)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundLink {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: link '%s' cannot be found\n", link)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.Save(d, p.out); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinkCmd) rm(link, slug string) subcommands.ExitStatus {
|
||||||
|
d, err := data.Load(p.out)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundSlug, foundLink bool
|
||||||
|
for i, soft := range d.Softwares {
|
||||||
|
if soft.Slug == slug {
|
||||||
|
var index int
|
||||||
|
for y, l := range soft.DownloadLinks {
|
||||||
|
if l.URL == link {
|
||||||
|
index = y
|
||||||
|
foundLink = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundLink {
|
||||||
|
soft.DownloadLinks = append(soft.DownloadLinks[:index], soft.DownloadLinks[index+1:]...)
|
||||||
|
}
|
||||||
|
d.Softwares[i] = soft
|
||||||
|
foundSlug = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundSlug {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: slug '%s' cannot be found\n", slug)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundLink {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: link '%s' cannot be found\n", link)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.Save(d, p.out); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(link string, soft data.Software) bool {
|
||||||
|
for _, l := range soft.DownloadLinks {
|
||||||
|
if l.URL == link {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
39
cmd/cli/commands/version/version.go
Normal file
39
cmd/cli/commands/version/version.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"downloadhub/pkg/constants"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
VersionCmd struct {
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*VersionCmd) Name() string { return "version" }
|
||||||
|
func (*VersionCmd) Synopsis() string { return "show version and system information" }
|
||||||
|
func (*VersionCmd) Usage() string {
|
||||||
|
return `Usage: ./cli version
|
||||||
|
|
||||||
|
Print the version of the software
|
||||||
|
|
||||||
|
Options:
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VersionCmd) SetFlags(f *flag.FlagSet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VersionCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
|
fmt.Println("Client: downloadhub cli")
|
||||||
|
fmt.Println(" Version: " + constants.Version)
|
||||||
|
fmt.Println(" Go version: " + runtime.Version())
|
||||||
|
fmt.Println(" OS/Arch: " + runtime.GOOS + "/" + runtime.GOARCH)
|
||||||
|
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
18
cmd/cli/flag/flag.go
Normal file
18
cmd/cli/flag/flag.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Array []string
|
||||||
|
|
||||||
|
// String is an implementation of the flag.Value interface
|
||||||
|
func (i *Array) String() string {
|
||||||
|
return strings.Join(*i, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is an implementation of the flag.Value interface
|
||||||
|
func (i *Array) Set(value string) error {
|
||||||
|
*i = append(*i, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
29
cmd/cli/main.go
Normal file
29
cmd/cli/main.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"downloadhub/cmd/cli/commands/add"
|
||||||
|
"downloadhub/cmd/cli/commands/edit"
|
||||||
|
"downloadhub/cmd/cli/commands/link"
|
||||||
|
"downloadhub/cmd/cli/commands/version"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
subcommands.Register(subcommands.HelpCommand(), "help")
|
||||||
|
subcommands.Register(subcommands.FlagsCommand(), "help")
|
||||||
|
subcommands.Register(subcommands.CommandsCommand(), "help")
|
||||||
|
subcommands.Register(&version.VersionCmd{}, "help")
|
||||||
|
|
||||||
|
subcommands.Register(&add.AddCmd{}, "management")
|
||||||
|
subcommands.Register(&edit.EditCmd{}, "management")
|
||||||
|
subcommands.Register(&link.LinkCmd{}, "management")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
os.Exit(int(subcommands.Execute(ctx)))
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"downloadhub/data"
|
"downloadhub/pkg/data"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
type (
|
type (
|
||||||
Server struct {
|
Server struct {
|
||||||
r chi.Router
|
r chi.Router
|
||||||
d data.Service
|
d data.Data
|
||||||
port uint16
|
port uint16
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -26,7 +26,7 @@ var index string
|
|||||||
//go:embed templates/description.html
|
//go:embed templates/description.html
|
||||||
var description string
|
var description string
|
||||||
|
|
||||||
func New(port uint16, d data.Service) *Server {
|
func New(port uint16, d data.Data) *Server {
|
||||||
indexTemplate := template.New("index")
|
indexTemplate := template.New("index")
|
||||||
indexTemplate.Parse(index)
|
indexTemplate.Parse(index)
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ func New(port uint16, d data.Service) *Server {
|
|||||||
var soft data.Software
|
var soft data.Software
|
||||||
var found bool
|
var found bool
|
||||||
for _, s := range d.Softwares {
|
for _, s := range d.Softwares {
|
||||||
if s.UUID == softID {
|
if s.Slug == softID {
|
||||||
soft = s
|
soft = s
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
73
cmd/server/api/templates/description.html
Normal file
73
cmd/server/api/templates/description.html
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DownloadHub - {{.Name}}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||||
|
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-light bg-light">
|
||||||
|
<a style="margin-left: 1rem;" class="navbar-brand mb-0 h1" href="/">DownloadHub</a>
|
||||||
|
</nav>
|
||||||
|
<div class="container" style="margin-top: 1rem; margin-bottom: 1rem;">
|
||||||
|
<h2>{{.Name}} v{{.Version}}</h2>
|
||||||
|
<hr/>
|
||||||
|
<div id="carousel" class="carousel slide">
|
||||||
|
<div class="carousel-inner">
|
||||||
|
{{ range $i, $url := .ScreenshotURLs }}
|
||||||
|
{{ if not $i }}
|
||||||
|
<div class="carousel-item active">
|
||||||
|
<img src="{{ $url }}" class="d-block w-100">
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="carousel-item">
|
||||||
|
<img src="{{ $url }}" class="d-block w-100">
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<button class="carousel-control-prev" type="button" data-bs-target="#carousel" data-bs-slide="prev">
|
||||||
|
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||||
|
<span class="visually-hidden">Previous</span>
|
||||||
|
</button>
|
||||||
|
<button class="carousel-control-next" type="button" data-bs-target="#carousel" data-bs-slide="next">
|
||||||
|
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||||
|
<span class="visually-hidden">Next</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p>{{.Description}}</p>
|
||||||
|
{{ if .Description }}
|
||||||
|
<hr />
|
||||||
|
{{ end }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Assets
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{{range .DownloadLinks}}
|
||||||
|
<a href="{{.URL}}">
|
||||||
|
<li class="list-group-item">
|
||||||
|
{{ if .Name }}
|
||||||
|
{{ if .OS }}
|
||||||
|
{{ .Name }} - {{ .OS }}/{{ .Arch }}
|
||||||
|
{{ else }}
|
||||||
|
{{ .Name }}
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
{{ .OS }}/{{ .Arch }}
|
||||||
|
{{ end }}
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -12,19 +12,22 @@
|
|||||||
<nav class="navbar navbar-light bg-light">
|
<nav class="navbar navbar-light bg-light">
|
||||||
<span style="margin-left: 1rem;" class="navbar-brand mb-0 h1">DownloadHub</span>
|
<span style="margin-left: 1rem;" class="navbar-brand mb-0 h1">DownloadHub</span>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container" style="margin-top: 1rem;">
|
<div style="width: 100%; height: 20rem; background-color: #f2f2f2; display: flex; justify-content: center; align-items: center;">
|
||||||
{{range .}}
|
<h1>DownloadHub</h1>
|
||||||
<div class="row">
|
|
||||||
<div class="col-3">
|
|
||||||
<img width="100%" src="{{index .ScreenshotURLs 0}}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="container" style="margin-top: 1rem; display: flex">
|
||||||
<h2><a href="/d/{{.UUID}}">{{.Name}}</a></h2>
|
{{ range . }}
|
||||||
<p>{{.Description}}</p>
|
<div class="card" style="width: 18rem; margin: 2rem">
|
||||||
|
{{ if .ScreenshotURLs }}
|
||||||
|
<img src="{{ index .ScreenshotURLs 0 }}" class="card-img-top">
|
||||||
|
{{ end }}
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ .Name }}</h5>
|
||||||
|
<p class="card-text">{{ .Description }}</p>
|
||||||
|
<a href="/d/{{ .Slug }}" class="btn btn-primary">More info</a>
|
||||||
</div>
|
</div>
|
||||||
<hr style="margin-top: 1rem;" />
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"downloadhub/api"
|
"downloadhub/cmd/server/api"
|
||||||
"downloadhub/data"
|
"downloadhub/pkg/data"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -16,12 +16,15 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
slog.Info("loading configuration...")
|
slog.Info("loading configuration...")
|
||||||
d := data.Load(configFile)
|
d, err := data.Load(configFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
slog.Info("configuration loaded!")
|
slog.Info("configuration loaded!")
|
||||||
|
|
||||||
slog.Info(fmt.Sprintf("starting server on :%d", port))
|
slog.Info(fmt.Sprintf("starting server on :%d", port))
|
||||||
s := api.New(uint16(port), d)
|
s := api.New(uint16(port), d)
|
||||||
err := s.Serve()
|
err = s.Serve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM golang:1.23.1-alpine3.20 as build
|
FROM golang:1.25.0-alpine3.22 as build
|
||||||
|
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
RUN cd /src \
|
RUN cd /src \
|
||||||
&& go build -o downloadhub \
|
&& go build -o downloadhub ./cmd/server \
|
||||||
&& mkdir -p ./fs/var/opt/downloadhub \
|
&& mkdir -p ./fs/var/opt/downloadhub \
|
||||||
&& mkdir -p ./fs/opt/downloadhub \
|
&& mkdir -p ./fs/opt/downloadhub \
|
||||||
&& cp downloadhub ./fs/opt/downloadhub/downloadhub
|
&& cp downloadhub ./fs/opt/downloadhub/downloadhub
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -1,8 +1,9 @@
|
|||||||
module downloadhub
|
module downloadhub
|
||||||
|
|
||||||
go 1.22
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
|
github.com/google/subcommands v1.2.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
)
|
)
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -1,4 +1,6 @@
|
|||||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||||
|
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
|||||||
5
pkg/constants/constants.go
Normal file
5
pkg/constants/constants.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version string = "0.0.1"
|
||||||
|
)
|
||||||
@@ -2,18 +2,18 @@ package data
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Service struct {
|
Data struct {
|
||||||
Softwares []Software
|
Softwares []Software
|
||||||
}
|
}
|
||||||
|
|
||||||
Software struct {
|
Software struct {
|
||||||
UUID string
|
Slug string `json:"slug"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@@ -25,6 +25,7 @@ type (
|
|||||||
DownloadLink struct {
|
DownloadLink struct {
|
||||||
OS string `json:"os"`
|
OS string `json:"os"`
|
||||||
Arch string `json:"arch"`
|
Arch string `json:"arch"`
|
||||||
|
Name string `json:"name"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,10 +34,13 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Load(path string) Service {
|
func Load(path string) (Data, error) {
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY, 0744)
|
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to open data file: " + err.Error())
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return Data{}, nil
|
||||||
|
}
|
||||||
|
return Data{}, fmt.Errorf("failed to open data file: %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -44,18 +48,24 @@ func Load(path string) Service {
|
|||||||
d := json.NewDecoder(f)
|
d := json.NewDecoder(f)
|
||||||
err = d.Decode(&s)
|
err = d.Decode(&s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to parse data file: " + err.Error())
|
return Data{}, fmt.Errorf("failed to parse data file: %w", err)
|
||||||
}
|
|
||||||
s = generateUUID(s)
|
|
||||||
return Service{
|
|
||||||
Softwares: s.Softwares,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Data(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateUUID(d document) document {
|
func Save(doc Data, path string) error {
|
||||||
for i, s := range d.Softwares {
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0740)
|
||||||
s.UUID = uuid.New().String()
|
if err != nil {
|
||||||
d.Softwares[i] = s
|
return fmt.Errorf("failed to open data file: %w", err)
|
||||||
}
|
}
|
||||||
return d
|
defer f.Close()
|
||||||
|
|
||||||
|
e := json.NewEncoder(f)
|
||||||
|
err = e.Encode(document(doc))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user