Skip to content

Commit 1d758fc

Browse files
committed
Initialize repository
0 parents  commit 1d758fc

28 files changed

+1107
-0
lines changed

.editorconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_style = tab
7+
insert_final_newline = true
8+
max_line_length = 120
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false
13+
14+
[*.{yml,yaml}]
15+
indent_style = space
16+
17+
[Makefile]
18+
indent_style = tab

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
*.db
2+
*.yml
3+
debugging/
4+
pushbits
5+
6+
### Go
7+
# Binaries for programs and plugins
8+
*.exe
9+
*.exe~
10+
*.dll
11+
*.so
12+
*.dylib
13+
14+
# Test binary, built with `go test -c`
15+
*.test
16+
17+
# Output of the go coverage tool, specifically when used with LiteIDE
18+
*.out
19+
20+
# Dependency directories (remove the comment below to include it)
21+
# vendor/
22+

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ISC License (ISC)
2+
3+
Copyright 2020 eikendev
4+
5+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.PHONY: test
2+
test:
3+
stdout=$$(gofmt -l . 2>&1); \
4+
if [ "$$stdout" ]; then \
5+
exit 1; \
6+
fi
7+
go test -v -cover ./...
8+
stdout=$$(golint ./... 2>&1); \
9+
if [ "$$stdout" ]; then \
10+
exit 1; \
11+
fi
12+
13+
.PHONY: dependencies
14+
dependencies:
15+
go get -u golang.org/x/lint/golint

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## About
2+
3+
PushBits is a relay server for push notifications.
4+
It enables your services to send notifications via a simple web API, and delivers them to you through various messaging services.
5+
6+
The vision is to have compatibility with [Gotify](https://gotify.net/) on the sending side, while on the receiving side established services are used.
7+
This has the advantages that
8+
- plugins written for Gotify and
9+
- clients written for all major platforms can be reused.
10+
11+
For now, only the [Matrix protocol](https://matrix.org/) is supported, but support for different services like [Telegram](https://telegram.org/) could be added in the future.
12+
I am myself experimenting with Matrix currently because I like the idea of a federated, synchronized but still end-to-end encrypted protocol.
13+
14+
The idea for this software and most parts of the initial source are heavily inspired by [Gotify](https://gotify.net/).
15+
Many thanks to [jmattheis](https://jmattheis.de/) for his well-structured code.
16+
17+
## Usage
18+
19+
PushBits is meant to be self-hosted.
20+
You are advised to install PushBits behind a reverse proxy and enable TLS.
21+
22+
At the moment, there is no front-end implemented.
23+
New users and applications need to be created via the API.
24+
Details will be made available once the interface is more stable.
25+
26+
## Development
27+
28+
PushBits is currently in alpha stage.
29+
The API is neither stable, nor is provided functionality guaranteed to work.
30+
Stay tuned! 😉

api/application.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package api
2+
3+
import (
4+
"log"
5+
"net/http"
6+
7+
"github.com/eikendev/pushbits/authentication"
8+
"github.com/eikendev/pushbits/model"
9+
10+
"github.com/gin-gonic/gin"
11+
)
12+
13+
// The ApplicationDatabase interface for encapsulating database access.
14+
type ApplicationDatabase interface {
15+
CreateApplication(application *model.Application) error
16+
GetApplicationByToken(token string) (*model.Application, error)
17+
}
18+
19+
// The ApplicationDispatcher interface for relaying notifications.
20+
type ApplicationDispatcher interface {
21+
RegisterApplication(name, user string) (string, error)
22+
}
23+
24+
// ApplicationHandler holds information for processing requests about applications.
25+
type ApplicationHandler struct {
26+
DB ApplicationDatabase
27+
Dispatcher ApplicationDispatcher
28+
}
29+
30+
func (h *ApplicationHandler) applicationExists(token string) bool {
31+
application, _ := h.DB.GetApplicationByToken(token)
32+
return application != nil
33+
}
34+
35+
// CreateApplication is used to create a new user.
36+
func (h *ApplicationHandler) CreateApplication(ctx *gin.Context) {
37+
application := model.Application{}
38+
39+
if success := successOrAbort(ctx, http.StatusBadRequest, ctx.Bind(&application)); !success {
40+
return
41+
}
42+
43+
user := authentication.GetUser(ctx)
44+
45+
application.Token = authentication.GenerateNotExistingToken(authentication.GenerateApplicationToken, h.applicationExists)
46+
application.UserID = user.ID
47+
48+
log.Printf("User %s will receive notifications for application %s.\n", user.Name, application.Name)
49+
50+
matrixid, err := h.Dispatcher.RegisterApplication(application.Name, user.MatrixID)
51+
52+
if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success {
53+
return
54+
}
55+
56+
application.MatrixID = matrixid
57+
58+
if success := successOrAbort(ctx, http.StatusInternalServerError, h.DB.CreateApplication(&application)); !success {
59+
return
60+
}
61+
62+
ctx.JSON(http.StatusOK, &application)
63+
}

api/notification.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package api
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"strings"
7+
"time"
8+
9+
"github.com/eikendev/pushbits/authentication"
10+
"github.com/eikendev/pushbits/model"
11+
12+
"github.com/gin-gonic/gin"
13+
)
14+
15+
// The NotificationDatabase interface for encapsulating database access.
16+
type NotificationDatabase interface {
17+
}
18+
19+
// The NotificationDispatcher interface for relaying notifications.
20+
type NotificationDispatcher interface {
21+
SendNotification(a *model.Application, n *model.Notification) error
22+
}
23+
24+
// NotificationHandler holds information for processing requests about notifications.
25+
type NotificationHandler struct {
26+
DB NotificationDatabase
27+
Dispatcher NotificationDispatcher
28+
}
29+
30+
// CreateNotification is used to create a new notification for a user.
31+
func (h *NotificationHandler) CreateNotification(ctx *gin.Context) {
32+
notification := model.Notification{}
33+
34+
if success := successOrAbort(ctx, http.StatusBadRequest, ctx.Bind(&notification)); !success {
35+
return
36+
}
37+
38+
application := authentication.GetApplication(ctx)
39+
log.Printf("Sending notification for application %s.\n", application.Name)
40+
41+
notification.ID = 0
42+
notification.ApplicationID = application.ID
43+
if strings.TrimSpace(notification.Title) == "" {
44+
notification.Title = application.Name
45+
}
46+
notification.Date = time.Now()
47+
48+
if success := successOrAbort(ctx, http.StatusInternalServerError, h.Dispatcher.SendNotification(application, &notification)); !success {
49+
return
50+
}
51+
52+
ctx.JSON(http.StatusOK, &notification)
53+
}

api/user.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package api
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/eikendev/pushbits/model"
8+
9+
"github.com/gin-gonic/gin"
10+
)
11+
12+
// The UserDatabase interface for encapsulating database access.
13+
type UserDatabase interface {
14+
CreateUser(user *model.User) error
15+
GetUserByName(name string) (*model.User, error)
16+
}
17+
18+
// UserHandler holds information for processing requests about users.
19+
type UserHandler struct {
20+
DB UserDatabase
21+
}
22+
23+
func (h *UserHandler) userExists(name string) bool {
24+
user, _ := h.DB.GetUserByName(name)
25+
return user != nil
26+
}
27+
28+
// CreateUser creates a new user.
29+
func (h *UserHandler) CreateUser(ctx *gin.Context) {
30+
externalUser := model.ExternalUserWithCredentials{}
31+
32+
if success := successOrAbort(ctx, http.StatusBadRequest, ctx.Bind(&externalUser)); !success {
33+
return
34+
}
35+
36+
user := externalUser.IntoInternalUser()
37+
38+
if h.userExists(user.Name) {
39+
ctx.AbortWithError(http.StatusBadRequest, errors.New("username already exists"))
40+
return
41+
}
42+
43+
if success := successOrAbort(ctx, http.StatusInternalServerError, h.DB.CreateUser(user)); !success {
44+
return
45+
}
46+
47+
ctx.JSON(http.StatusOK, user.IntoExternalUser())
48+
}

api/util.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package api
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
)
6+
7+
func successOrAbort(ctx *gin.Context, code int, err error) bool {
8+
if err != nil {
9+
ctx.AbortWithError(code, err)
10+
}
11+
12+
return err == nil
13+
}

app.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
9+
"github.com/eikendev/pushbits/configuration"
10+
"github.com/eikendev/pushbits/database"
11+
"github.com/eikendev/pushbits/dispatcher"
12+
"github.com/eikendev/pushbits/router"
13+
"github.com/eikendev/pushbits/runner"
14+
)
15+
16+
func setupCleanup(db *database.Database, dp *dispatcher.Dispatcher) {
17+
c := make(chan os.Signal)
18+
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
19+
20+
go func() {
21+
<-c
22+
dp.Close()
23+
db.Close()
24+
os.Exit(1)
25+
}()
26+
}
27+
28+
func main() {
29+
log.Println("Starting PushBits.")
30+
31+
c := configuration.Get()
32+
33+
db, err := database.Create(c.Database.Dialect, c.Database.Connection)
34+
if err != nil {
35+
panic(err)
36+
}
37+
defer db.Close()
38+
39+
if err := db.Populate(c.Admin.Name, c.Admin.Password, c.Admin.MatrixID); err != nil {
40+
panic(err)
41+
}
42+
43+
dp, err := dispatcher.Create(db, c.Matrix.Homeserver, c.Matrix.Username, c.Matrix.Password)
44+
if err != nil {
45+
panic(err)
46+
}
47+
defer dp.Close()
48+
49+
setupCleanup(db, dp)
50+
51+
engine := router.Create(db, dp)
52+
runner.Run(engine)
53+
}

0 commit comments

Comments
 (0)