added template for new golang backend

This commit is contained in:
Mike Schwörer 2022-11-13 19:17:07 +01:00
parent bd11d7973c
commit 0e58a5c5f0
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
36 changed files with 2908 additions and 0 deletions

176
server/.gitignore vendored Normal file
View File

@ -0,0 +1,176 @@
_build
.run-data
DOCKER_GIT_INFO
##############
# Created by https://www.toptal.com/developers/gitignore/api/goland,macos,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=goland,macos,linux
### GoLand ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### GoLand Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
# End of https://www.toptal.com/developers/gitignore/api/goland,macos,linux

8
server/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

8
server/.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/server.iml" filepath="$PROJECT_DIR$/.idea/server.iml" />
</modules>
</component>
</project>

9
server/.idea/server.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
server/.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

65
server/Makefile Normal file
View File

@ -0,0 +1,65 @@
DOCKER_REPO=registry.blackforestbytes.com
DOCKER_NAME=scn_server
PORT=9090
NAMESPACE=$(shell git rev-parse --abbrev-ref HEAD)
HASH=$(shell git rev-parse HEAD)
build:
rm -f ./_build/scn_backend
go build -v -o _build/scn_backend ./cmd/scnserver
run: build
mkdir -p .run-data
_build/scn_backend
build-docker:
[ ! -f "DOCKER_GIT_INFO" ] || rm DOCKER_GIT_INFO
git rev-parse --abbrev-ref HEAD >> DOCKER_GIT_INFO
git rev-parse HEAD >> DOCKER_GIT_INFO
git log -1 --format=%cd --date=iso >> DOCKER_GIT_INFO
git config --get remote.origin.url >> DOCKER_GIT_INFO
docker build \
-t "$(DOCKER_NAME):$(HASH)" \
-t "$(DOCKER_NAME):$(NAMESPACE)-latest" \
-t "$(DOCKER_NAME):latest" \
-t "$(DOCKER_REPO)/$(DOCKER_NAME):$(HASH)" \
-t "$(DOCKER_REPO)/$(DOCKER_NAME):$(NAMESPACE)-latest" \
-t "$(DOCKER_REPO)/$(DOCKER_NAME):latest" \
.
build-swagger:
which swag || go install github.com/swaggo/swag/cmd/swag@latest
swag init -generalInfo api/router.go --output ./swagger/ --outputTypes "json,yaml"
run-docker-local:
mkdir -p .run-data
docker run --rm \
--init \
--env "CONF_NS=local" \
--volume "$(shell pwd)/.run-data/docker-local:/data" \
--network host \
$(DOCKER_NAME):latest
inspect-docker:
mkdir -p .run-data
docker run -ti \
--rm \
--volume "$(shell pwd)/.run-data/docker-inspect:/data" \
$(DOCKER_NAME):latest \
bash
push-docker:
docker image push "$(DOCKER_REPO)/$(DOCKER_NAME):$(HASH)"
docker image push "$(DOCKER_REPO)/$(DOCKER_NAME):$(NAMESPACE)-latest"
docker image push "$(DOCKER_REPO)/$(DOCKER_NAME):latest"
clean:
rm -rf _build/*
rm -rf .run-data/*
git clean -fdx
go clean
fmt:
go fmt ./...
swag fmt

View File

@ -0,0 +1,30 @@
package apierr
type APIError int
const (
NO_ERROR APIError = 0000
MISSING_UID APIError = 1101
MISSING_TOK APIError = 1102
MISSING_TITLE APIError = 1103
INVALID_PRIO APIError = 1104
REQ_METHOD APIError = 1105
NO_TITLE APIError = 1201
TITLE_TOO_LONG APIError = 1202
CONTENT_TOO_LONG APIError = 1203
USR_MSG_ID_TOO_LONG APIError = 1204
TIMESTAMP_OUT_OF_RANGE APIError = 1205
USER_NOT_FOUND APIError = 1301
USER_AUTH_FAILED APIError = 1302
NO_DEVICE_LINKED APIError = 1401
QUOTA_REACHED APIError = 2101
FIREBASE_COM_FAILED APIError = 9901
FIREBASE_COM_ERRORED APIError = 9902
INTERNAL_EXCEPTION APIError = 9903
)

View File

@ -0,0 +1,98 @@
package handler
import (
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
"blackforestbytes.com/simplecloudnotifier/logic"
"bytes"
"github.com/gin-gonic/gin"
sqlite3 "github.com/mattn/go-sqlite3"
"net/http"
)
type CommonHandler struct {
app *logic.Application
}
func NewCommonHandler(app *logic.Application) CommonHandler {
return CommonHandler{
app: app,
}
}
type pingResponse struct {
Message string `json:"message"`
Info pingResponseInfo `json:"info"`
}
type pingResponseInfo struct {
Method string `json:"method"`
Request string `json:"request"`
Headers map[string][]string `json:"headers"`
URI string `json:"uri"`
Address string `json:"addr"`
}
// Ping swaggerdoc
//
// @Success 200 {object} pingResponse
// @Failure 500 {object} ginresp.errBody
// @Router /ping [get]
// @Router /ping [post]
// @Router /ping [put]
// @Router /ping [delete]
// @Router /ping [patch]
func (h CommonHandler) Ping(g *gin.Context) ginresp.HTTPResponse {
buf := new(bytes.Buffer)
_, _ = buf.ReadFrom(g.Request.Body)
resuestBody := buf.String()
return ginresp.JSON(http.StatusOK, pingResponse{
Message: "Pong",
Info: pingResponseInfo{
Method: g.Request.Method,
Request: resuestBody,
Headers: g.Request.Header,
URI: g.Request.RequestURI,
Address: g.Request.RemoteAddr,
},
})
}
// DatabaseTest swaggerdoc
//
// @Success 200 {object} handler.DatabaseTest.response
// @Failure 500 {object} ginresp.errBody
// @Router /db-test [get]
func (h CommonHandler) DatabaseTest(g *gin.Context) ginresp.HTTPResponse {
type response struct {
Success bool `json:"success"`
LibVersion string `json:"libVersion"`
LibVersionNumber int `json:"libVersionNumber"`
SourceID string `json:"sourceID"`
}
libVersion, libVersionNumber, sourceID := sqlite3.Version()
err := h.app.Database.Ping()
if err != nil {
return ginresp.InternalError(err)
}
return ginresp.JSON(http.StatusOK, response{
Success: true,
LibVersion: libVersion,
LibVersionNumber: libVersionNumber,
SourceID: sourceID,
})
}
// Health swaggerdoc
//
// @Success 200 {object} handler.Health.response
// @Failure 500 {object} ginresp.errBody
// @Router /health [get]
func (h CommonHandler) Health(*gin.Context) ginresp.HTTPResponse {
type response struct {
Status string `json:"status"`
}
return ginresp.JSON(http.StatusOK, response{Status: "ok"})
}

View File

@ -0,0 +1,248 @@
package handler
import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/api/models"
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
"blackforestbytes.com/simplecloudnotifier/logic"
"github.com/gin-gonic/gin"
)
type CompatHandler struct {
app *logic.Application
}
func NewCompatHandler(app *logic.Application) CompatHandler {
return CompatHandler{
app: app,
}
}
// Register swaggerdoc
//
// @Summary Register a new account
// @Param fcm_token query string true "the (android) fcm token"
// @Param pro query string true "if the user is a paid account" Enums(true, false)
// @Param pro_token query string true "the (android) IAP token"
// @Success 200 {object} handler.Register.response
// @Failure 500 {object} ginresp.internAPIError
// @Router /register.php [get]
func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse {
type query struct {
FCMToken string `form:"fcm_token"`
Pro string `form:"pro"`
ProToken string `form:"pro_token"`
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
UserID string `json:"user_id"`
UserKey string `json:"user_key"`
QuotaUsed string `json:"quota"`
QuotaMax string `json:"quota_max"`
IsPro string `json:"is_pro"`
}
//TODO
return ginresp.NotImplemented(0)
}
// Info swaggerdoc
//
// @Summary Get information about the current user
// @Param user_id query string true "the user_id"
// @Param user_key query string true "the user_key"
// @Success 200 {object} handler.Info.response
// @Failure 500 {object} ginresp.internAPIError
// @Router /info.php [get]
func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
type query struct {
UserID string `form:"user_id"`
UserKey string `form:"user_key"`
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
UserID string `json:"user_id"`
UserKey string `json:"user_key"`
QuotaUsed string `json:"quota"`
QuotaMax string `json:"quota_max"`
IsPro string `json:"is_pro"`
FCMSet bool `json:"fcm_token_set"`
UnackCount int `json:"unack_count"`
}
//TODO
return ginresp.NotImplemented(0)
}
// Ack swaggerdoc
//
// @Summary Acknowledge that a message was received
// @Param user_id query string true "the user_id"
// @Param user_key query string true "the user_key"
// @Param scn_msg_id query string true "the message id"
// @Success 200 {object} handler.Ack.response
// @Failure 500 {object} ginresp.internAPIError
// @Router /ack.php [get]
func (h CompatHandler) Ack(g *gin.Context) ginresp.HTTPResponse {
type query struct {
UserID string `form:"user_id"`
UserKey string `form:"user_key"`
MessageID string `form:"scn_msg_id"`
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
PrevAckValue int `json:"prev_ack"`
NewAckValue int `json:"new_ack"`
}
//TODO
return ginresp.NotImplemented(0)
}
// Requery swaggerdoc
//
// @Summary Return all not-acknowledged messages
// @Param user_id query string true "the user_id"
// @Param user_key query string true "the user_key"
// @Success 200 {object} handler.Requery.response
// @Failure 500 {object} ginresp.internAPIError
// @Router /requery.php [get]
func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse {
type query struct {
UserID string `form:"user_id"`
UserKey string `form:"user_key"`
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
Count int `json:"count"`
Data []models.CompatMessage `json:"data"`
}
//TODO
return ginresp.NotImplemented(0)
}
// Update swaggerdoc
//
// @Summary Set the fcm-token (android)
// @Param user_id query string true "the user_id"
// @Param user_key query string true "the user_key"
// @Param fcm_token query string true "the (android) fcm token"
// @Success 200 {object} handler.Update.response
// @Failure 500 {object} ginresp.internAPIError
// @Router /update.php [get]
func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse {
type query struct {
UserID string `form:"user_id"`
UserKey string `form:"user_key"`
FCMToken string `form:"fcm_token"`
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
UserID string `json:"user_id"`
UserKey string `json:"user_key"`
QuotaUsed string `json:"quota"`
QuotaMax string `json:"quota_max"`
IsPro string `json:"is_pro"`
}
//TODO
return ginresp.NotImplemented(0)
}
// Expand swaggerdoc
//
// @Summary Get a whole (potentially truncated) message
// @Success 200 {object} handler.Expand.response
// @Failure 500 {object} ginresp.internAPIError
// @Router /expand.php [get]
func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
type query struct {
UserID string `form:"user_id"`
UserKey string `form:"user_key"`
MessageID string `form:"scn_msg_id"`
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
Data models.ShortCompatMessage `json:"data"`
}
//TODO
return ginresp.NotImplemented(0)
}
// Upgrade swaggerdoc
//
// @Summary Upgrade a free account to a paid account
// @Param user_id query string true "the user_id"
// @Param user_key query string true "the user_key"
// @Param pro query string true "if the user is a paid account" Enums(true, false)
// @Param pro_token query string true "the (android) IAP token"
// @Success 200 {object} handler.Upgrade.response
// @Failure 500 {object} ginresp.internAPIError
// @Router /upgrade.php [get]
func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse {
type query struct {
UserID string `form:"user_id"`
UserKey string `form:"user_key"`
Pro string `form:"pro"`
ProToken string `form:"pro_token"`
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
Data models.ShortCompatMessage `json:"data"`
}
//TODO
return ginresp.NotImplemented(0)
}
// Send swaggerdoc
//
// @Summary Send a message
// @Description all aeguments can either be supplied in the query or in the json body
// @Param user_id query string true "the user_id"
// @Param user_key query string true "the user_key"
// @Param title query string true "The message title"
// @Param content query string false "The message content"
// @Param priority query string false "The message priority" Enum(0, 1, 2)
// @Param msg_id query string false "The message idempotency id"
// @Param timestamp query string false "The message timestamp"
// @Param user_id body string true "the user_id"
// @Param user_key body string true "the user_key"
// @Param title body string true "The message title"
// @Param content body string false "The message content"
// @Param priority body string false "The message priority" Enum(0, 1, 2)
// @Param msg_id body string false "The message idempotency id"
// @Param timestamp body string false "The message timestamp"
// @Success 200 {object} handler.Send.response
// @Failure 500 {object} ginresp.sendAPIError
// @Router /send.php [post]
func (h CompatHandler) Send(g *gin.Context) ginresp.HTTPResponse {
type query struct {
//TODO
}
type response struct {
Success string `json:"success"`
Message string `json:"message"`
//TODO
}
//TODO
return ginresp.SendAPIError(apierr.INTERNAL_EXCEPTION, -1, "NotImplemented")
}

View File

@ -0,0 +1,20 @@
package models
type CompatMessage struct {
Title string `json:"title"`
Body string `json:"body"`
Priority int `json:"priority"`
Timestamp int64 `json:"timestamp"`
UserMessageID string `json:"usr_msg_id"`
SCNMessageID string `json:"scn_msg_id"`
}
type ShortCompatMessage struct {
Title string `json:"title"`
Body string `json:"body"`
Trimmed bool `json:"trimmed"`
Priority int `json:"priority"`
Timestamp int64 `json:"timestamp"`
UserMessageID string `json:"usr_msg_id"`
SCNMessageID string `json:"scn_msg_id"`
}

52
server/api/router.go Normal file
View File

@ -0,0 +1,52 @@
package api
import (
"blackforestbytes.com/simplecloudnotifier/api/handler"
"blackforestbytes.com/simplecloudnotifier/common/ginext"
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
"blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/swagger"
"github.com/gin-gonic/gin"
)
type Router struct {
app *logic.Application
commonHandler handler.CommonHandler
compatHandler handler.CompatHandler
}
func NewRouter(app *logic.Application) *Router {
return &Router{
app: app,
commonHandler: handler.NewCommonHandler(app),
compatHandler: handler.NewCompatHandler(app),
}
}
// Init swaggerdocs
// @title SimpleCloudNotifier API
// @version 2.0
// @description API for SCN
// @host scn.blackforestbytes.com
// @BasePath /api/
func (r *Router) Init(e *gin.Engine) {
e.Any("/ping", ginresp.Wrap(r.commonHandler.Ping))
e.POST("/db-test", ginresp.Wrap(r.commonHandler.DatabaseTest))
e.GET("/health", ginresp.Wrap(r.commonHandler.Health))
e.GET("documentation/swagger", ginext.RedirectTemporary("/documentation/swagger/"))
e.GET("documentation/swagger/", ginresp.Wrap(swagger.Handle))
e.GET("documentation/swagger/:fn", ginresp.Wrap(swagger.Handle))
e.POST("/send.php", ginresp.Wrap(r.compatHandler.Send))
e.GET("/register.php", ginresp.Wrap(r.compatHandler.Register))
e.GET("/info.php", ginresp.Wrap(r.compatHandler.Info))
e.GET("/ack.php", ginresp.Wrap(r.compatHandler.Ack))
e.GET("/requery.php", ginresp.Wrap(r.compatHandler.Requery))
e.GET("/update.php", ginresp.Wrap(r.compatHandler.Update))
e.GET("/expand.php", ginresp.Wrap(r.compatHandler.Expand))
e.GET("/upgrade.php", ginresp.Wrap(r.compatHandler.Upgrade))
}

View File

@ -0,0 +1,37 @@
package main
import (
scn "blackforestbytes.com/simplecloudnotifier"
"blackforestbytes.com/simplecloudnotifier/api"
"blackforestbytes.com/simplecloudnotifier/common"
"blackforestbytes.com/simplecloudnotifier/common/ginext"
"blackforestbytes.com/simplecloudnotifier/db"
"blackforestbytes.com/simplecloudnotifier/logic"
"fmt"
"github.com/rs/zerolog/log"
)
var conf = scn.Conf
func main() {
common.Init(conf)
log.Info().Msg(fmt.Sprintf("Starting with config-namespace <%s>", conf.Namespace))
sqlite, err := db.NewDatabase(conf)
if err != nil {
panic(err)
}
app := logic.NewApp(sqlite)
ginengine := ginext.NewEngine(conf)
router := api.NewRouter(app)
app.Init(conf, ginengine)
router.Init(ginengine)
app.Run()
}

View File

@ -0,0 +1,21 @@
package ginext
import (
"github.com/gin-gonic/gin"
"net/http"
)
func CorsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusOK)
} else {
c.Next()
}
}
}

View File

@ -0,0 +1,21 @@
package ginext
import (
scn "blackforestbytes.com/simplecloudnotifier"
"github.com/gin-gonic/gin"
)
func NewEngine(cfg scn.Config) *gin.Engine {
engine := gin.New()
engine.RedirectFixedPath = false
engine.RedirectTrailingSlash = false
engine.Use(CorsMiddleware())
if cfg.GinDebug {
engine.Use(gin.Logger())
}
return engine
}

View File

@ -0,0 +1,24 @@
package ginext
import (
"github.com/gin-gonic/gin"
"net/http"
)
func RedirectFound(newuri string) gin.HandlerFunc {
return func(g *gin.Context) {
g.Redirect(http.StatusFound, newuri)
}
}
func RedirectTemporary(newuri string) gin.HandlerFunc {
return func(g *gin.Context) {
g.Redirect(http.StatusTemporaryRedirect, newuri)
}
}
func RedirectPermanent(newuri string) gin.HandlerFunc {
return func(g *gin.Context) {
g.Redirect(http.StatusPermanentRedirect, newuri)
}
}

View File

@ -0,0 +1,14 @@
package ginresp
type sendAPIError struct {
Success bool `json:"success"`
Error int `json:"error"`
ErrorHighlight int `json:"errhighlight"`
Message string `json:"message"`
}
type internAPIError struct {
Success bool `json:"success"`
ErrorID int `json:"errid,omitempty"`
Message string `json:"message"`
}

View File

@ -0,0 +1,6 @@
package ginresp
type errBody struct {
Success bool `json:"success"`
Message string `json:"message"`
}

View File

@ -0,0 +1,85 @@
package ginresp
import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
"github.com/gin-gonic/gin"
"net/http"
)
type HTTPResponse interface {
Write(context *gin.Context)
}
type jsonHTTPResponse struct {
statusCode int
data any
}
func (j jsonHTTPResponse) Write(g *gin.Context) {
g.JSON(j.statusCode, j.data)
}
type emptyHTTPResponse struct {
statusCode int
data any
}
func (j emptyHTTPResponse) Write(g *gin.Context) {
g.Status(j.statusCode)
}
type textHTTPResponse struct {
statusCode int
data string
}
func (j textHTTPResponse) Write(g *gin.Context) {
g.String(j.statusCode, "%s", j.data)
}
type dataHTTPResponse struct {
statusCode int
data []byte
contentType string
}
func (j dataHTTPResponse) Write(g *gin.Context) {
g.Data(j.statusCode, j.contentType, j.data)
}
type errHTTPResponse struct {
statusCode int
data any
}
func (j errHTTPResponse) Write(g *gin.Context) {
g.JSON(j.statusCode, j.data)
}
func Status(sc int) HTTPResponse {
return &emptyHTTPResponse{statusCode: sc}
}
func JSON(sc int, data any) HTTPResponse {
return &jsonHTTPResponse{statusCode: sc, data: data}
}
func Data(sc int, contentType string, data []byte) HTTPResponse {
return &dataHTTPResponse{statusCode: sc, contentType: contentType, data: data}
}
func Text(sc int, data string) HTTPResponse {
return &textHTTPResponse{statusCode: sc, data: data}
}
func InternalError(e error) HTTPResponse {
return &errHTTPResponse{statusCode: http.StatusInternalServerError, data: errBody{Success: false, Message: e.Error()}}
}
func NotImplemented(errid int) HTTPResponse {
return &errHTTPResponse{statusCode: http.StatusInternalServerError, data: internAPIError{Success: false, ErrorID: errid, Message: "NotImplemented"}}
}
func SendAPIError(errorid apierr.APIError, highlight int, msg string) HTTPResponse {
return &errHTTPResponse{statusCode: http.StatusInternalServerError, data: sendAPIError{Success: false, Error: int(errorid), ErrorHighlight: highlight, Message: msg}}
}

View File

@ -0,0 +1,21 @@
package ginresp
import "github.com/gin-gonic/gin"
type WHandlerFunc func(*gin.Context) HTTPResponse
func Wrap(fn WHandlerFunc) gin.HandlerFunc {
return func(context *gin.Context) {
wrap := fn(context)
if context.Writer.Written() {
panic("Writing in WrapperFunc is not supported")
}
wrap.Write(context)
}
}

35
server/common/init.go Normal file
View File

@ -0,0 +1,35 @@
package common
import (
scn "blackforestbytes.com/simplecloudnotifier"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
)
func Init(cfg scn.Config) {
cw := zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: "2006-01-02 15:04:05 Z07:00",
}
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
multi := zerolog.MultiLevelWriter(cw)
logger := zerolog.New(multi).With().
Timestamp().
Caller().
Logger()
log.Logger = logger
if cfg.GinDebug {
gin.SetMode(gin.DebugMode)
zerolog.SetGlobalLevel(zerolog.DebugLevel)
} else {
gin.SetMode(gin.ReleaseMode)
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}
log.Debug().Msg("Initialized")
}

78
server/config.go Normal file
View File

@ -0,0 +1,78 @@
package server
import (
"github.com/rs/zerolog/log"
"os"
)
type Config struct {
Namespace string
GinDebug bool
ServerIP string
ServerPort string
DBFile string
}
var Conf Config
var configLoc = Config{
Namespace: "local",
GinDebug: true,
ServerIP: "0.0.0.0",
ServerPort: "8080",
DBFile: ".run-data/db.sqlite3",
}
var configDev = Config{
Namespace: "develop",
GinDebug: true,
ServerIP: "0.0.0.0",
ServerPort: "80",
DBFile: "/data/scn.sqlite3",
}
var configStag = Config{
Namespace: "staging",
GinDebug: true,
ServerIP: "0.0.0.0",
ServerPort: "80",
DBFile: "/data/scn.sqlite3",
}
var configProd = Config{
Namespace: "production",
GinDebug: false,
ServerIP: "0.0.0.0",
ServerPort: "80",
DBFile: "/data/scn.sqlite3",
}
var allConfig = []Config{
configLoc,
configDev,
configStag,
configProd,
}
func getConfig(ns string) (Config, bool) {
if ns == "" {
return configLoc, true
}
for _, c := range allConfig {
if c.Namespace == ns {
return c, true
}
}
return Config{}, false
}
func init() {
ns := os.Getenv("CONF_NS")
cfg, ok := getConfig(ns)
if !ok {
log.Fatal().Str("ns", ns).Msg("Unknown config-namespace")
}
Conf = cfg
}

11
server/db/database.go Normal file
View File

@ -0,0 +1,11 @@
package db
import (
scn "blackforestbytes.com/simplecloudnotifier"
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
func NewDatabase(conf scn.Config) (*sql.DB, error) {
return sql.Open("sqlite3", conf.DBFile)
}

50
server/docs/docs.go Normal file
View File

@ -0,0 +1,50 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/ping": {
"get": {
"description": "asdf",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Test request",
"operationId": "ping",
"responses": {}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "",
Description: "",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

22
server/docs/swagger.json Normal file
View File

@ -0,0 +1,22 @@
{
"swagger": "2.0",
"info": {
"contact": {}
},
"paths": {
"/ping": {
"get": {
"description": "asdf",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Test request",
"operationId": "ping",
"responses": {}
}
}
}
}

14
server/docs/swagger.yaml Normal file
View File

@ -0,0 +1,14 @@
info:
contact: {}
paths:
/ping:
get:
consumes:
- application/json
description: asdf
operationId: ping
produces:
- application/json
responses: {}
summary: Test request
swagger: "2.0"

42
server/go.mod Normal file
View File

@ -0,0 +1,42 @@
module blackforestbytes.com/simplecloudnotifier
go 1.19
require (
github.com/gin-gonic/gin v1.8.1
github.com/rs/zerolog v1.28.0
github.com/swaggo/swag v1.8.7
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

133
server/go.sum Normal file
View File

@ -0,0 +1,133 @@
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,63 @@
package logic
import (
scn "blackforestbytes.com/simplecloudnotifier"
"context"
"database/sql"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
type Application struct {
Config scn.Config
Gin *gin.Engine
Database *sql.DB
}
func NewApp(db *sql.DB) *Application {
return &Application{Database: db}
}
func (app *Application) Init(cfg scn.Config, g *gin.Engine) {
app.Config = cfg
app.Gin = g
}
func (app *Application) Run() {
httpserver := &http.Server{
Addr: net.JoinHostPort(app.Config.ServerIP, app.Config.ServerPort),
Handler: app.Gin,
}
errChan := make(chan error)
go func() {
log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started")
errChan <- httpserver.ListenAndServe()
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
select {
case <-stop:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
log.Info().Msg("Stopping HTTP-Server")
err := httpserver.Shutdown(ctx)
if err != nil {
log.Info().Err(err).Msg("Error while stopping the http-server")
return
}
log.Info().Msg("Stopped HTTP-Server")
case err := <-errChan:
log.Error().Err(err).Msg("HTTP-Server failed")
}
}

27
server/swagger/index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>API Documentation</title>
<link rel="stylesheet" href="swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js" crossorigin></script>
<script src="swagger-ui-standalone-preset.js" crossorigin></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: './swagger.json',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout"
});
};
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

68
server/swagger/swagger.go Normal file
View File

@ -0,0 +1,68 @@
package swagger
import (
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
"embed"
_ "embed"
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
//go:embed index.html
//go:embed swagger.json
//go:embed swagger.yaml
//go:embed swagger-ui-bundle.js
//go:embed swagger-ui.css
//go:embed swagger-ui-standalone-preset.js
var assets embed.FS
func getAsset(fn string) ([]byte, string, bool) {
data, err := assets.ReadFile(fn)
if err != nil {
return nil, "", false
}
mime := "text/plain"
lowerFN := strings.ToLower(fn)
if strings.HasSuffix(lowerFN, ".html") || strings.HasSuffix(lowerFN, ".htm") {
mime = "text/html"
} else if strings.HasSuffix(lowerFN, ".css") {
mime = "text/css"
} else if strings.HasSuffix(lowerFN, ".js") {
mime = "text/javascript"
} else if strings.HasSuffix(lowerFN, ".json") {
mime = "application/json"
} else if strings.HasSuffix(lowerFN, ".jpeg") || strings.HasSuffix(lowerFN, ".jpg") {
mime = "image/jpeg"
} else if strings.HasSuffix(lowerFN, ".png") {
mime = "image/png"
} else if strings.HasSuffix(lowerFN, ".svg") {
mime = "image/svg+xml"
}
return data, mime, true
}
func Handle(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
Filename string `uri:"fn"`
}
var u uri
if err := g.ShouldBindUri(&u); err != nil {
return ginresp.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
if u.Filename == "" {
index, _, _ := getAsset("index.html")
return ginresp.Data(http.StatusOK, "text/html", index)
}
if data, mime, ok := getAsset(u.Filename); ok {
return ginresp.Data(http.StatusOK, mime, data)
}
return ginresp.JSON(http.StatusNotFound, gin.H{"error": "AssetNotFound", "filename": u.Filename})
}

844
server/swagger/swagger.json Normal file
View File

@ -0,0 +1,844 @@
{
"swagger": "2.0",
"info": {
"description": "API for SCN",
"title": "SimpleCloudNotifier API",
"contact": {},
"version": "2.0"
},
"host": "scn.blackforestbytes.com",
"basePath": "/api/",
"paths": {
"/ack.php": {
"get": {
"summary": "Acknowledge that a message was received",
"parameters": [
{
"type": "string",
"description": "the user_id",
"name": "user_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the user_key",
"name": "user_key",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the message id",
"name": "scn_msg_id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Ack.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.internAPIError"
}
}
}
}
},
"/db-test": {
"get": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.DatabaseTest.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.errBody"
}
}
}
}
},
"/expand.php": {
"get": {
"summary": "Get a whole (potentially truncated) message",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Expand.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.internAPIError"
}
}
}
}
},
"/health": {
"get": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Health.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.errBody"
}
}
}
}
},
"/info.php": {
"get": {
"summary": "Get information about the current user",
"parameters": [
{
"type": "string",
"description": "the user_id",
"name": "user_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the user_key",
"name": "user_key",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Info.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.internAPIError"
}
}
}
}
},
"/ping": {
"get": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.pingResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.errBody"
}
}
}
},
"put": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.pingResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.errBody"
}
}
}
},
"post": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.pingResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.errBody"
}
}
}
},
"delete": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.pingResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.errBody"
}
}
}
},
"patch": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.pingResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.errBody"
}
}
}
}
},
"/register.php": {
"get": {
"summary": "Register a new account",
"parameters": [
{
"type": "string",
"description": "the (android) fcm token",
"name": "fcm_token",
"in": "query",
"required": true
},
{
"enum": [
"true",
"false"
],
"type": "string",
"description": "if the user is a paid account",
"name": "pro",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the (android) IAP token",
"name": "pro_token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Register.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.internAPIError"
}
}
}
}
},
"/requery.php": {
"get": {
"summary": "Return all not-acknowledged messages",
"parameters": [
{
"type": "string",
"description": "the user_id",
"name": "user_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the user_key",
"name": "user_key",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Requery.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.internAPIError"
}
}
}
}
},
"/send.php": {
"post": {
"description": "all aeguments can either be supplied in the query or in the json body",
"summary": "Send a message",
"parameters": [
{
"type": "string",
"description": "the user_id",
"name": "user_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the user_key",
"name": "user_key",
"in": "query",
"required": true
},
{
"type": "string",
"description": "The message title",
"name": "title",
"in": "query",
"required": true
},
{
"type": "string",
"description": "The message content",
"name": "content",
"in": "query"
},
{
"type": "string",
"description": "The message priority",
"name": "priority",
"in": "query"
},
{
"type": "string",
"description": "The message idempotency id",
"name": "msg_id",
"in": "query"
},
{
"type": "string",
"description": "The message timestamp",
"name": "timestamp",
"in": "query"
},
{
"description": "the user_id",
"name": "user_id",
"in": "body",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "the user_key",
"name": "user_key",
"in": "body",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The message title",
"name": "title",
"in": "body",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The message content",
"name": "content",
"in": "body",
"schema": {
"type": "string"
}
},
{
"description": "The message priority",
"name": "priority",
"in": "body",
"schema": {
"type": "string"
}
},
{
"description": "The message idempotency id",
"name": "msg_id",
"in": "body",
"schema": {
"type": "string"
}
},
{
"description": "The message timestamp",
"name": "timestamp",
"in": "body",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Send.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.sendAPIError"
}
}
}
}
},
"/update.php": {
"get": {
"summary": "Set the fcm-token (android)",
"parameters": [
{
"type": "string",
"description": "the user_id",
"name": "user_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the user_key",
"name": "user_key",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the (android) fcm token",
"name": "fcm_token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Update.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.internAPIError"
}
}
}
}
},
"/upgrade.php": {
"get": {
"summary": "Upgrade a free account to a paid account",
"parameters": [
{
"type": "string",
"description": "the user_id",
"name": "user_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the user_key",
"name": "user_key",
"in": "query",
"required": true
},
{
"enum": [
"true",
"false"
],
"type": "string",
"description": "if the user is a paid account",
"name": "pro",
"in": "query",
"required": true
},
{
"type": "string",
"description": "the (android) IAP token",
"name": "pro_token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Upgrade.response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.internAPIError"
}
}
}
}
}
},
"definitions": {
"ginresp.errBody": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
}
},
"ginresp.internAPIError": {
"type": "object",
"properties": {
"errid": {
"type": "integer"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
}
},
"ginresp.sendAPIError": {
"type": "object",
"properties": {
"errhighlight": {
"type": "integer"
},
"error": {
"type": "integer"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
}
},
"handler.Ack.response": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"new_ack": {
"type": "integer"
},
"prev_ack": {
"type": "integer"
},
"success": {
"type": "string"
}
}
},
"handler.DatabaseTest.response": {
"type": "object",
"properties": {
"libVersion": {
"type": "string"
},
"libVersionNumber": {
"type": "integer"
},
"sourceID": {
"type": "string"
},
"success": {
"type": "boolean"
}
}
},
"handler.Expand.response": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.ShortCompatMessage"
},
"message": {
"type": "string"
},
"success": {
"type": "string"
}
}
},
"handler.Health.response": {
"type": "object",
"properties": {
"status": {
"type": "string"
}
}
},
"handler.Info.response": {
"type": "object",
"properties": {
"fcm_token_set": {
"type": "boolean"
},
"is_pro": {
"type": "string"
},
"message": {
"type": "string"
},
"quota": {
"type": "string"
},
"quota_max": {
"type": "string"
},
"success": {
"type": "string"
},
"unack_count": {
"type": "integer"
},
"user_id": {
"type": "string"
},
"user_key": {
"type": "string"
}
}
},
"handler.Register.response": {
"type": "object",
"properties": {
"is_pro": {
"type": "string"
},
"message": {
"type": "string"
},
"quota": {
"type": "string"
},
"quota_max": {
"type": "string"
},
"success": {
"type": "string"
},
"user_id": {
"type": "string"
},
"user_key": {
"type": "string"
}
}
},
"handler.Requery.response": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/models.CompatMessage"
}
},
"message": {
"type": "string"
},
"success": {
"type": "string"
}
}
},
"handler.Send.response": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"success": {
"type": "string"
}
}
},
"handler.Update.response": {
"type": "object",
"properties": {
"is_pro": {
"type": "string"
},
"message": {
"type": "string"
},
"quota": {
"type": "string"
},
"quota_max": {
"type": "string"
},
"success": {
"type": "string"
},
"user_id": {
"type": "string"
},
"user_key": {
"type": "string"
}
}
},
"handler.Upgrade.response": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.ShortCompatMessage"
},
"message": {
"type": "string"
},
"success": {
"type": "string"
}
}
},
"handler.pingResponse": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/handler.pingResponseInfo"
},
"message": {
"type": "string"
}
}
},
"handler.pingResponseInfo": {
"type": "object",
"properties": {
"addr": {
"type": "string"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"method": {
"type": "string"
},
"request": {
"type": "string"
},
"uri": {
"type": "string"
}
}
},
"models.CompatMessage": {
"type": "object",
"properties": {
"body": {
"type": "string"
},
"priority": {
"type": "integer"
},
"scn_msg_id": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"title": {
"type": "string"
},
"usr_msg_id": {
"type": "string"
}
}
},
"models.ShortCompatMessage": {
"type": "object",
"properties": {
"body": {
"type": "string"
},
"priority": {
"type": "integer"
},
"scn_msg_id": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"title": {
"type": "string"
},
"trimmed": {
"type": "boolean"
},
"usr_msg_id": {
"type": "string"
}
}
}
}
}

551
server/swagger/swagger.yaml Normal file
View File

@ -0,0 +1,551 @@
basePath: /api/
definitions:
ginresp.errBody:
properties:
message:
type: string
success:
type: boolean
type: object
ginresp.internAPIError:
properties:
errid:
type: integer
message:
type: string
success:
type: boolean
type: object
ginresp.sendAPIError:
properties:
errhighlight:
type: integer
error:
type: integer
message:
type: string
success:
type: boolean
type: object
handler.Ack.response:
properties:
message:
type: string
new_ack:
type: integer
prev_ack:
type: integer
success:
type: string
type: object
handler.DatabaseTest.response:
properties:
libVersion:
type: string
libVersionNumber:
type: integer
sourceID:
type: string
success:
type: boolean
type: object
handler.Expand.response:
properties:
data:
$ref: '#/definitions/models.ShortCompatMessage'
message:
type: string
success:
type: string
type: object
handler.Health.response:
properties:
status:
type: string
type: object
handler.Info.response:
properties:
fcm_token_set:
type: boolean
is_pro:
type: string
message:
type: string
quota:
type: string
quota_max:
type: string
success:
type: string
unack_count:
type: integer
user_id:
type: string
user_key:
type: string
type: object
handler.Register.response:
properties:
is_pro:
type: string
message:
type: string
quota:
type: string
quota_max:
type: string
success:
type: string
user_id:
type: string
user_key:
type: string
type: object
handler.Requery.response:
properties:
count:
type: integer
data:
items:
$ref: '#/definitions/models.CompatMessage'
type: array
message:
type: string
success:
type: string
type: object
handler.Send.response:
properties:
message:
type: string
success:
type: string
type: object
handler.Update.response:
properties:
is_pro:
type: string
message:
type: string
quota:
type: string
quota_max:
type: string
success:
type: string
user_id:
type: string
user_key:
type: string
type: object
handler.Upgrade.response:
properties:
data:
$ref: '#/definitions/models.ShortCompatMessage'
message:
type: string
success:
type: string
type: object
handler.pingResponse:
properties:
info:
$ref: '#/definitions/handler.pingResponseInfo'
message:
type: string
type: object
handler.pingResponseInfo:
properties:
addr:
type: string
headers:
additionalProperties:
items:
type: string
type: array
type: object
method:
type: string
request:
type: string
uri:
type: string
type: object
models.CompatMessage:
properties:
body:
type: string
priority:
type: integer
scn_msg_id:
type: string
timestamp:
type: integer
title:
type: string
usr_msg_id:
type: string
type: object
models.ShortCompatMessage:
properties:
body:
type: string
priority:
type: integer
scn_msg_id:
type: string
timestamp:
type: integer
title:
type: string
trimmed:
type: boolean
usr_msg_id:
type: string
type: object
host: scn.blackforestbytes.com
info:
contact: {}
description: API for SCN
title: SimpleCloudNotifier API
version: "2.0"
paths:
/ack.php:
get:
parameters:
- description: the user_id
in: query
name: user_id
required: true
type: string
- description: the user_key
in: query
name: user_key
required: true
type: string
- description: the message id
in: query
name: scn_msg_id
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Ack.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.internAPIError'
summary: Acknowledge that a message was received
/db-test:
get:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.DatabaseTest.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.errBody'
/expand.php:
get:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Expand.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.internAPIError'
summary: Get a whole (potentially truncated) message
/health:
get:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Health.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.errBody'
/info.php:
get:
parameters:
- description: the user_id
in: query
name: user_id
required: true
type: string
- description: the user_key
in: query
name: user_key
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Info.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.internAPIError'
summary: Get information about the current user
/ping:
delete:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.pingResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.errBody'
get:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.pingResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.errBody'
patch:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.pingResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.errBody'
post:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.pingResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.errBody'
put:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.pingResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.errBody'
/register.php:
get:
parameters:
- description: the (android) fcm token
in: query
name: fcm_token
required: true
type: string
- description: if the user is a paid account
enum:
- "true"
- "false"
in: query
name: pro
required: true
type: string
- description: the (android) IAP token
in: query
name: pro_token
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Register.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.internAPIError'
summary: Register a new account
/requery.php:
get:
parameters:
- description: the user_id
in: query
name: user_id
required: true
type: string
- description: the user_key
in: query
name: user_key
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Requery.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.internAPIError'
summary: Return all not-acknowledged messages
/send.php:
post:
description: all aeguments can either be supplied in the query or in the json
body
parameters:
- description: the user_id
in: query
name: user_id
required: true
type: string
- description: the user_key
in: query
name: user_key
required: true
type: string
- description: The message title
in: query
name: title
required: true
type: string
- description: The message content
in: query
name: content
type: string
- description: The message priority
in: query
name: priority
type: string
- description: The message idempotency id
in: query
name: msg_id
type: string
- description: The message timestamp
in: query
name: timestamp
type: string
- description: the user_id
in: body
name: user_id
required: true
schema:
type: string
- description: the user_key
in: body
name: user_key
required: true
schema:
type: string
- description: The message title
in: body
name: title
required: true
schema:
type: string
- description: The message content
in: body
name: content
schema:
type: string
- description: The message priority
in: body
name: priority
schema:
type: string
- description: The message idempotency id
in: body
name: msg_id
schema:
type: string
- description: The message timestamp
in: body
name: timestamp
schema:
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Send.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.sendAPIError'
summary: Send a message
/update.php:
get:
parameters:
- description: the user_id
in: query
name: user_id
required: true
type: string
- description: the user_key
in: query
name: user_key
required: true
type: string
- description: the (android) fcm token
in: query
name: fcm_token
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Update.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.internAPIError'
summary: Set the fcm-token (android)
/upgrade.php:
get:
parameters:
- description: the user_id
in: query
name: user_id
required: true
type: string
- description: the user_key
in: query
name: user_key
required: true
type: string
- description: if the user is a paid account
enum:
- "true"
- "false"
in: query
name: pro
required: true
type: string
- description: the (android) IAP token
in: query
name: pro_token
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Upgrade.response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.internAPIError'
summary: Upgrade a free account to a paid account
swagger: "2.0"