Skip to content

Commit

Permalink
Integrate API with Redirector
Browse files Browse the repository at this point in the history
  • Loading branch information
nownabe committed Aug 19, 2023
1 parent aa64253 commit e50d7eb
Show file tree
Hide file tree
Showing 21 changed files with 2,330 additions and 244 deletions.
12 changes: 0 additions & 12 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"cloud.google.com/go/firestore"
"github.com/nownabe/golink/go/clog"
"github.com/nownabe/golink/go/errors"
"github.com/rs/cors"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
Expand Down Expand Up @@ -98,14 +97,3 @@ func (a *api) serve(ctx context.Context) error {

return nil
}

func (a *api) cors(h http.Handler) http.Handler {
c := cors.New(cors.Options{
AllowedOrigins: a.allowedOrigins,
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowCredentials: true,
AllowedHeaders: []string{"*"},
})

return c.Handler(h)
}
3 changes: 0 additions & 3 deletions api/dto_internal_test.go

This file was deleted.

193 changes: 0 additions & 193 deletions api/repository.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package api

import (
"context"
"time"

"cloud.google.com/go/firestore"
"github.com/nownabe/golink/go/errors"
"google.golang.org/api/iterator"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

const collectionName = "golinks"
Expand All @@ -18,190 +12,3 @@ var errDocumentNotFound = errors.NewWithoutStack("not found")
type repository struct {
firestore *firestore.Client
}

func (r *repository) Transaction(
ctx context.Context,
f func(ctx context.Context, tx *firestore.Transaction) error,
) error {
return r.firestore.RunTransaction(ctx, f)
}

func (r *repository) TxExists(ctx context.Context, tx *firestore.Transaction, name string) (bool, error) {
col := r.collection()
doc := col.Doc(nameToID(name))

s, err := tx.Get(doc)
if status.Code(err) == codes.NotFound {
return false, nil
}
if err != nil {
return false, errors.Wrapf(err, "failed to get %s", doc.Path)
}

return s.Exists(), nil
}

func (r *repository) Get(ctx context.Context, name string) (*dto, error) {
col := r.collection()
doc := col.Doc(nameToID(name))

s, err := doc.Get(ctx)
if status.Code(err) == codes.NotFound {
return nil, errors.Wrapf(errDocumentNotFound, "%s not found", doc.Path)
}

if err != nil {
return nil, errors.Wrapf(err, "failed to get %s", doc.Path)
}
var o dto
if err := s.DataTo(&o); err != nil {
return nil, errors.Wrapf(err, "failed to populate %s", doc.Path)
}

return &o, nil
}

func (r *repository) TxGet(ctx context.Context, tx *firestore.Transaction, name string) (*dto, error) {
col := r.collection()
doc := col.Doc(nameToID(name))

s, err := tx.Get(doc)
if status.Code(err) == codes.NotFound {
return nil, errors.Wrapf(errDocumentNotFound, "%s not found", doc.Path)
}
if err != nil {
return nil, errors.Wrapf(err, "failed to get %s", doc.Path)
}

var o dto
if err := s.DataTo(&o); err != nil {
return nil, errors.Wrapf(err, "failed to populate %s", doc.Path)
}

return &o, nil
}

func (r *repository) TxCreate(ctx context.Context, tx *firestore.Transaction, dto *dto) error {
col := r.collection()
doc := col.Doc(dto.ID())

dto.CreatedAt = time.Now()
dto.UpdatedAt = time.Now()

if err := tx.Create(doc, dto); err != nil {
return errors.Wrapf(err, "failed to create %s", doc.Path)
}

return nil
}

func (r *repository) ListByOwner(ctx context.Context, owner string) ([]*dto, error) {
col := r.collection()
iter := col.Where("owners", "array-contains", owner).Documents(ctx)
defer iter.Stop()

var dtos []*dto
for {
s, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, errors.Wrapf(err, "failed to iterate %s", col.Path)
}

var o dto
if err := s.DataTo(&o); err != nil {
return nil, errors.Wrapf(err, "failed to populate %s", s.Ref.Path)
}

dtos = append(dtos, &o)
}

return dtos, nil
}

func (r *repository) ListByURL(ctx context.Context, url string) ([]*dto, error) {
col := r.collection()
iter := col.Where("url", "==", url).Documents(ctx)
defer iter.Stop()

var dtos []*dto
for {
s, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, errors.Wrapf(err, "failed to iterate %s", col.Path)
}

var o dto
if err := s.DataTo(&o); err != nil {
return nil, errors.Wrapf(err, "failed to populate %s", s.Ref.Path)
}

dtos = append(dtos, &o)
}

return dtos, nil
}

func (r *repository) TxUpdate(ctx context.Context, tx *firestore.Transaction, dto *dto) error {
col := r.collection()
doc := col.Doc(dto.ID())

dto.UpdatedAt = time.Now()

if err := tx.Update(doc, []firestore.Update{
{Path: "url", Value: dto.URL},
{Path: "updated_at", Value: dto.UpdatedAt},
}); err != nil {
return errors.Wrapf(err, "failed to update %s", doc.Path)
}

return nil
}

func (r *repository) TxDelete(ctx context.Context, tx *firestore.Transaction, name string) error {
col := r.collection()
doc := col.Doc(nameToID(name))

if err := tx.Delete(doc); err != nil {
return errors.Wrapf(err, "failed to delete %s", doc.Path)
}

return nil
}

func (r *repository) TxAddOwner(ctx context.Context, tx *firestore.Transaction, name string, owner string) error {
col := r.collection()
doc := col.Doc(nameToID(name))

if err := tx.Update(doc, []firestore.Update{
{Path: "owners", Value: firestore.ArrayUnion(owner)},
{Path: "updated_at", Value: time.Now()},
}); err != nil {
return errors.Wrapf(err, "failed to update %s", doc.Path)
}

return nil
}

func (r *repository) TxRemoveOwner(ctx context.Context, tx *firestore.Transaction, name string, owner string) error {
col := r.collection()
doc := col.Doc(nameToID(name))

if err := tx.Update(doc, []firestore.Update{
{Path: "owners", Value: firestore.ArrayRemove(owner)},
{Path: "updated_at", Value: time.Now()},
}); err != nil {
return errors.Wrapf(err, "failed to update %s", doc.Path)
}

return nil
}

func (r *repository) collection() *firestore.CollectionRef {
return r.firestore.Collection(collectionName)
}
File renamed without changes.
24 changes: 7 additions & 17 deletions api/api_handler.go → backend/api_handler.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package api
package backend

import (
"net/http"
Expand All @@ -7,16 +7,10 @@ import (
"github.com/bufbuild/connect-go"
"github.com/nownabe/golink/api/gen/golink/v1/golinkv1connect"

"github.com/nownabe/golink/api/interceptor"
"github.com/nownabe/golink/backend/interceptor"
)

type APIConfig struct {
Prefix string
Debug bool
DummyUser string
}

func newAPIHandler(cfg *APIConfig, repo *repository) http.Handler {
func newAPIHandler(repo *repository, debug bool, dummyUser string) http.Handler {
// TODO: Move interceptors to route http middlewares
interceptors := []connect.Interceptor{
// outermost
Expand All @@ -28,8 +22,8 @@ func newAPIHandler(cfg *APIConfig, repo *repository) http.Handler {
// innermost
}

if cfg.DummyUser != "" {
u := strings.Split(cfg.DummyUser, ":")
if dummyUser != "" {
u := strings.Split(dummyUser, ":")
interceptors = append([]connect.Interceptor{interceptor.NewDummyUser(u[0], u[1])}, interceptors...)
}

Expand All @@ -40,7 +34,7 @@ func newAPIHandler(cfg *APIConfig, repo *repository) http.Handler {
svc := &golinkService{repo}
grpcHandler.Handle(golinkv1connect.NewGolinkServiceHandler(svc, interceptorsOpt))

if cfg.Debug {
if debug {
grpcHandler.Handle(golinkv1connect.NewDebugServiceHandler(&debugService{}, interceptorsOpt))
}

Expand All @@ -49,11 +43,7 @@ func newAPIHandler(cfg *APIConfig, repo *repository) http.Handler {

grpcHandler.HandleFunc("/", http.NotFound)

// https://connectrpc.com/docs/go/routing#prefixing-routes
prefixedMux := http.NewServeMux()
prefixedMux.Handle(cfg.Prefix+"/", http.StripPrefix(cfg.Prefix, grpcHandler))

return prefixedMux
return grpcHandler
}

func healthz(w http.ResponseWriter, _ *http.Request) {
Expand Down
34 changes: 32 additions & 2 deletions backend/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"cloud.google.com/go/firestore"
"github.com/nownabe/golink/go/clog"
"github.com/nownabe/golink/go/errors"
"github.com/rs/cors"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)

const (
Expand All @@ -25,33 +28,49 @@ type App interface {
// New returns a new backend app.
func New(
port string,
allowedOrigins []string,
apiPrefix string,
consolePrefix string,
firestoreClient *firestore.Client,
debug bool,
dummyUser string,
) App {
repo := &repository{firestoreClient}

return &app{
port: port,
port: port,
allowedOrigins: allowedOrigins,
apiPrefix: apiPrefix,
redirectHandler: &redirectHandler{
consolePrefix: consolePrefix,
repo: repo,
},
apiHandler: newAPIHandler(repo, debug, dummyUser),
}
}

type app struct {
port string
allowedOrigins []string
apiPrefix string
redirectHandler http.Handler
apiHandler http.Handler
}

func (a *app) Run(ctx context.Context) error {
return a.serve(ctx)
}

func (a *app) serve(ctx context.Context) error {
mux := http.NewServeMux()
// https://connectrpc.com/docs/go/routing#prefixing-routes
mux.Handle(a.apiPrefix+"/", http.StripPrefix(a.apiPrefix, a.apiHandler))
mux.Handle("/", a.redirectHandler)

h2s := &http2.Server{}
s := &http.Server{
Addr: ":" + a.port,
Handler: a.redirectHandler,
Handler: a.cors(h2c.NewHandler(mux, h2s)),
ReadHeaderTimeout: readHeaderTimeoutSeconds * time.Second,
}

Expand Down Expand Up @@ -86,3 +105,14 @@ func (a *app) serve(ctx context.Context) error {

return nil
}

func (a *app) cors(h http.Handler) http.Handler {
c := cors.New(cors.Options{
AllowedOrigins: a.allowedOrigins,
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowCredentials: true,
AllowedHeaders: []string{"*"},
})

return c.Handler(h)
}
Loading

0 comments on commit e50d7eb

Please sign in to comment.