<commit_before>package server

import (
	"database/sql"
	"io"
	"net/http"
	"strconv"
	"strings"

	goji "goji.io"
	"goji.io/pat"

	"dmitryfrank.com/geekmarks/server/cptr"
	hh "dmitryfrank.com/geekmarks/server/httphelper"
	"dmitryfrank.com/geekmarks/server/middleware"
	"dmitryfrank.com/geekmarks/server/storage"
	"github.com/golang/glog"
	"github.com/juju/errors"
)

type GMServer struct {
	si storage.Storage
}

func New(si storage.Storage) (*GMServer, error) {
	return &GMServer{
		si: si,
	}, nil
}

func (gm *GMServer) CreateHandler() (http.Handler, error) {
	rRoot := goji.NewMux()
	rRoot.Use(middleware.MakeLogger())

	rAPI := goji.SubMux()
	rRoot.Handle(pat.New("/api/*"), rAPI)
	{
		rAPI.Use(hh.MakeDesiredContentTypeMiddleware("application/json"))
		// We use authnMiddleware here and not on the root router above, since we
		// need hh.MakeDesiredContentTypeMiddleware to go before it.
		rAPI.Use(gm.authnMiddleware)

		rAPIUsers := goji.SubMux()
		rAPI.Handle(pat.New("/users/:userid/*"), rAPIUsers)
		{
			gm.setupUserAPIEndpoints(rAPIUsers, gm.getUserFromURLParam)
		}

		rAPIMy := goji.SubMux()
		rAPI.Handle(pat.New("/my/*"), rAPIMy)
		{
			// "my" endpoints don't make sense for non-authenticated users
			rAPIMy.Use(gm.authnRequiredMiddleware)

			gm.setupUserAPIEndpoints(rAPIMy, gm.getUserFromAuthn)
		}

		rAPI.HandleFunc(
			pat.Get("/test_internal_error"), hh.MakeAPIHandler(
				func(r *http.Request) (resp interface{}, err error) {
					errTest := errors.Errorf("some private error")
					errTest = errors.Annotatef(errTest, "private annotation")
					return nil, errors.Annotatef(hh.MakeInternalServerError(errTest), "public annotation")
				},
			),
		)
	}

	return rRoot, nil
}

type getSubjUser func(r *http.Request) (*storage.UserData, error)
type webSocketMux func(
	reader io.Reader, caller, subjUser *storage.UserData,
) (resp interface{}, err error)

// Sets up user-related endpoints at a given mux. We need this function since
// we have two ways to access user data: through the "/api/users/:userid" and
// through the shortcut "/api/my"; so, in order to avoid duplication, this
// function sets up everything given the function that gets user data.
func (gm *GMServer) setupUserAPIEndpoints(mux *goji.Mux, gsu getSubjUser) {
	mkUserHandler := func(
		uh func(gmr *GMRequest) (resp interface{}, err error),
		gsu getSubjUser,
	) func(r *http.Request) (resp interface{}, err error) {
		return func(r *http.Request) (resp interface{}, err error) {
			gmr, err := makeGMRequestFromHttpRequest(r, gsu)
			if err != nil {
				return nil, errors.Trace(err)
			}
			return uh(gmr)
		}
	}

	mkUserHandlerWWriter := func(
		uh func(w http.ResponseWriter, r *http.Request, gsu getSubjUser, wsMux webSocketMux) (err error),
		gsu getSubjUser, wsMux webSocketMux,
	) func(w http.ResponseWriter, r *http.Request) (err error) {
		return func(w http.ResponseWriter, r *http.Request) (err error) {
			return uh(w, r, gsu, wsMux)
		}
	}

	// TODO: refactor this ugly mux
	wsMux := func(
		reader io.Reader, caller, subjUser *storage.UserData,
	) (resp interface{}, err error) {
		wsr, err := parseWebSocketRequest(reader)
		if err != nil {
			return nil, errors.Trace(err)
		}

		if wsr.Path == "/tags" || strings.HasPrefix(wsr.Path, "/tags/") {
			path := wsr.Path[len("/tags"):]

			gmr, err := makeGMRequestFromWebSocketRequest(
				wsr, caller, subjUser, path,
			)
			if err != nil {
				return nil, errors.Trace(err)
			}

			switch wsr.Method {
			case "GET":
				resp, err = gm.userTagsGet(gmr)
				if err != nil {
					return nil, errors.Trace(err)
				}
			case "POST":
				resp, err = gm.userTagsPost(gmr)
				if err != nil {
					return nil, errors.Trace(err)
				}
			default:
				return nil, errors.Errorf("wrong method")
			}
		} else {
			return nil, errors.Errorf("wrong path")
		}

		return resp, nil
	}<commit_msg>Add websocket endpoint /bookmarks<commit_after>package server

import (
	"database/sql"
	"io"
	"net/http"
	"strconv"
	"strings"

	goji "goji.io"
	"goji.io/pat"

	"dmitryfrank.com/geekmarks/server/cptr"
	hh "dmitryfrank.com/geekmarks/server/httphelper"
	"dmitryfrank.com/geekmarks/server/middleware"
	"dmitryfrank.com/geekmarks/server/storage"
	"github.com/golang/glog"
	"github.com/juju/errors"
)

type GMServer struct {
	si storage.Storage
}

func New(si storage.Storage) (*GMServer, error) {
	return &GMServer{
		si: si,
	}, nil
}

func (gm *GMServer) CreateHandler() (http.Handler, error) {
	rRoot := goji.NewMux()
	rRoot.Use(middleware.MakeLogger())

	rAPI := goji.SubMux()
	rRoot.Handle(pat.New("/api/*"), rAPI)
	{
		rAPI.Use(hh.MakeDesiredContentTypeMiddleware("application/json"))
		// We use authnMiddleware here and not on the root router above, since we
		// need hh.MakeDesiredContentTypeMiddleware to go before it.
		rAPI.Use(gm.authnMiddleware)

		rAPIUsers := goji.SubMux()
		rAPI.Handle(pat.New("/users/:userid/*"), rAPIUsers)
		{
			gm.setupUserAPIEndpoints(rAPIUsers, gm.getUserFromURLParam)
		}

		rAPIMy := goji.SubMux()
		rAPI.Handle(pat.New("/my/*"), rAPIMy)
		{
			// "my" endpoints don't make sense for non-authenticated users
			rAPIMy.Use(gm.authnRequiredMiddleware)

			gm.setupUserAPIEndpoints(rAPIMy, gm.getUserFromAuthn)
		}

		rAPI.HandleFunc(
			pat.Get("/test_internal_error"), hh.MakeAPIHandler(
				func(r *http.Request) (resp interface{}, err error) {
					errTest := errors.Errorf("some private error")
					errTest = errors.Annotatef(errTest, "private annotation")
					return nil, errors.Annotatef(hh.MakeInternalServerError(errTest), "public annotation")
				},
			),
		)
	}

	return rRoot, nil
}

type getSubjUser func(r *http.Request) (*storage.UserData, error)
type webSocketMux func(
	reader io.Reader, caller, subjUser *storage.UserData,
) (resp interface{}, err error)

// Sets up user-related endpoints at a given mux. We need this function since
// we have two ways to access user data: through the "/api/users/:userid" and
// through the shortcut "/api/my"; so, in order to avoid duplication, this
// function sets up everything given the function that gets user data.
func (gm *GMServer) setupUserAPIEndpoints(mux *goji.Mux, gsu getSubjUser) {
	mkUserHandler := func(
		uh func(gmr *GMRequest) (resp interface{}, err error),
		gsu getSubjUser,
	) func(r *http.Request) (resp interface{}, err error) {
		return func(r *http.Request) (resp interface{}, err error) {
			gmr, err := makeGMRequestFromHttpRequest(r, gsu)
			if err != nil {
				return nil, errors.Trace(err)
			}
			return uh(gmr)
		}
	}

	mkUserHandlerWWriter := func(
		uh func(w http.ResponseWriter, r *http.Request, gsu getSubjUser, wsMux webSocketMux) (err error),
		gsu getSubjUser, wsMux webSocketMux,
	) func(w http.ResponseWriter, r *http.Request) (err error) {
		return func(w http.ResponseWriter, r *http.Request) (err error) {
			return uh(w, r, gsu, wsMux)
		}
	}

	// TODO: refactor this ugly mux
	wsMux := func(
		reader io.Reader, caller, subjUser *storage.UserData,
	) (resp interface{}, err error) {
		wsr, err := parseWebSocketRequest(reader)
		if err != nil {
			return nil, errors.Trace(err)
		}

		if wsr.Path == "/tags" || strings.HasPrefix(wsr.Path, "/tags/") {
			path := wsr.Path[len("/tags"):]

			gmr, err := makeGMRequestFromWebSocketRequest(
				wsr, caller, subjUser, path,
			)
			if err != nil {
				return nil, errors.Trace(err)
			}

			switch wsr.Method {
			case "GET":
				resp, err = gm.userTagsGet(gmr)
				if err != nil {
					return nil, errors.Trace(err)
				}
			case "POST":
				resp, err = gm.userTagsPost(gmr)
				if err != nil {
					return nil, errors.Trace(err)
				}
			default:
				return nil, errors.Errorf("wrong method")
			}

		} else if wsr.Path == "/bookmarks" {
			gmr, err := makeGMRequestFromWebSocketRequest(
				wsr, caller, subjUser, "",
			)
			if err != nil {
				return nil, errors.Trace(err)
			}

			switch wsr.Method {
			case "GET":
				resp, err = gm.userBookmarksGet(gmr)
				if err != nil {
					return nil, errors.Trace(err)
				}
			case "POST":
				resp, err = gm.userBookmarksPost(gmr)
				if err != nil {
					return nil, errors.Trace(err)
				}
			default:
				return nil, errors.Errorf("wrong method")
			}

		} else {
			return nil, errors.Errorf("wrong path")
		}

		return resp, nil
	}
{'fraction_non_alphanumeric': 0.1370595382746051, 'fraction_numerical': 0.0, 'mean_word_length': 3.660815402038505, 'pattern_counts': {'":': 6, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2451559', 'n_tokens_mistral': 3364, 'n_tokens_neox': 2988, 'n_words': 724}