initial commit

This commit is contained in:
ston1th 2023-03-14 01:43:04 +01:00
commit 60fb40d61a
34 changed files with 2571 additions and 0 deletions

103
pkg/api/types/approver.go Normal file
View file

@ -0,0 +1,103 @@
// Copyright (C) 2023 Marius Schellenberger
package types
import (
"sync"
"time"
"git.giftfish.de/ston1th/keyctl/pkg/key"
"golang.org/x/exp/maps"
)
const maxSize = 10
type ApprovalMap map[string]Approval
type Approver struct {
// protects m
mu sync.RWMutex
m ApprovalMap
}
func NewApprover() *Approver {
return &Approver{
m: make(ApprovalMap),
}
}
func (a *Approver) New(id, name string) (aid string, err error) {
a.mu.Lock()
defer a.mu.Unlock()
size := len(a.m)
if size > maxSize {
err = ErrTooManyApprovals
return
}
aid, err = key.GenerateID()
if err != nil {
return
}
a.m[aid] = Approval{
ID: id,
Name: name,
Created: time.Now().Unix(),
}
return
}
func (a *Approver) List() (m ApprovalMap) {
a.mu.RLock()
m = maps.Clone(a.m)
a.mu.RUnlock()
return
}
func (a *Approver) Status(id, aid string) (s Status) {
a.mu.RLock()
if app, ok := a.m[aid]; ok && app.ID == id {
s = app.Status
}
a.mu.RUnlock()
return
}
func (a *Approver) Update(aid string, s Status) {
a.mu.Lock()
if _, ok := a.m[aid]; ok {
a.m[aid].Status = s
}
a.mu.Unlock()
}
type Approval struct {
ID string
Name string
Created int64
Status Status
}
type Status int
const (
Pending Status = iota
Approved
Rejected
)
func (s Status) String() string {
switch s {
case Pending:
return "pending"
case Approved:
return "approved"
case Rejected:
return "rejected"
}
return "[invalid]"
}
type Route struct {
Path string
Handler CtxHandler
Methods []string
}

138
pkg/api/types/context.go Normal file
View file

@ -0,0 +1,138 @@
// Copyright (C) 2023 Marius Schellenberger
package types
import (
"encoding/json"
"io"
"net"
"net/http"
"github.com/go-logr/logr"
"github.com/gorilla/mux"
"git.giftfish.de/ston1th/keyctl/pkg/db"
)
type ContextData struct {
DB *db.DB
}
func NewContext(w http.ResponseWriter, r *http.Request, data *ContextData, log logr.Logger) *Context {
h := w.Header()
h.Set("Content-Type", "application/json")
return &Context{
Code: http.StatusOK,
Request: r,
Response: w,
Data: data,
Log: log,
}
}
// Context is the context object shared between http handlers
type Context struct {
Code int
Request *http.Request
Response http.ResponseWriter
Data *ContextData
Log logr.Logger
}
// Method returns the request method
func (c *Context) Method() string {
return c.Request.Method
}
// Path returns the request URL path
func (c *Context) Path() string {
return c.Request.URL.Path
}
// Header returns the given http header
func (c *Context) Header(name string) string {
return c.Request.Header.Get(name)
}
// SetHeader sets the given http header
func (c *Context) SetHeader(name, value string) {
h := c.Response.Header()
h.Set(name, value)
}
// Form returns the given form value
func (c *Context) Form(name string) string {
return c.Request.FormValue(name)
}
func (c *Context) ClientIP() (host string) {
host, _, _ = net.SplitHostPort(c.Request.RemoteAddr)
return
}
// Var returns the given url variable
func (c *Context) Var(name string) (ret string) {
ret, _ = mux.Vars(c.Request)[name]
return
}
func (c *Context) log() {
c.Log.Info("access",
"addr", c.Request.RemoteAddr,
"method", c.Request.Method,
"path", c.Request.URL.Path,
"code", c.Code,
)
}
func (c *Context) NotFound() {
c.Err(ErrNotFound)
}
// OK is a empty HTTP 200 JSON response
func (c *Context) OK() {
c.log()
}
func (c *Context) ReadBody() (b []byte, err error) {
b, err = io.ReadAll(c.Request.Body)
c.Request.Body.Close()
return
}
func (c *Context) Body(b []byte) {
c.Response.Write(b)
c.log()
}
// JSON is a json response
func (c *Context) JSON(v any) {
err := json.NewEncoder(c.Response).Encode(v)
if err != nil {
c.Err(ErrISE)
return
}
c.log()
}
func (c *Context) Redirect(url string, code int) {
c.Code = code
http.Redirect(c.Response, c.Request, url, code)
c.log()
}
// Err is a HTTPErr wrapper for the custom Error type
func (c *Context) Err(err Error) {
c.HTTPErr(err.JSON(), err.Code())
}
// HTTPErr is a http.Error wrapper
func (c *Context) HTTPErr(err string, code int) {
c.Code = code
http.Error(c.Response, err, code)
c.log()
}
type CtxHandler func(*Context)

55
pkg/api/types/error.go Normal file
View file

@ -0,0 +1,55 @@
// Copyright (C) 2023 Marius Schellenberger
package types
import (
"net/http"
"strconv"
)
// Error interface is a custom api error
type Error interface {
Error() string
JSON() string
Status() int
}
// Err implements the Error interface
type Err struct {
err string
status int
}
// Error returns the error string
func (a Err) Error() string {
return a.err
}
// Error returns the error string as JSON format
func (a Err) JSON() string {
return `{"error":"` + a.err + `","status":` + strconv.Itoa(a.status) + `}`
}
// Status returns the HTTP status code
func (a Err) Status() int {
return a.status
}
func newError(err string, status int) Error {
return Err{
err: err,
status: status,
}
}
var (
ErrInvalidAPIRoute = newError("invalid api route", http.StatusNotFound)
ErrBadreq = newError("bad request", http.StatusBadRequest)
ErrForbidden = newError("forbidden", http.StatusForbidden)
ErrNotFound = newError("not found", http.StatusNotFound)
ErrKeyNotFound = newError("key not found", http.StatusNotFound)
ErrReqNotFound = newError("key request not found", http.StatusNotFound)
ErrTooManyApprovals = newError("too many approvals pending", http.StatusConflict)
ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed)
ErrISE = newError("internal server error", http.StatusInternalServerError)
)

9
pkg/api/types/types.go Normal file
View file

@ -0,0 +1,9 @@
// Copyright (C) 2023 Marius Schellenberger
package types
type Route struct {
Path string
Handler CtxHandler
Methods []string
}