feat(api,db): add /user routes
parent
071ff8ac68
commit
f24d6b6bda
@ -0,0 +1,86 @@
|
||||
//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.8.2 --package=api --generate types -o types.gen.go ../schema/schema.yaml
|
||||
//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.8.2 --package=api --generate chi-server -o routes.gen.go ../schema/schema.yaml
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hhhapz/hackathon/models"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// inProd tells the server whether it is in prod.
|
||||
// It should be set using ldflags on build time.
|
||||
var inProd bool
|
||||
|
||||
type Server struct {
|
||||
*AuthService
|
||||
*UserService
|
||||
}
|
||||
|
||||
type OAuthStore interface {
|
||||
Create(callback string) (code string)
|
||||
Validate(code string) (string, bool)
|
||||
Exchange(ctx context.Context, code string) (*oauth2.Token, error)
|
||||
Remove(code string)
|
||||
}
|
||||
|
||||
type UserStore interface {
|
||||
CreateToken(ctx context.Context, user *models.User) (*models.Token, error)
|
||||
RevokeToken(ctx context.Context, token string) error
|
||||
RevokeUserTokens(ctx context.Context, user *models.User) (int64, error)
|
||||
|
||||
User(ctx context.Context, email string) (*models.User, error)
|
||||
Users(ctx context.Context) ([]*models.User, error)
|
||||
|
||||
DecodeUser(ctx context.Context, buf []byte) (*models.User, error)
|
||||
UserByToken(ctx context.Context, token string) (*models.User, error)
|
||||
|
||||
UpdateUser(ctx context.Context, user *models.User) error
|
||||
}
|
||||
|
||||
var _ ServerInterface = (*Server)(nil)
|
||||
|
||||
func NewServer(oaStore OAuthStore, userStore UserStore) *Server {
|
||||
return &Server{
|
||||
AuthService: &AuthService{
|
||||
oauthStore: oaStore,
|
||||
userStore: userStore,
|
||||
},
|
||||
UserService: &UserService{
|
||||
userStore: userStore,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func serverError(w http.ResponseWriter, code int, message string) {
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(Error{
|
||||
Code: code,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
type parseError struct {
|
||||
typ string
|
||||
value string
|
||||
reason string
|
||||
message string
|
||||
}
|
||||
|
||||
func newParseError(typ, value, reason string) error {
|
||||
return parseError{
|
||||
typ: typ,
|
||||
value: value,
|
||||
reason: reason,
|
||||
message: "invalid %s(%q): %s",
|
||||
}
|
||||
}
|
||||
|
||||
func (err parseError) Error() string {
|
||||
return fmt.Sprintf(err.message, err.typ, err.value, err.reason)
|
||||
}
|
@ -1,341 +0,0 @@
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/deepmap/oapi-codegen version v1.8.2 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/deepmap/oapi-codegen/pkg/runtime"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// ConsentPage defines model for ConsentPage.
|
||||
type ConsentPage struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// Error defines model for Error.
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// DefaultResponse defines model for DefaultResponse.
|
||||
type DefaultResponse Error
|
||||
|
||||
// AuthorizeCallbackParams defines parameters for AuthorizeCallback.
|
||||
type AuthorizeCallbackParams struct {
|
||||
State string `json:"state"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GenOauthParams defines parameters for GenOauth.
|
||||
type GenOauthParams struct {
|
||||
Callback string `json:"callback"`
|
||||
}
|
||||
|
||||
// DeleteTokenParams defines parameters for DeleteToken.
|
||||
type DeleteTokenParams struct {
|
||||
All *bool `json:"all,omitempty"`
|
||||
|
||||
// User authentication token
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ServerInterface represents all server handlers.
|
||||
type ServerInterface interface {
|
||||
|
||||
// (GET /auth/authorize)
|
||||
AuthorizeCallback(w http.ResponseWriter, r *http.Request, params AuthorizeCallbackParams)
|
||||
|
||||
// (GET /auth/code)
|
||||
GenOauth(w http.ResponseWriter, r *http.Request, params GenOauthParams)
|
||||
|
||||
// (DELETE /auth/token)
|
||||
DeleteToken(w http.ResponseWriter, r *http.Request, params DeleteTokenParams)
|
||||
}
|
||||
|
||||
// ServerInterfaceWrapper converts contexts to parameters.
|
||||
type ServerInterfaceWrapper struct {
|
||||
Handler ServerInterface
|
||||
HandlerMiddlewares []MiddlewareFunc
|
||||
}
|
||||
|
||||
type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc
|
||||
|
||||
// AuthorizeCallback operation middleware
|
||||
func (siw *ServerInterfaceWrapper) AuthorizeCallback(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params AuthorizeCallbackParams
|
||||
|
||||
// ------------- Required query parameter "state" -------------
|
||||
if paramValue := r.URL.Query().Get("state"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument state is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "state", r.URL.Query(), ¶ms.State)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter state: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Required query parameter "code" -------------
|
||||
if paramValue := r.URL.Query().Get("code"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument code is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "code", r.URL.Query(), ¶ms.Code)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter code: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.AuthorizeCallback(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// GenOauth operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GenOauth(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params GenOauthParams
|
||||
|
||||
// ------------- Required query parameter "callback" -------------
|
||||
if paramValue := r.URL.Query().Get("callback"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument callback is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "callback", r.URL.Query(), ¶ms.Callback)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter callback: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GenOauth(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// DeleteToken operation middleware
|
||||
func (siw *ServerInterfaceWrapper) DeleteToken(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params DeleteTokenParams
|
||||
|
||||
// ------------- Optional query parameter "all" -------------
|
||||
if paramValue := r.URL.Query().Get("all"); paramValue != "" {
|
||||
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, false, "all", r.URL.Query(), ¶ms.All)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter all: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var cookie *http.Cookie
|
||||
|
||||
if cookie, err = r.Cookie("token"); err == nil {
|
||||
var value string
|
||||
err = runtime.BindStyledParameter("simple", true, "token", cookie.Value, &value)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid format for parameter token: %s", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params.Token = value
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument token is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.DeleteToken(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// Handler creates http.Handler with routing matching OpenAPI spec.
|
||||
func Handler(si ServerInterface) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{})
|
||||
}
|
||||
|
||||
type ChiServerOptions struct {
|
||||
BaseURL string
|
||||
BaseRouter chi.Router
|
||||
Middlewares []MiddlewareFunc
|
||||
}
|
||||
|
||||
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
|
||||
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseURL: baseURL,
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
// HandlerWithOptions creates http.Handler with additional options
|
||||
func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler {
|
||||
r := options.BaseRouter
|
||||
|
||||
if r == nil {
|
||||
r = chi.NewRouter()
|
||||
}
|
||||
wrapper := ServerInterfaceWrapper{
|
||||
Handler: si,
|
||||
HandlerMiddlewares: options.Middlewares,
|
||||
}
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/auth/authorize", wrapper.AuthorizeCallback)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/auth/code", wrapper.GenOauth)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Delete(options.BaseURL+"/auth/token", wrapper.DeleteToken)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"H4sIAAAAAAAC/7RVy1P7NhD+VzTbHl3bBU4+lYFOSYcODI8Tw0GRN7aILAlpDQ2Z/O+dlZ0HJC1Mh98F",
|
||||
"7PU+vv2+3c0SlOu8s2gpQrWEgNE7GzG9nONM9oZuRhublLOElvhRem+0kqSdLZ6is2yLqsVO8tPPAWdQ",
|
||||
"wU/FNn8xfI3F7yG4AKvVKoMaowracxKo4N7i3x4VYS0ihhcMAtlVuCC0fZFG16KPyC++J+D4MSVXPGOM",
|
||||
"lq5lk5D64DwG0kMrfTD8b+ZCJwkq6IOGDGjhESqIFLRtUr6Az70OWEP1kGIeN05u+oSKYJXBAH+vhHJ1",
|
||||
"KlwPrEF1UpabaG0JGwwc3mGMI8b/rp8Sbv33oXCAtjO3FkaqJAx2UhuooMPfWtm9SUvS5P4VMrCy4/gL",
|
||||
"topTo2FPgVPRSjWX1DqbidZFlkJb8efkVpxeT0T0qPRsVF2ogJIdpguxSZlDBkYrHOdlrPjX5E5cjtZs",
|
||||
"EANaIh+rokgY807TL2Nc7kJTMDTSZDj69lU2DQZxsYYGGbxgiAPkMv81L9ndebTSa6jgOC/zEjLwktqk",
|
||||
"TSF7atMfF/RbQtZgIosVTN1Mam5/7XEmjZlKNU9JguyQMESoHpagueZzj2GxJTSSJG5sqx6FHrOdddhT",
|
||||
"+nCiUfKv53nM3q/scXk0zOCuqDdY64CKBDnxilPP05RBi7JOTS3h0g2Kvl/hT5ZlWN9x1g8v/AZa8fGU",
|
||||
"cDDJhilNpMMjWwaZ1ot0UKE/0F6x19eEUVsZ/53TT/r8SPFRWX7bJdy9WQfu4RVTI0YnwV7i/mYCP4p4",
|
||||
"cnO0w/gYJNwn/zzZ75Lbl/iXxsAu1RvYM2kibrieOmdQ2mEvPvwk8L1ndGhpfXdoBJAqKufmGrcl1x//",
|
||||
"/w4dlSf7O5RwxF4pjHHWG7MQxjUN1sL19O2CrFb/BAAA//9U4MuzmAcAAA==",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
// or error if failed to decode
|
||||
func decodeSpec() ([]byte, error) {
|
||||
zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error base64 decoding spec: %s", err)
|
||||
}
|
||||
zr, err := gzip.NewReader(bytes.NewReader(zipped))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decompressing spec: %s", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
_, err = buf.ReadFrom(zr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decompressing spec: %s", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var rawSpec = decodeSpecCached()
|
||||
|
||||
// a naive cached of a decoded swagger spec
|
||||
func decodeSpecCached() func() ([]byte, error) {
|
||||
data, err := decodeSpec()
|
||||
return func() ([]byte, error) {
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
|
||||
// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
|
||||
func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
|
||||
var res = make(map[string]func() ([]byte, error))
|
||||
if len(pathToFile) > 0 {
|
||||
res[pathToFile] = rawSpec
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// GetSwagger returns the Swagger specification corresponding to the generated code
|
||||
// in this file. The external references of Swagger specification are resolved.
|
||||
// The logic of resolving external references is tightly connected to "import-mapping" feature.
|
||||
// Externally referenced files must be embedded in the corresponding golang packages.
|
||||
// Urls can be supported but this task was out of the scope.
|
||||
func GetSwagger() (swagger *openapi3.T, err error) {
|
||||
var resolvePath = PathToRawSpec("")
|
||||
|
||||
loader := openapi3.NewLoader()
|
||||
loader.IsExternalRefsAllowed = true
|
||||
loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
|
||||
var pathToFile = url.String()
|
||||
pathToFile = path.Clean(pathToFile)
|
||||
getSpec, ok := resolvePath[pathToFile]
|
||||
if !ok {
|
||||
err1 := fmt.Errorf("path not found: %s", pathToFile)
|
||||
return nil, err1
|
||||
}
|
||||
return getSpec()
|
||||
}
|
||||
var specData []byte
|
||||
specData, err = rawSpec()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
swagger, err = loader.LoadFromData(specData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.8.2 --package=api --generate types,chi-server,spec -o hackathon.gen.go ../schema/schema.yaml
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/hhhapz/hackathon/auth"
|
||||
"github.com/hhhapz/hackathon/models"
|
||||
)
|
||||
|
||||
// inProd tells the server whether it is in prod.
|
||||
// It should be set using ldflags on build time.
|
||||
var inProd bool
|
||||
|
||||
type Server struct {
|
||||
*AuthService
|
||||
}
|
||||
|
||||
type OAuthStore interface {
|
||||
Create(callback string) (code string)
|
||||
Validate(code string) (callback string, valid bool)
|
||||
Remove(code string)
|
||||
}
|
||||
|
||||
type UserStore interface {
|
||||
CreateToken(ctx context.Context, user *models.User) (*models.Token, error)
|
||||
RevokeToken(ctx context.Context, token string) error
|
||||
RevokeUserTokens(ctx context.Context, user *models.User) (int64, error)
|
||||
|
||||
DecodeUser(ctx context.Context, buf []byte) (*models.User, error)
|
||||
UserByToken(ctx context.Context, token string) (*models.User, error)
|
||||
}
|
||||
|
||||
var _ ServerInterface = (*Server)(nil)
|
||||
|
||||
func NewServer(oaConfig auth.OAuthConfig, oaStore OAuthStore, userStore UserStore) *Server {
|
||||
return &Server{
|
||||
AuthService: &AuthService{
|
||||
oauthConfig: oaConfig,
|
||||
oauthStore: oaStore,
|
||||
userStore: userStore,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func serverError(w http.ResponseWriter, code int, message string) {
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(Error{
|
||||
Code: code,
|
||||
Message: message,
|
||||
})
|
||||
}
|
@ -0,0 +1,450 @@
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/deepmap/oapi-codegen version v1.8.2 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/deepmap/oapi-codegen/pkg/runtime"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// ServerInterface represents all server handlers.
|
||||
type ServerInterface interface {
|
||||
|
||||
// (GET /auth/authorize)
|
||||
AuthorizeCallback(w http.ResponseWriter, r *http.Request, params AuthorizeCallbackParams)
|
||||
|
||||
// (GET /auth/code)
|
||||
GenOauth(w http.ResponseWriter, r *http.Request, params GenOauthParams)
|
||||
|
||||
// (DELETE /auth/token)
|
||||
DeleteToken(w http.ResponseWriter, r *http.Request, params DeleteTokenParams)
|
||||
|
||||
// (GET /users/all)
|
||||
GetAllUsers(w http.ResponseWriter, r *http.Request, params GetAllUsersParams)
|
||||
|
||||
// (GET /users/email)
|
||||
GetUserByEmail(w http.ResponseWriter, r *http.Request, params GetUserByEmailParams)
|
||||
|
||||
// (PATCH /users/email)
|
||||
ModifyOtherUser(w http.ResponseWriter, r *http.Request, params ModifyOtherUserParams)
|
||||
|
||||
// (GET /users/me)
|
||||
GetMe(w http.ResponseWriter, r *http.Request, params GetMeParams)
|
||||
|
||||
// (PATCH /users/me)
|
||||
ModifyUser(w http.ResponseWriter, r *http.Request, params ModifyUserParams)
|
||||
}
|
||||
|
||||
// ServerInterfaceWrapper converts contexts to parameters.
|
||||
type ServerInterfaceWrapper struct {
|
||||
Handler ServerInterface
|
||||
HandlerMiddlewares []MiddlewareFunc
|
||||
}
|
||||
|
||||
type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc
|
||||
|
||||
// AuthorizeCallback operation middleware
|
||||
func (siw *ServerInterfaceWrapper) AuthorizeCallback(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params AuthorizeCallbackParams
|
||||
|
||||
// ------------- Required query parameter "state" -------------
|
||||
if paramValue := r.URL.Query().Get("state"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument state is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "state", r.URL.Query(), ¶ms.State)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter state: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Required query parameter "code" -------------
|
||||
if paramValue := r.URL.Query().Get("code"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument code is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "code", r.URL.Query(), ¶ms.Code)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter code: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.AuthorizeCallback(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// GenOauth operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GenOauth(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params GenOauthParams
|
||||
|
||||
// ------------- Required query parameter "callback" -------------
|
||||
if paramValue := r.URL.Query().Get("callback"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument callback is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "callback", r.URL.Query(), ¶ms.Callback)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter callback: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GenOauth(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// DeleteToken operation middleware
|
||||
func (siw *ServerInterfaceWrapper) DeleteToken(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params DeleteTokenParams
|
||||
|
||||
// ------------- Optional query parameter "all" -------------
|
||||
if paramValue := r.URL.Query().Get("all"); paramValue != "" {
|
||||
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, false, "all", r.URL.Query(), ¶ms.All)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter all: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var cookie *http.Cookie
|
||||
|
||||
if cookie, err = r.Cookie("token"); err == nil {
|
||||
var value string
|
||||
err = runtime.BindStyledParameter("simple", true, "token", cookie.Value, &value)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid format for parameter token: %s", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params.Token = value
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument token is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.DeleteToken(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// GetAllUsers operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetAllUsers(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params GetAllUsersParams
|
||||
|
||||
var cookie *http.Cookie
|
||||
|
||||
if cookie, err = r.Cookie("token"); err == nil {
|
||||
var value string
|
||||
err = runtime.BindStyledParameter("simple", true, "token", cookie.Value, &value)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid format for parameter token: %s", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params.Token = value
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument token is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetAllUsers(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// GetUserByEmail operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetUserByEmail(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params GetUserByEmailParams
|
||||
|
||||
// ------------- Required query parameter "email" -------------
|
||||
if paramValue := r.URL.Query().Get("email"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument email is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "email", r.URL.Query(), ¶ms.Email)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter email: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var cookie *http.Cookie
|
||||
|
||||
if cookie, err = r.Cookie("token"); err == nil {
|
||||
var value string
|
||||
err = runtime.BindStyledParameter("simple", true, "token", cookie.Value, &value)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid format for parameter token: %s", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params.Token = value
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument token is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetUserByEmail(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// ModifyOtherUser operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ModifyOtherUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params ModifyOtherUserParams
|
||||
|
||||
// ------------- Required query parameter "email" -------------
|
||||
if paramValue := r.URL.Query().Get("email"); paramValue != "" {
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument email is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, true, "email", r.URL.Query(), ¶ms.Email)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Invalid format for parameter email: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var cookie *http.Cookie
|
||||
|
||||
if cookie, err = r.Cookie("token"); err == nil {
|
||||
var value string
|
||||
err = runtime.BindStyledParameter("simple", true, "token", cookie.Value, &value)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid format for parameter token: %s", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params.Token = value
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument token is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ModifyOtherUser(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// GetMe operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params GetMeParams
|
||||
|
||||
var cookie *http.Cookie
|
||||
|
||||
if cookie, err = r.Cookie("token"); err == nil {
|
||||
var value string
|
||||
err = runtime.BindStyledParameter("simple", true, "token", cookie.Value, &value)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid format for parameter token: %s", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params.Token = value
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument token is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetMe(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// ModifyUser operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ModifyUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var err error
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params ModifyUserParams
|
||||
|
||||
var cookie *http.Cookie
|
||||
|
||||
if cookie, err = r.Cookie("token"); err == nil {
|
||||
var value string
|
||||
err = runtime.BindStyledParameter("simple", true, "token", cookie.Value, &value)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid format for parameter token: %s", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params.Token = value
|
||||
|
||||
} else {
|
||||
http.Error(w, "Query argument token is required, but not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ModifyUser(w, r, params)
|
||||
}
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// Handler creates http.Handler with routing matching OpenAPI spec.
|
||||
func Handler(si ServerInterface) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{})
|
||||
}
|
||||
|
||||
type ChiServerOptions struct {
|
||||
BaseURL string
|
||||
BaseRouter chi.Router
|
||||
Middlewares []MiddlewareFunc
|
||||
}
|
||||
|
||||
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
|
||||
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseURL: baseURL,
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
// HandlerWithOptions creates http.Handler with additional options
|
||||
func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler {
|
||||
r := options.BaseRouter
|
||||
|
||||
if r == nil {
|
||||
r = chi.NewRouter()
|
||||
}
|
||||
wrapper := ServerInterfaceWrapper{
|
||||
Handler: si,
|
||||
HandlerMiddlewares: options.Middlewares,
|
||||
}
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/auth/authorize", wrapper.AuthorizeCallback)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/auth/code", wrapper.GenOauth)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Delete(options.BaseURL+"/auth/token", wrapper.DeleteToken)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/users/all", wrapper.GetAllUsers)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/users/email", wrapper.GetUserByEmail)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Patch(options.BaseURL+"/users/email", wrapper.ModifyOtherUser)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/users/me", wrapper.GetMe)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Patch(options.BaseURL+"/users/me", wrapper.ModifyUser)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/deepmap/oapi-codegen version v1.8.2 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
openapi_types "github.com/deepmap/oapi-codegen/pkg/types"
|
||||
)
|
||||
|
||||
// ConsentPage defines model for ConsentPage.
|
||||
type ConsentPage struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// Error defines model for Error.
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// User defines model for User.
|
||||
type User struct {
|
||||
Admin bool `json:"admin"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Email openapi_types.Email `json:"email"`
|
||||
|
||||
// GradeLevel is only present if teacher is false.
|
||||
GradeLevel *int `json:"grade_level,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Picture string `json:"picture"`
|
||||
Teacher bool `json:"teacher"`
|
||||
}
|
||||
|
||||
// DefaultResponse defines model for DefaultResponse.
|
||||
type DefaultResponse Error
|
||||
|
||||
// AuthorizeCallbackParams defines parameters for AuthorizeCallback.
|
||||
type AuthorizeCallbackParams struct {
|
||||
State string `json:"state"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GenOauthParams defines parameters for GenOauth.
|
||||
type GenOauthParams struct {
|
||||
Callback string `json:"callback"`
|
||||
}
|
||||
|
||||
// DeleteTokenParams defines parameters for DeleteToken.
|
||||
type DeleteTokenParams struct {
|
||||
All *bool `json:"all,omitempty"`
|
||||
|
||||
// User authentication token.
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// GetAllUsersParams defines parameters for GetAllUsers.
|
||||
type GetAllUsersParams struct {
|
||||
// User authentication token.
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// GetUserByEmailParams defines parameters for GetUserByEmail.
|
||||
type GetUserByEmailParams struct {
|
||||
// User email.
|
||||
Email openapi_types.Email `json:"email"`
|
||||
|
||||
// User authentication token.
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ModifyOtherUserJSONBody defines parameters for ModifyOtherUser.
|
||||
type ModifyOtherUserJSONBody struct {
|
||||
Admin bool `json:"admin"`
|
||||
GradeLevel int `json:"grade_level"`
|
||||
Name string `json:"name"`
|
||||
NewEmail *openapi_types.Email `json:"new_email,omitempty"`
|
||||
Picture string `json:"picture"`
|
||||
Teacher bool `json:"teacher"`
|
||||
}
|
||||
|
||||
// ModifyOtherUserParams defines parameters for ModifyOtherUser.
|
||||
type ModifyOtherUserParams struct {
|
||||
// User email.
|
||||
Email openapi_types.Email `json:"email"`
|
||||
|
||||
// User authentication token.
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// GetMeParams defines parameters for GetMe.
|
||||
type GetMeParams struct {
|
||||
// User authentication token.
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ModifyUserJSONBody defines parameters for ModifyUser.
|
||||
type ModifyUserJSONBody struct {
|
||||
GradeLevel int `json:"grade_level"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ModifyUserParams defines parameters for ModifyUser.
|
||||
type ModifyUserParams struct {
|
||||
// User authentication token.
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ModifyOtherUserJSONRequestBody defines body for ModifyOtherUser for application/json ContentType.
|
||||
type ModifyOtherUserJSONRequestBody ModifyOtherUserJSONBody
|
||||
|
||||
// ModifyUserJSONRequestBody defines body for ModifyUser for application/json ContentType.
|
||||
type ModifyUserJSONRequestBody ModifyUserJSONBody
|
@ -0,0 +1,210 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/deepmap/oapi-codegen/pkg/types"
|
||||
"github.com/hhhapz/hackathon/models"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
userStore UserStore
|
||||
}
|
||||
|
||||
func (us *UserService) GetMe(w http.ResponseWriter, r *http.Request, params GetMeParams) {
|
||||
u, err := us.userStore.UserByToken(r.Context(), params.Token)
|
||||
if err != nil {
|
||||
serverError(w, http.StatusUnauthorized, "Invalid token provided")
|
||||
return
|
||||
}
|
||||
|
||||
writeUser(w, u)
|
||||
}
|
||||
|
||||
func (us *UserService) ModifyUser(w http.ResponseWriter, r *http.Request, params ModifyUserParams) {
|
||||
u, err := us.userStore.UserByToken(r.Context(), params.Token)
|
||||
if err != nil {
|
||||
serverError(w, http.StatusUnauthorized, "Invalid token provided")
|
||||
return
|
||||
}
|
||||
|
||||
var body ModifyUserJSONBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
serverError(w, http.StatusBadRequest, "Invalid JSON body for ModifyUser")
|
||||
return
|
||||
}
|
||||
|
||||
u.Name = strings.TrimSpace(body.Name)
|
||||
if len(u.Name) < 3 || len(u.Name) > 30 {
|
||||
serverError(w, http.StatusUnprocessableEntity, "Name must be between 3 and 30 characters long")
|
||||
return
|
||||
}
|
||||
if !u.Teacher {
|
||||
if body.GradeLevel < 9 || body.GradeLevel > 12 {
|
||||
serverError(w, http.StatusUnprocessableEntity, "Grade Level must be between 9 and 12")
|
||||
return
|
||||
}
|
||||
u.GradeLevel = sql.NullInt64{Int64: int64(body.GradeLevel), Valid: true}
|
||||
}
|
||||
|
||||
err = us.userStore.UpdateUser(r.Context(), u)
|
||||
if err != nil {
|
||||
log.Printf("Could not modify user %s: %v", u.Email, err)
|
||||
serverError(w, http.StatusInternalServerError, "Could not update user")
|
||||
return
|
||||
}
|
||||
|
||||
writeUser(w, u)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUserByEmail(w http.ResponseWriter, r *http.Request, params GetUserByEmailParams) {
|
||||
u, err := us.userStore.UserByToken(r.Context(), params.Token)
|
||||
if err != nil {
|
||||
serverError(w, http.StatusUnauthorized, "Invalid token provided")
|
||||
return
|
||||
}
|
||||
|
||||
email := string(params.Email)
|
||||
if u.Email == email {
|
||||
writeUser(w, u)
|
||||
return
|
||||
}
|
||||
|
||||
if !u.Admin {
|
||||
log.Printf("user accessing unauthorized endpoint: %s", u.Email)
|
||||
serverError(w, http.StatusForbidden, "You are not authorized to do this")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := us.userStore.User(r.Context(), email)
|
||||
if err != nil {
|
||||
log.Printf("Could not fetch user(%q): %v", email, err)
|
||||
serverError(w, http.StatusNotFound, "Could not find user with the specified email")
|
||||
return
|
||||
}
|
||||
|
||||
writeUser(w, user)
|
||||
}
|
||||
|
||||
func (us *UserService) ModifyOtherUser(w http.ResponseWriter, r *http.Request, params ModifyOtherUserParams) {
|
||||
u, err := us.userStore.UserByToken(r.Context(), params.Token)
|
||||
if err != nil {
|
||||
serverError(w, http.StatusUnauthorized, "Invalid token provided")
|
||||
return
|
||||
}
|
||||
|
||||
if !u.Admin {
|
||||
log.Printf("user accessing unauthorized endpoint: %s", u.Email)
|
||||
serverError(w, http.StatusForbidden, "You are not authorized to do this")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := us.userStore.User(r.Context(), string(params.Email))
|
||||
if err != nil {
|
||||
log.Printf("Could not fetch user(%q): %v", params.Email, err)
|
||||
serverError(w, http.StatusNotFound, "Could not find user with the specified email")
|
||||
return
|
||||
}
|
||||
|
||||
var body ModifyOtherUserJSONBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
serverError(w, http.StatusBadRequest, "Invalid JSON body for ModifyOtherUser")
|
||||
return
|
||||
}
|
||||
|
||||
user.Name = strings.TrimSpace(body.Name)
|
||||
user.Email = string(*body.NewEmail)
|
||||
user.Picture = body.Picture
|
||||
user.Teacher = body.Teacher
|
||||
user.Admin = body.Admin
|
||||
|
||||
if len(user.Name) < 3 || len(user.Name) > 30 {
|
||||
serverError(w, http.StatusUnprocessableEntity, "Name must be between 3 and 30 characters long")
|
||||
return
|
||||
}
|
||||
user.GradeLevel = sql.NullInt64{}
|
||||
if !user.Teacher {
|
||||
if body.GradeLevel < 9 || body.GradeLevel > 12 {
|
||||
serverError(w, http.StatusUnprocessableEntity, "Grade Level must be between 9 and 12")
|
||||
return
|
||||
}
|
||||
user.GradeLevel = sql.NullInt64{Int64: int64(body.GradeLevel), Valid: true}
|
||||
}
|
||||
|
||||
writeUser(w, user)
|
||||
}
|
||||
|
||||
func (us *UserService) GetAllUsers(w http.ResponseWriter, r *http.Request, params GetAllUsersParams) {
|
||||
u, err := us.userStore.UserByToken(r.Context(), params.Token)
|
||||
if err != nil {
|
||||
serverError(w, http.StatusUnauthorized, "Invalid token provided")
|
||||
return
|
||||
}
|
||||
|
||||
if !u.Admin {
|
||||
log.Printf("user accessing unauthorized endpoint: %s", u.Email)
|
||||
serverError(w, http.StatusForbidden, "You are not authorized to do this")
|
||||
return
|
||||
}
|
||||
|
||||
users, err := us.userStore.Users(r.Context())
|
||||
if err != nil {
|
||||
log.Printf("user accessing unauthorized endpoint: %s", u.Email)
|
||||
serverError(w, http.StatusInternalServerError, "Could not fetch users")
|
||||
return
|
||||
}
|
||||
|
||||
writeUsers(w, users)
|
||||
}
|
||||
|
||||
func writeUser(w http.ResponseWriter, u *models.User) {
|
||||
var grade *int
|
||||
if u.GradeLevel.Valid {
|
||||
g := int(u.GradeLevel.Int64)
|
||||
grade = &g
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(User{
|
||||
Admin: u.Admin,
|
||||
CreatedAt: u.CreatedAt.Time(),
|
||||
Email: types.Email(u.Email),
|
||||
GradeLevel: grade,
|
||||
Id: u.ID,
|
||||
Name: u.Name,
|
||||
Picture: u.Picture,
|
||||
Teacher: u.Teacher,
|
||||
})
|
||||
}
|
||||
|
||||
func writeUsers(w http.ResponseWriter, us []*models.User) {
|
||||
var users []User
|
||||
|
||||
for _, u := range us {
|
||||
var grade *int
|
||||
if u.GradeLevel.Valid {
|
||||
g := int(u.GradeLevel.Int64)
|
||||
grade = &g
|
||||
}
|
||||
|
||||
users = append(users, User{
|
||||
Admin: u.Admin,
|
||||
CreatedAt: u.CreatedAt.Time(),
|
||||
Email: types.Email(u.Email),
|
||||
GradeLevel: grade,
|
||||
Id: u.ID,
|
||||
Name: u.Name,
|
||||
Picture: u.Picture,
|
||||
Teacher: u.Teacher,
|
||||
})
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(users)
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
type OAuthConfig struct {
|
||||
*oauth2.Config
|
||||
}
|
||||
|
||||
const (
|
||||
base = "https://www.googleapis.com"
|
||||
scopeEmail = base + "/auth/userinfo.email"
|
||||
scopeProfile = base + "/auth/userinfo.profile"
|
||||
)
|
||||
|
||||
func NewOauthConfig(path string) (OAuthConfig, error) {
|
||||
key, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return OAuthConfig{}, fmt.Errorf("could not open file: %w", err)
|
||||
}
|
||||
|
||||
config, err := google.ConfigFromJSON(key, scopeEmail, scopeProfile)
|
||||
if err != nil {
|
||||
return OAuthConfig{}, fmt.Errorf("could not load config: %w", err)
|
||||
}
|
||||
|
||||
return OAuthConfig{
|
||||
Config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var ErrInvalidState = errors.New("invalid ouath state provided")
|
||||
|
||||
type OAuthManager interface {
|
||||
Create(callback string) (code string)
|
||||
Validate(code string) (callback string, valid bool)
|
||||
}
|
||||
|
||||
func (cfg OAuthConfig) AuthCodeURL(manager OAuthManager, callback string) string {
|
||||
state := manager.Create(callback)
|
||||
fmt.Printf("state: %v\n", state)
|
||||
|
||||
return cfg.Config.AuthCodeURL(state, oauth2.AccessTypeOffline)
|
||||
}
|
||||
|
||||
func (cfg OAuthConfig) Exchange(ctx context.Context, manager OAuthManager, state, code string) (string, *oauth2.Token, error) {
|
||||
var ok bool
|
||||
var callback string
|
||||
if callback, ok = manager.Validate(state); !ok {
|
||||
return "", nil, ErrInvalidState
|
||||
}
|
||||
|
||||
tk, err := cfg.Config.Exchange(ctx, code, oauth2.AccessTypeOffline)
|
||||
return callback, tk, err
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
package models
|
||||
|
||||
// Code generated by xo. DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// UserInfo represents a row from 'user_info'.
|
||||
type UserInfo struct {
|
||||
UserID string `json:"user_id"` // user_id
|
||||
GradeLevel int `json:"grade_level"` // grade_level
|
||||
Teacher bool `json:"teacher"` // teacher
|
||||
Admin bool `json:"admin"` // admin
|
||||
// xo fields
|
||||
_exists, _deleted bool
|
||||
}
|
||||
|
||||
// Exists returns true when the UserInfo exists in the database.
|
||||
func (ui *UserInfo) Exists() bool {
|
||||
return ui._exists
|
||||
}
|
||||
|
||||
// Deleted returns true when the UserInfo has been marked for deletion from
|
||||
// the database.
|
||||
func (ui *UserInfo) Deleted() bool {
|
||||
return ui._deleted
|
||||
}
|
||||
|
||||
// Insert inserts the UserInfo to the database.
|
||||
func (ui *UserInfo) Insert(ctx context.Context, db DB) error {
|
||||
switch {
|
||||
case ui._exists: // already exists
|
||||
return logerror(&ErrInsertFailed{ErrAlreadyExists})
|
||||
case ui._deleted: // deleted
|
||||
return logerror(&ErrInsertFailed{ErrMarkedForDeletion})
|
||||
}
|
||||
// insert (manual)
|
||||
const sqlstr = `INSERT INTO user_info (` +
|
||||
`user_id, grade_level, teacher, admin` +
|
||||
`) VALUES (` +
|
||||
`$1, $2, $3, $4` +
|
||||
`)`
|
||||
// run
|
||||
logf(sqlstr, ui.UserID, ui.GradeLevel, ui.Teacher, ui.Admin)
|
||||
if _, err := db.ExecContext(ctx, sqlstr, ui.UserID, ui.GradeLevel, ui.Teacher, ui.Admin); err != nil {
|
||||
return logerror(err)
|
||||
}
|
||||
// set exists
|
||||
ui._exists = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates a UserInfo in the database.
|
||||
func (ui *UserInfo) Update(ctx context.Context, db DB) error {
|
||||
switch {
|
||||
case !ui._exists: // doesn't exist
|
||||
return logerror(&ErrUpdateFailed{ErrDoesNotExist})
|
||||
case ui._deleted: // deleted
|
||||
return logerror(&ErrUpdateFailed{ErrMarkedForDeletion})
|
||||
}
|
||||
// update with primary key
|
||||
const sqlstr = `UPDATE user_info SET ` +
|
||||
`grade_level = $1, teacher = $2, admin = $3 ` +
|
||||
`WHERE user_id = $4`
|
||||
// run
|
||||
logf(sqlstr, ui.GradeLevel, ui.Teacher, ui.Admin, ui.UserID)
|
||||
if _, err := db.ExecContext(ctx, sqlstr, ui.GradeLevel, ui.Teacher, ui.Admin, ui.UserID); err != nil {
|
||||
return logerror(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save saves the UserInfo to the database.
|
||||
func (ui *UserInfo) Save(ctx context.Context, db DB) error {
|
||||
if ui.Exists() {
|
||||
return ui.Update(ctx, db)
|
||||
}
|
||||
return ui.Insert(ctx, db)
|
||||
}
|
||||
|
||||
// Upsert performs an upsert for UserInfo.
|
||||
func (ui *UserInfo) Upsert(ctx context.Context, db DB) error {
|
||||
switch {
|
||||
case ui._deleted: // deleted
|
||||
return logerror(&ErrUpsertFailed{ErrMarkedForDeletion})
|
||||
}
|
||||
// upsert
|
||||
const sqlstr = `INSERT INTO user_info (` +
|
||||
`user_id, grade_level, teacher, admin` +
|
||||
`) VALUES (` +
|
||||
`$1, $2, $3, $4` +
|
||||
`)` +
|
||||
` ON CONFLICT (user_id) DO ` +
|
||||
`UPDATE SET ` +
|
||||
`grade_level = EXCLUDED.grade_level, teacher = EXCLUDED.teacher, admin = EXCLUDED.admin `
|
||||
// run
|
||||
logf(sqlstr, ui.UserID, ui.GradeLevel, ui.Teacher, ui.Admin)
|
||||
if _, err := db.ExecContext(ctx, sqlstr, ui.UserID, ui.GradeLevel, ui.Teacher, ui.Admin); err != nil {
|
||||
return logerror(err)
|
||||
}
|
||||
// set exists
|
||||
ui._exists = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the UserInfo from the database.
|
||||
func (ui *UserInfo) Delete(ctx context.Context, db DB) error {
|
||||
switch {
|
||||
case !ui._exists: // doesn't exist
|
||||
return nil
|
||||
case ui._deleted: // deleted
|
||||
return nil
|
||||
}
|
||||
// delete with single primary key
|
||||
const sqlstr = `DELETE FROM user_info ` +
|
||||
`WHERE user_id = $1`
|
||||
// run
|
||||
logf(sqlstr, ui.UserID)
|
||||
if _, err := db.ExecContext(ctx, sqlstr, ui.UserID); err != nil {
|
||||
return logerror(err)
|
||||
}
|
||||
// set deleted
|
||||
ui._deleted = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserInfoByUserID retrieves a row from 'user_info' as a UserInfo.
|
||||
//
|
||||
// Generated from index 'sqlite_autoindex_user_info_1'.
|
||||
func UserInfoByUserID(ctx context.Context, db DB, userID string) (*UserInfo, error) {
|
||||
// query
|
||||
const sqlstr = `SELECT ` +
|
||||
`user_id, grade_level, teacher, admin ` +
|
||||
`FROM user_info ` +
|
||||
`WHERE user_id = $1`
|
||||
// run
|
||||
logf(sqlstr, userID)
|
||||
ui := UserInfo{
|
||||
_exists: true,
|
||||
}
|
||||
if err := db.QueryRowContext(ctx, sqlstr, userID).Scan(&ui.UserID, &ui.GradeLevel, &ui.Teacher, &ui.Admin); err != nil {
|
||||
return nil, logerror(err)
|
||||
}
|
||||
return &ui, nil
|
||||
}
|
||||
|
||||
// User returns the User associated with the UserInfo's (UserID).
|
||||
//
|
||||
// Generated from foreign key 'user_info_user_id_fkey'.
|
||||
func (ui *UserInfo) User(ctx context.Context, db DB) (*User, error) {
|
||||
return UserByID(ctx, db, ui.UserID)
|
||||
}
|
Loading…
Reference in New Issue