Compare commits

..

1 Commits

Author SHA1 Message Date
Amolith 8476d3ae6c
Pass messages from backend to frontend 2024-02-23 19:30:23 -05:00
12 changed files with 161 additions and 213 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

View File

@ -1,3 +0,0 @@
SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
SPDX-License-Identifier: CC0-1.0

2
.gitignore vendored
View File

@ -5,7 +5,7 @@
/willow /willow
/*.csv /*.csv
/data/ /data/
/*.sqlite* /*.sqlite
/config.toml /config.toml
/.idea/ /.idea/

140
README.md
View File

@ -12,134 +12,74 @@ SPDX-License-Identifier: CC0-1.0
_Forge-agnostic software release tracker_ _Forge-agnostic software release tracker_
![screenshot of willow's current web UI](.files/2024-02-24.png) ![screenshot of willow's current web UI](.files/2023-10-29.png)
_This UI is Amolith's attempt at a balance between simple, pleasant, and _This UI is a minimal proof-of-concept, it's going to change drastically in the
functional. Amolith is not a UX professional and would **very** much welcome near future._
input from someone more knowledgeable!_
## What is it? ## What is it?
_If you'd rather watch a short video, Amolith gave a 5-minute [lightning talk on _If you'd rather watch a video, I gave a [lightning talk on Willow] at the 2023
Willow] at the 2023 Ubuntu Summit._ Ubuntu Summit._
[lightning talk on Willow]: https://youtu.be/XIGxKyekvBQ?t=29900 [lightning talk on Willow]: https://youtu.be/XIGxKyekvBQ?t=29900
Willow helps developers, sysadmins, and homelabbers keep up with software Willow tracks software releases across arbitrary forge platforms by trying to
releases across arbitrary forge platforms, including full-featured forges like support one of the very few things they all have in common: the VCS. At the
GitHub, GitLab, or [Forgejo] as well as more minimal options like [cgit] or moment, git is the _only_ supported VCS, but I would be interested in adding
[stagit]. Pijul, Fossil, Mercurial, etc. You can also track releases using RSS feeds.
[Forgejo]: https://forgejo.org/ Willow exists because decentralisation can be annoying. One piece of software
[cgit]: https://git.zx2c4.com/cgit/ can be found on GitHub, another piece on GitLab, one on Bitbucket, a fourth on
[stagit]: https://codemadness.org/stagit.html SourceHut, and a fifth on the developer's self-hosted Forgejo instance. Forgejo
and GitHub have RSS feeds that only notify you of releases. GitLab doesn't
support RSS feeds for anything, just an API you can poke. Some software updates
might be on the developers' personal blog. Sometimes there are CVEs for specific
software and they get published somewhere completely different before they're
fixed in a release.
It exists because decentralisation, as wonderful as it is, does have some pain I want to bring all that scattered information under one roof so a developer or
points. One piece of software is on GitHub, another piece is on GitLab, one on sysadmin can pop open willow's web UI and immediately see what needs updating
Bitbucket, a fourth on [SourceHut], a fifth on the developer's self-hosted where. I've recorded some of my other ideas and plans in [my wiki].
Forgejo instance.
[SourceHut]: https://sourcehut.org/ [my wiki]: https://wiki.secluded.site/hypha/willow
The capabilities of each platform can also differ, further complicating the
space. For example, Forgejo and GitHub have APIs and RSS release feeds,
SourceHut has an API and RSS feeds that notify you of _all_ activity in the
repo, GitLab only has an API, and there's no standard for discovering the
capabilities of arbitrary git frontends like [legit].
[legit]: https://github.com/icyphox/legit
And _then_ you have different pieces of information in different places; some
developers might publish release announcements on their personal blog and some
projects might release security advisories on an external platform prior to
publishing a release.
All this important info is scattered all over the internet. Willow brings some
order to that chaos by supporting both RSS and one of the _very_ few things all
the forges and frontends have in common: their **V**ersion **C**ontrol
**S**ystem. At the moment, [Git] is the _only_ supported VCS, but we're
definitely interested in adding support for [Pijul], [Fossil], [Mercurial], and
potentially others.
[Git]: https://git-scm.com/
[Pijul]: https://pijul.org/
[Fossil]: https://www.fossil-scm.org/
[Mercurial]: https://www.mercurial-scm.org/
Amolith (the creator) has recorded some of his other ideas, thoughts, and plans
in [his wiki].
[his wiki]: https://wiki.secluded.site/hypha/willow
## Installation and use ## Installation and use
**Disclaimers:** _**Note:** prebuilt binaries will be available after I release [v0.0.1]_
1. Prebuilt binaries will be available with the [v0.0.1] release, greatly
simplifying installation.
2. We consider the project _alpha-quality_. There will be bugs.
3. Amolith has tried to make the web UI accessible, but is unsure of its current
usability.
4. The app is not localised yet and English is the only available language.
5. Help with any/all of the above is most welcome!
[v0.0.1]: https://todo.sr.ht/~amolith/willow?search=status%3Aopen%20label%3A%22v0.0.1%22 [v0.0.1]: https://todo.sr.ht/~amolith/willow?search=status%3Aopen%20label%3A%22v0.0.1%22
[communication platforms]: #contributing
### Installation
This assumes Willow will run on an always-on server, like a VPS.
* Clone the repo with `git clone https://git.sr.ht/~amolith/willow`
* Enter the repo's folder with `cd willow`
* Build the binary with `CGO_ENABLED=0 go build -ldflags="-s -w" -o willow
./cmd`
* Transfer the binary to the server however you like
* Execute the binary with `./willow`
* Edit the config with `vim config.toml`
* Daemonise Willow using systemd or OpenRC or whatever you prefer
* Reverse-proxy the web UI (defaults to `localhost:1313`) with Caddy or NGINX or
whatever you prefer
### Use
* Clone the repo
* Build the binary with `CGO_ENABLED=0 go build -ldflags="-s -w" -o willow ./cmd`
* Upload it to a remote server
* Execute the binary
* Edit the `config.toml`
* Create a user with `./willow -a <username>` * Create a user with `./willow -a <username>`
* Open the web UI (defaults to `localhost:1313`, but [installation] had you put * Execute the binary again
a proxy in front) * Reverse proxy `http://localhost:1313`
* Open the web UI
* Click `Track new project` * Click `Track new project`
* Fill out the form and press `Next` * Fill out the form
* Indicate which version you're currently on and press `Track releases` * Indicate which version you're currently on
* You're now tracking that project's releases! * That's it!
[installation]: #installation Note that I still consider the project to be in _alpha_ state. There will be
bugs; please help fix them!
If you no longer use that project, click the `Delete?` link to remove it, and,
if applicable, Willow's copy of its repo.
If you're no longer running the version Willow says you've selected, click the
`Modify?` link to select a different version.
If there are projects where your selected version does _not_ match what Willow
thinks is latest, they'll show up at the top under the **Outdated projects**
heading and have a link at the bottom of the card to `View release notes`.
Clicking that link populates the right column with those release notes.
If there are projects where your selected version _does_ match what Willow
thinks is latest, they'll show up at the bottom under the **Up-to-date
projects** heading.
## Contributing ## Contributing
Contributions are very much welcome! Please take a look at the [ticket Contributions are very much welcome! Please take a look at the [ticket
tracker][todo] and see if there's anything you're interested in working on. If tracker][todo] and see if there's anything you're interested in working on. If
there's specific functionality you'd like to see implemented and it's not there's specific functionality you'd like to see implemented and it's not
mentioned in the ticket tracker, please describe it through one of the platforms mentioned in the ticket tracker, please send a description to the [mailing
below so we can discuss its inclusion. If we don't feel like it fits with list][email] so we can discuss its inclusion. If I don't feel like it fits with
Willow's goals, you're encouraged to fork the project and make whatever changes Willow's goals, you're encouraged to fork the project and make whatever changes
you like! you like!
Questions, comments, and patches can always go to the [mailing list][email], but Questions, comments, and patches can always be sent to the [mailing
there's also an [IRC channel][irc] and an [XMPP MUC][xmpp] for real-time list][email], but I'm also in the [IRC channel][irc]/[XMPP room][xmpp] pretty
interactions. much 24/7. I might not see messages right away, so please stick around.
- Email: [~amolith/willow@lists.sr.ht][email] - Email: [~amolith/willow@lists.sr.ht][email]
- IRC: [irc.libera.chat/#willow][irc] - IRC: [irc.libera.chat/#willow][irc]

View File

@ -22,9 +22,9 @@ func DeleteProject(db *sql.DB, mu *sync.Mutex, id string) error {
} }
// GetProject returns a project from the database // GetProject returns a project from the database
func GetProject(db *sql.DB, id string) (map[string]string, error) { func GetProject(db *sql.DB, url string) (map[string]string, error) {
var name, forge, url, version string var id, name, forge, version string
err := db.QueryRow("SELECT name, forge, url, version FROM projects WHERE id = ?", id).Scan(&name, &forge, &url, &version) err := db.QueryRow("SELECT id, name, forge, version FROM projects WHERE url = ?", url).Scan(&id, &name, &forge, &version)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,7 +9,7 @@ import (
"sync" "sync"
) )
// UpsertRelease adds or updates a release for a project with a given ID in the // UpsertRelease adds or updates a release for a project with a given URL in the
// database // database
func UpsertRelease(db *sql.DB, mu *sync.Mutex, id, projectID, url, tag, content, date string) error { func UpsertRelease(db *sql.DB, mu *sync.Mutex, id, projectID, url, tag, content, date string) error {
mu.Lock() mu.Lock()
@ -48,12 +48,11 @@ func GetReleases(db *sql.DB, projectID string) ([]map[string]string, error) {
return nil, err return nil, err
} }
releases = append(releases, map[string]string{ releases = append(releases, map[string]string{
"id": id, "id": id,
"project_id": projectID, "url": url,
"url": url, "tag": tag,
"tag": tag, "content": content,
"content": content, "date": date,
"date": date,
}) })
} }
return releases, nil return releases, nil

View File

@ -7,6 +7,7 @@ package git
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@ -148,23 +149,42 @@ func RemoveRepo(url string) (err error) {
return err return err
} }
path = path[:strings.LastIndex(path, "/")] // TODO: Check whether the two parent directories are empty and remove them if
dirs := strings.Split(path, "/") // so
for i := 0; i < 2; i++ {
for range dirs { path = strings.TrimSuffix(path, "/")
if path == "data" { if path == "data" {
break break
} }
err = os.Remove(path) empty, err := dirEmpty(path)
if err != nil { if err != nil {
// This folder likely has data, so might as well save some time by return err
// not checking the parents we can't delete anyway. }
break if empty {
err = os.Remove(path)
if err != nil {
return err
}
} }
path = path[:strings.LastIndex(path, "/")] path = path[:strings.LastIndex(path, "/")]
} }
return nil return err
}
// dirEmpty checks if a directory is empty.
func dirEmpty(name string) (empty bool, err error) {
f, err := os.Open(name)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1)
if err == io.EOF {
return true, nil
}
return false, err
} }
// stringifyRepo accepts a repository URI string and the corresponding local // stringifyRepo accepts a repository URI string and the corresponding local

View File

@ -7,7 +7,6 @@ package project
import ( import (
"crypto/sha256" "crypto/sha256"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"log" "log"
"sort" "sort"
@ -42,33 +41,24 @@ type Release struct {
// GetReleases returns a list of all releases for a project from the database // GetReleases returns a list of all releases for a project from the database
func GetReleases(dbConn *sql.DB, mu *sync.Mutex, proj Project) (Project, error) { func GetReleases(dbConn *sql.DB, mu *sync.Mutex, proj Project) (Project, error) {
proj.ID = GenProjectID(proj.URL, proj.Name, proj.Forge)
ret, err := db.GetReleases(dbConn, proj.ID) ret, err := db.GetReleases(dbConn, proj.ID)
if err != nil { if err != nil {
return proj, err return proj, err
} }
// TODO: figure out a clean way to remove this so the home page loads
// immediately.
if len(ret) == 0 { if len(ret) == 0 {
proj, err = fetchReleases(dbConn, mu, proj)
if err != nil {
return proj, err
}
err = upsertReleases(dbConn, mu, proj.ID, proj.Releases)
if err != nil {
return proj, err
}
return proj, nil return proj, nil
} }
for _, row := range ret { for _, row := range ret {
proj.Releases = append(proj.Releases, Release{ proj.Releases = append(proj.Releases, Release{
ID: row["id"], ID: row["id"],
ProjectID: proj.ID, Tag: row["tag"],
Tag: row["tag"], Content: row["content"],
Content: row["content"], URL: row["release_url"],
URL: row["release_url"], Date: time.Time{},
Date: time.Time{},
}) })
} }
proj.Releases = SortReleases(proj.Releases) proj.Releases = SortReleases(proj.Releases)
@ -93,7 +83,7 @@ func fetchReleases(dbConn *sql.DB, mu *sync.Mutex, p Project) (Project, error) {
URL: release.URL, URL: release.URL,
Date: release.Date, Date: release.Date,
}) })
err = upsertReleases(dbConn, mu, p.ID, p.Releases) err = upsertRelease(dbConn, mu, p.URL, p.Releases)
if err != nil { if err != nil {
log.Printf("Error upserting release: %v", err) log.Printf("Error upserting release: %v", err)
return p, err return p, err
@ -112,7 +102,7 @@ func fetchReleases(dbConn *sql.DB, mu *sync.Mutex, p Project) (Project, error) {
URL: release.URL, URL: release.URL,
Date: release.Date, Date: release.Date,
}) })
err = upsertReleases(dbConn, mu, p.ID, p.Releases) err = upsertRelease(dbConn, mu, p.URL, p.Releases)
if err != nil { if err != nil {
log.Printf("Error upserting release: %v", err) log.Printf("Error upserting release: %v", err)
return p, err return p, err
@ -130,18 +120,12 @@ func SortReleases(releases []Release) []Release {
return releases return releases
} }
func SortProjects(projects []Project) []Project { // upsertRelease updates or inserts a release in the database
sort.Slice(projects, func(i, j int) bool { func upsertRelease(dbConn *sql.DB, mu *sync.Mutex, url string, releases []Release) error {
return strings.ToLower(projects[i].Name) < strings.ToLower(projects[j].Name)
})
return projects
}
// upsertReleases updates or inserts a release in the database
func upsertReleases(dbConn *sql.DB, mu *sync.Mutex, projID string, releases []Release) error {
for _, release := range releases { for _, release := range releases {
date := release.Date.Format("2006-01-02 15:04:05") date := release.Date.Format("2006-01-02 15:04:05")
err := db.UpsertRelease(dbConn, mu, release.ID, projID, release.URL, release.Tag, release.Content, date) id := GenReleaseID(url, release.URL, release.Tag)
err := db.UpsertRelease(dbConn, mu, id, url, release.URL, release.Tag, release.Content, date)
if err != nil { if err != nil {
log.Printf("Error upserting release: %v", err) log.Printf("Error upserting release: %v", err)
return err return err
@ -172,19 +156,12 @@ func Track(dbConn *sql.DB, mu *sync.Mutex, manualRefresh *chan struct{}, name, u
} }
func Untrack(dbConn *sql.DB, mu *sync.Mutex, id string) { func Untrack(dbConn *sql.DB, mu *sync.Mutex, id string) {
proj, err := db.GetProject(dbConn, id) err := db.DeleteProject(dbConn, mu, id)
if err != nil {
fmt.Println("Error getting project:", err)
}
err = db.DeleteProject(dbConn, mu, proj["id"])
if err != nil { if err != nil {
fmt.Println("Error deleting project:", err) fmt.Println("Error deleting project:", err)
} }
// TODO: before removing, check whether other tracked projects use the same err = git.RemoveRepo(id)
// repo
err = git.RemoveRepo(proj["url"])
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@ -210,7 +187,7 @@ func RefreshLoop(dbConn *sql.DB, mu *sync.Mutex, interval int, manualRefresh, re
return strings.ToLower(projectsList[i].Name) < strings.ToLower(projectsList[j].Name) return strings.ToLower(projectsList[i].Name) < strings.ToLower(projectsList[j].Name)
}) })
for i := range projectsList { for i := range projectsList {
err = upsertReleases(dbConn, mu, projectsList[i].ID, projectsList[i].Releases) err = upsertRelease(dbConn, mu, projectsList[i].URL, projectsList[i].Releases)
if err != nil { if err != nil {
fmt.Println("Error upserting release:", err) fmt.Println("Error upserting release:", err)
continue continue
@ -237,26 +214,24 @@ func RefreshLoop(dbConn *sql.DB, mu *sync.Mutex, interval int, manualRefresh, re
} }
// GetProject returns a project from the database // GetProject returns a project from the database
func GetProject(dbConn *sql.DB, proj Project) (Project, error) { func GetProject(dbConn *sql.DB, id string) (Project, error) {
projectDB, err := db.GetProject(dbConn, proj.ID) projectDB, err := db.GetProject(dbConn, id)
if err != nil && errors.Is(err, sql.ErrNoRows) { if err != nil {
return proj, nil return Project{}, err
} else if err != nil {
return proj, err
} }
p := Project{ p := Project{
ID: proj.ID, ID: projectDB["id"],
URL: proj.URL, URL: projectDB["url"],
Name: proj.Name, Name: projectDB["name"],
Forge: proj.Forge, Forge: projectDB["forge"],
Running: projectDB["version"], Running: projectDB["version"],
} }
return p, err return p, err
} }
// GetProjectWithReleases returns a single project from the database along with its releases // GetProjectWithReleases returns a single project from the database along with its releases
func GetProjectWithReleases(dbConn *sql.DB, mu *sync.Mutex, proj Project) (Project, error) { func GetProjectWithReleases(dbConn *sql.DB, mu *sync.Mutex, id string) (Project, error) {
project, err := GetProject(dbConn, proj) project, err := GetProject(dbConn, id)
if err != nil { if err != nil {
return Project{}, err return Project{}, err
} }
@ -282,7 +257,7 @@ func GetProjects(dbConn *sql.DB) ([]Project, error) {
} }
} }
return SortProjects(projects), nil return projects, nil
} }
// GetProjectsWithReleases returns a list of all projects and all their releases // GetProjectsWithReleases returns a list of all projects and all their releases
@ -301,5 +276,5 @@ func GetProjectsWithReleases(dbConn *sql.DB, mu *sync.Mutex) ([]Project, error)
projects[i].Releases = SortReleases(projects[i].Releases) projects[i].Releases = SortReleases(projects[i].Releases)
} }
return SortProjects(projects), nil return projects, nil
} }

View File

@ -21,11 +21,12 @@
<header class="wrapper"> <header class="wrapper">
<h1>Willow &nbsp;&nbsp;&nbsp;<span><a href="/logout">Log out</a></span></h1> <h1>Willow &nbsp;&nbsp;&nbsp;<span><a href="/logout">Log out</a></span></h1>
<p><a href="/new">Track a new project</a></p> <p><a href="/new">Track a new project</a></p>
<p class="message-{{ .MessageType }}">{{ .Message }}</p>
</header> </header>
<div class="two_column"> <div class="two_column">
<div class="projects"> <div class="projects">
<!-- Range through projects that aren't yet up-to-date --> <!-- Range through projects that aren't yet up-to-date -->
{{- range . -}} {{- range .Projects -}}
{{- if ne .Running (index .Releases 0).Tag -}} {{- if ne .Running (index .Releases 0).Tag -}}
<h2>Outdated projects</h2> <h2>Outdated projects</h2>
{{- break -}} {{- break -}}
@ -34,7 +35,7 @@
{{- range . -}} {{- range . -}}
{{- if ne .Running (index .Releases 0).Tag -}} {{- if ne .Running (index .Releases 0).Tag -}}
<div id="{{ .ID }}" class="project card"> <div id="{{ .ID }}" class="project card">
<h3><a href="{{ .URL }}">{{ .Name }}</a>&nbsp;&nbsp;&nbsp;<span class="delete"><a href="/new?action=delete&id={{ .ID }}">Delete?</a></span></h3> <h3><a href="{{ .URL }}">{{ .Name }}</a>&nbsp;&nbsp;&nbsp;<span class="delete"><a href="/new?action=delete&url={{ .URL }}">Delete?</a></span></h3>
<p>You've selected {{ .Running }}. <a href="/new?action=update&url={{ .URL }}&forge={{ .Forge }}&name={{ .Name }}">Modify?</a></p> <p>You've selected {{ .Running }}. <a href="/new?action=update&url={{ .URL }}&forge={{ .Forge }}&name={{ .Name }}">Modify?</a></p>
<p>Latest: <a href="{{ (index .Releases 0).URL }}">{{ (index .Releases 0).Tag }}</a></p> <p>Latest: <a href="{{ (index .Releases 0).URL }}">{{ (index .Releases 0).Tag }}</a></p>
<p><a href="#{{ (index .Releases 0).ID }}">View release notes</a></p> <p><a href="#{{ (index .Releases 0).ID }}">View release notes</a></p>
@ -43,16 +44,16 @@
{{- end -}} {{- end -}}
<!-- Range through projects that _are_ up-to-date --> <!-- Range through projects that _are_ up-to-date -->
{{- range . -}} {{- range .Projects -}}
{{- if eq .Running (index .Releases 0).Tag -}} {{- if eq .Running (index .Releases 0).Tag -}}
<h2>Up-to-date projects</h2> <h2>Up-to-date projects</h2>
{{- break -}} {{- break -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- range . -}} {{- range .Projects -}}
{{- if eq .Running (index .Releases 0).Tag -}} {{- if eq .Running (index .Releases 0).Tag -}}
<div class="project card"> <div class="project card">
<h3><a href="{{ .URL }}">{{ .Name }}</a>&nbsp;&nbsp;&nbsp;<span class="delete"><a href="/new?action=delete&id={{ .ID }}">Delete?</a></span></h3> <h3><a href="{{ .URL }}">{{ .Name }}</a>&nbsp;&nbsp;&nbsp;<span class="delete"><a href="/new?action=delete&url={{ .URL }}">Delete?</a></span></h3>
<p>You've selected <a href="#{{ (index .Releases 0).ID }}">{{ .Running }}</a>. <a href="/new?action=update&url={{ .URL }}&forge={{ .Forge }}&name={{ .Name }}">Modify?</a></p> <p>You've selected <a href="#{{ (index .Releases 0).ID }}">{{ .Running }}</a>. <a href="/new?action=update&url={{ .URL }}&forge={{ .Forge }}&name={{ .Name }}">Modify?</a></p>
</div> </div>
{{- end -}} {{- end -}}
@ -60,7 +61,7 @@
</div> </div>
<div class="release_notes"> <div class="release_notes">
<h2>Release notes</h2> <h2>Release notes</h2>
{{- range . -}} {{- range .Projects -}}
<div id="{{ (index .Releases 0).ID }}" class="release_note card"> <div id="{{ (index .Releases 0).ID }}" class="release_note card">
<h3>{{ .Name }}: release notes for <a href="{{ (index .Releases 0).URL }}">{{ (index .Releases 0).Tag }}</a> <span class="close"><a href="#">&#x2716;</a></span></h3> <h3>{{ .Name }}: release notes for <a href="{{ (index .Releases 0).URL }}">{{ (index .Releases 0).Tag }}</a> <span class="close"><a href="#">&#x2716;</a></span></h3>
{{- if eq .Forge "github" "gitea" "forgejo" -}} {{- if eq .Forge "github" "gitea" "forgejo" -}}

View File

@ -44,7 +44,7 @@
<input type="hidden" name="name" value="{{ .Name }}"> <input type="hidden" name="name" value="{{ .Name }}">
<input type="hidden" name="forge" value="{{ .Forge }}"> <input type="hidden" name="forge" value="{{ .Forge }}">
<input type="hidden" name="id" value="{{ .ID }}"> <input type="hidden" name="id" value="{{ .ID }}">
<input class="button" type="submit" formaction="/new" value="Track releases"> <input class="button" type="submit" formaction="/new" value="Track future releases">
</form> </form>
<!-- Append these if they ever start limiting RSS entries: `(eq $forge "gitea") (eq $forge "forgejo")` --> <!-- Append these if they ever start limiting RSS entries: `(eq $forge "gitea") (eq $forge "forgejo")` -->
{{- if or (eq $forge "github") -}} {{- if or (eq $forge "github") -}}

View File

@ -128,7 +128,7 @@ header > h1 > span {
} }
.card { .card {
border: 2px solid #424242; border: 2px solid #ccc;
background: #1c1c1c; background: #1c1c1c;
} }

View File

@ -29,6 +29,12 @@ type Handler struct {
Mu *sync.Mutex Mu *sync.Mutex
} }
type page struct {
Projects []project.Project
Message string
MessageType string
}
//go:embed static //go:embed static
var fs embed.FS var fs embed.FS
@ -51,7 +57,12 @@ func (h Handler) RootHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
tmpl := template.Must(template.ParseFS(fs, "static/home.html")) tmpl := template.Must(template.ParseFS(fs, "static/home.html"))
if err := tmpl.Execute(w, data); err != nil { p := page{
Projects: data,
Message: "Hello world",
MessageType: "info",
}
if err := tmpl.Execute(w, p); err != nil {
fmt.Println(err) fmt.Println(err)
} }
} }
@ -80,34 +91,7 @@ func (h Handler) NewHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
forge := bmStrict.Sanitize(params.Get("forge")) proj, err := project.GetProject(h.DbConn, submittedURL)
if forge == "" {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("No forge provided"))
if err != nil {
fmt.Println(err)
}
return
}
name := bmStrict.Sanitize(params.Get("name"))
if name == "" {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("No name provided"))
if err != nil {
fmt.Println(err)
}
return
}
proj := project.Project{
ID: project.GenProjectID(submittedURL, name, forge),
URL: submittedURL,
Name: name,
Forge: forge,
}
proj, err := project.GetProject(h.DbConn, proj)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte(fmt.Sprintf("Error getting project: %s", err))) _, err := w.Write([]byte(fmt.Sprintf("Error getting project: %s", err)))
@ -117,6 +101,38 @@ func (h Handler) NewHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if proj.Running == "" {
forge := bmStrict.Sanitize(params.Get("forge"))
if forge == "" {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("No forge provided"))
if err != nil {
fmt.Println(err)
}
return
}
name := bmStrict.Sanitize(params.Get("name"))
if name == "" {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("No name provided"))
if err != nil {
fmt.Println(err)
}
return
}
proj = project.Project{
URL: submittedURL,
Name: name,
Forge: forge,
}
proj.URL = strings.TrimSuffix(proj.URL, ".git")
}
proj.ID = project.GenProjectID(proj.URL, proj.Name, proj.Forge)
proj, err = project.GetReleases(h.DbConn, h.Mu, proj) proj, err = project.GetReleases(h.DbConn, h.Mu, proj)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)