Skip to content

Commit

Permalink
all: cleanup a number of things, including error handling and documen…
Browse files Browse the repository at this point in the history
…tation (#6)

* sips: clean up error handling a bit

* internal/log: return an error from `Errorf()`

* cmd/sips: use new error handling stuff from `internal/log` and `sips`

* all: better documentation
  • Loading branch information
DeedleFake authored Jun 10, 2021
1 parent fce94d4 commit 4148bc6
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 85 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
sips
SIPS
====

[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/DeedleFake/sips)](https://pkg.go.dev/github.com/DeedleFake/sips)
[![Go Report Card](https://goreportcard.com/badge/github.com/DeedleFake/sips)](https://goreportcard.com/report/github.com/DeedleFake/sips)

*Disclaimer: sips is still in early development and is not guaranteed to do much of anything. Although it should function for basic usage, expect bugs, and definitely don't use it for anything that has money associated with it.*
*Disclaimer: SIPS is still in early development and is not guaranteed to do much of anything. Although it should function for basic usage, expect bugs, and definitely don't use it for anything that has money associated with it.*

sips is a Simple IPFS Pinning Service. It does the bare minimum necessary to present a functional [pinning service][pinning-service-api].
SIPS is a Simple IPFS Pinning Service. It does the bare minimum necessary to present a functional [pinning service][pinning-service-api].

Setup
-----

After installation, sips will have no users or tokens in its database. To create some, use the `sipsctl` utility that is provided:
After installation, SIPS will have no users or tokens in its database. To create some, use the `sipsctl` utility that is provided:

```bash
$ sipsctl users add whateverUsernameYouWant
Expand Down
41 changes: 41 additions & 0 deletions cmd/sips/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import "net/http"

type statusError struct {
StatusCode int
Err error
}

func Unauthorized(err error) error {
return statusError{
StatusCode: http.StatusUnauthorized,
Err: err,
}
}

func NotFound(err error) error {
return statusError{
StatusCode: http.StatusNotFound,
Err: err,
}
}

func BadRequest(err error) error {
return statusError{
StatusCode: http.StatusBadRequest,
Err: err,
}
}

func (err statusError) Error() string {
return err.Err.Error()
}

func (err statusError) Unwrap() error {
return err.Err
}

func (err statusError) Status() int {
return err.StatusCode
}
89 changes: 37 additions & 52 deletions cmd/sips/pinhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"io/fs"
"strconv"
"time"

Expand All @@ -23,15 +22,13 @@ type PinHandler struct {
func (h PinHandler) Pins(ctx context.Context, query sips.PinQuery) ([]sips.PinStatus, error) {
tx, err := h.DB.Begin(false)
if err != nil {
log.Errorf("begin transaction: %v", err)
return nil, err
return nil, log.Errorf("begin transaction: %w", err)
}
defer tx.Rollback()

user, err := auth(ctx, tx)
if err != nil {
log.Errorf("authenticate: %v", err)
return nil, err
return nil, Unauthorized(log.Errorf("authenticate: %w", err))
}

selector := tx.Select(&queryMatcher{Query: query})
Expand All @@ -42,8 +39,7 @@ func (h PinHandler) Pins(ctx context.Context, query sips.PinQuery) ([]sips.PinSt
var dbpins []dbs.Pin
err = selector.OrderBy("Created").Find(&dbpins)
if (err != nil) && (!errors.Is(err, storm.ErrNotFound)) {
log.Errorf("find pins for %v: %v", user.Name, err)
return nil, err
return nil, log.Errorf("find pins for %v: %w", user.Name, err)
}

pins := make([]sips.PinStatus, 0, len(dbpins))
Expand All @@ -65,15 +61,13 @@ func (h PinHandler) Pins(ctx context.Context, query sips.PinQuery) ([]sips.PinSt
func (h PinHandler) AddPin(ctx context.Context, pin sips.Pin) (sips.PinStatus, error) {
tx, err := h.DB.Begin(true)
if err != nil {
log.Errorf("begin transaction: %v", err)
return sips.PinStatus{}, err
return sips.PinStatus{}, log.Errorf("begin transaction: %w", err)
}
defer tx.Rollback()

user, err := auth(ctx, tx)
if err != nil {
log.Errorf("authenticate: %v", err)
return sips.PinStatus{}, err
return sips.PinStatus{}, Unauthorized(log.Errorf("authenticate: %w", err))
}

dbpin := dbs.Pin{
Expand All @@ -84,8 +78,7 @@ func (h PinHandler) AddPin(ctx context.Context, pin sips.Pin) (sips.PinStatus, e
}
err = tx.Save(&dbpin)
if err != nil {
log.Errorf("save pin %q: %v", pin.CID, err)
return sips.PinStatus{}, err
return sips.PinStatus{}, log.Errorf("save pin %q: %w", pin.CID, err)
}

if len(pin.Origins) != 0 {
Expand All @@ -96,13 +89,12 @@ func (h PinHandler) AddPin(ctx context.Context, pin sips.Pin) (sips.PinStatus, e

_, err = h.IPFS.PinAdd(ctx, pin.CID)
if err != nil {
log.Errorf("add pin %v: %v", pin.CID, err)
return sips.PinStatus{}, err
return sips.PinStatus{}, log.Errorf("add pin %v: %w", pin.CID, err)
}

id, err := h.IPFS.ID(ctx)
if err != nil {
log.Errorf("get IPFS id: %v", err)
log.Errorf("get IPFS id: %w", err)
// Purposefully don't return here.
}

Expand All @@ -118,33 +110,32 @@ func (h PinHandler) AddPin(ctx context.Context, pin sips.Pin) (sips.PinStatus, e
func (h PinHandler) GetPin(ctx context.Context, requestID string) (sips.PinStatus, error) {
pinID, err := strconv.ParseUint(requestID, 16, 64)
if err != nil {
log.Errorf("parse request ID %q: %v", requestID, err)
return sips.PinStatus{}, err
return sips.PinStatus{}, BadRequest(log.Errorf("parse request ID %q: %w", requestID, err))
}

tx, err := h.DB.Begin(false)
if err != nil {
log.Errorf("begin transaction: %v", err)
return sips.PinStatus{}, err
return sips.PinStatus{}, log.Errorf("begin transaction: %w", err)
}
defer tx.Rollback()

user, err := auth(ctx, tx)
if err != nil {
log.Errorf("authenticate: %v", err)
return sips.PinStatus{}, err
return sips.PinStatus{}, Unauthorized(log.Errorf("authenticate: %w", err))
}

var pin dbs.Pin
err = tx.One("ID", pinID, &pin)
if err != nil {
log.Errorf("find pin %v: %v", requestID, err)
err = log.Errorf("find pin %v: %w", requestID, err)
if errors.Is(err, storm.ErrNotFound) {
err = NotFound(err)
}
return sips.PinStatus{}, err
}

if pin.User != user.ID {
log.Errorf("user %v is not authorized to see pin %v", user.Name, requestID)
return sips.PinStatus{}, fs.ErrPermission
return sips.PinStatus{}, NotFound(log.Errorf("find pin %v: %w", requestID, storm.ErrNotFound))
}

return sips.PinStatus{
Expand All @@ -161,42 +152,40 @@ func (h PinHandler) GetPin(ctx context.Context, requestID string) (sips.PinStatu
func (h PinHandler) UpdatePin(ctx context.Context, requestID string, pin sips.Pin) (sips.PinStatus, error) {
pinID, err := strconv.ParseUint(requestID, 16, 64)
if err != nil {
log.Errorf("parse request ID: %q: %v", requestID, err)
return sips.PinStatus{}, err
return sips.PinStatus{}, BadRequest(log.Errorf("parse request ID: %q: %w", requestID, err))
}

tx, err := h.DB.Begin(true)
if err != nil {
log.Errorf("begin transaction: %v", err)
return sips.PinStatus{}, err
return sips.PinStatus{}, log.Errorf("begin transaction: %w", err)
}
defer tx.Rollback()

user, err := auth(ctx, tx)
if err != nil {
log.Errorf("authenticate: %v", err)
return sips.PinStatus{}, err
return sips.PinStatus{}, NotFound(log.Errorf("authenticate: %w", err))
}

var dbpin dbs.Pin
err = tx.One("ID", pinID, &dbpin)
if err != nil {
log.Errorf("find pin %v: %v", requestID, err)
err = log.Errorf("find pin %v: %w", requestID, err)
if errors.Is(err, storm.ErrNotFound) {
err = NotFound(err)
}
return sips.PinStatus{}, err
}
oldCID := dbpin.CID

if dbpin.User != user.ID {
log.Errorf("user %v not allowed to update pin %v", user.Name, requestID)
return sips.PinStatus{}, fs.ErrPermission
return sips.PinStatus{}, NotFound(log.Errorf("find pin %v: %w", requestID, storm.ErrNotFound))
}

dbpin.Name = pin.Name
dbpin.CID = pin.CID
err = tx.Update(&dbpin)
if err != nil {
log.Errorf("update pin %v: %v", requestID, err)
return sips.PinStatus{}, err
return sips.PinStatus{}, log.Errorf("update pin %v: %w", requestID, err)
}

if len(pin.Origins) != 0 {
Expand All @@ -207,13 +196,12 @@ func (h PinHandler) UpdatePin(ctx context.Context, requestID string, pin sips.Pi

_, err = h.IPFS.PinUpdate(ctx, oldCID, pin.CID, false)
if err != nil {
log.Errorf("add pin %v: %v", pin.CID, err)
return sips.PinStatus{}, err
return sips.PinStatus{}, log.Errorf("add pin %v: %w", pin.CID, err)
}

id, err := h.IPFS.ID(ctx)
if err != nil {
log.Errorf("get IPFS id: %v", err)
log.Errorf("get IPFS id: %w", err)
// Purposefully don't return here.
}

Expand All @@ -232,45 +220,42 @@ func (h PinHandler) UpdatePin(ctx context.Context, requestID string, pin sips.Pi
func (h PinHandler) DeletePin(ctx context.Context, requestID string) error {
pinID, err := strconv.ParseUint(requestID, 16, 64)
if err != nil {
log.Errorf("parse request ID %q: %v", requestID, err)
return err
return BadRequest(log.Errorf("parse request ID %q: %w", requestID, err))
}

tx, err := h.DB.Begin(true)
if err != nil {
log.Errorf("begin transaction: %v", err)
return err
return log.Errorf("begin transaction: %w", err)
}
defer tx.Rollback()

user, err := auth(ctx, tx)
if err != nil {
log.Errorf("authenticate: %v", err)
return err
return Unauthorized(log.Errorf("authenticate: %w", err))
}

var pin dbs.Pin
err = tx.One("ID", pinID, &pin)
if err != nil {
log.Errorf("find pin %v: %v", requestID, err)
err = log.Errorf("find pin %v: %w", requestID, err)
if errors.Is(err, storm.ErrNotFound) {
err = NotFound(err)
}
return err
}

if pin.User != user.ID {
log.Errorf("user %v is not authorized to delete pin %v", user.Name, requestID)
return fs.ErrPermission // TODO: That's just not right.
return NotFound(log.Errorf("find pin %v: %w", requestID, storm.ErrNotFound))
}

err = tx.DeleteStruct(&pin)
if err != nil {
log.Errorf("delete pin %v: %v", requestID, err)
return err
return log.Errorf("delete pin %v: %w", requestID, err)
}

_, err = h.IPFS.PinRm(ctx, pin.CID)
if err != nil {
log.Errorf("unpin %v: %v", pin.CID, err)
return err
return log.Errorf("unpin %v: %w", pin.CID, err)
}

return tx.Commit()
Expand Down
1 change: 1 addition & 0 deletions cmd/sips/sips.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// sips is the primary implementation of a simple pinning service daemon.
package main

import (
Expand Down
1 change: 1 addition & 0 deletions cmd/sipsctl/sipsctl.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// sipsctl is a simple utility for administrating the database used by SIPS.
package main

import (
Expand Down
2 changes: 2 additions & 0 deletions dbs/dbs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/asdine/storm"
)

// Open opens the database, initializing all of the required top-level
// buckets.
func Open(path string) (*storm.DB, error) {
db, err := storm.Open(path)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions dbs/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import (
"time"
)

// User reprsents a user in the database.
type User struct {
ID uint64 `storm:"increment"`
Created time.Time
Name string `storm:"index,unique"`
}

// Token represents an auth token in the database.
type Token struct {
ID string
Created time.Time
User uint64 `storm:"index"`
}

// Pin represents a pin entry in the database.
type Pin struct {
ID uint64 `storm:"increment"`
Created time.Time
Expand Down
8 changes: 8 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Package sips provides structures for implementing an IPFS pinning service.
//
// The primary purpose of this package is to allow a user to create an
// IPFS pinning service with minimal effort. The package is based
// around the PinHandler interface. An implementation of this
// interface can be passed to the Handler function in order to create
// an HTTP handler that serves a valid pinning service.
package sips
Loading

0 comments on commit 4148bc6

Please sign in to comment.