This commit is contained in:
15
internal/api/middleware/add_request_id.go
Normal file
15
internal/api/middleware/add_request_id.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
func AddRequestIDHeaderMiddleware(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(middleware.RequestIDHeader, middleware.GetReqID(r.Context()))
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
118
internal/api/middleware/current_user.go
Normal file
118
internal/api/middleware/current_user.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"clintonambulance.com/calculate_negative_points/internal/config"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
|
||||
func verifyTokenAndGetClaims(config *config.ApplicationConfig, ctx context.Context, token string) (*oidc.IDToken, Claims, error) {
|
||||
idToken, err := config.OidcConfig.Verifier.Verify(ctx, token)
|
||||
claims := Claims{Groups: []string{}}
|
||||
|
||||
if err != nil {
|
||||
return idToken, claims, err
|
||||
}
|
||||
|
||||
err = idToken.Claims(&claims)
|
||||
|
||||
if err != nil {
|
||||
return idToken, claims, err
|
||||
}
|
||||
|
||||
return idToken, claims, nil
|
||||
}
|
||||
|
||||
func serveWithTokenAndClaims(next http.Handler, r *http.Request, w http.ResponseWriter, claims Claims, token *oidc.IDToken) {
|
||||
ctx := context.WithValue(r.Context(), "user", token)
|
||||
ctx = context.WithValue(ctx, "claims", claims)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
func CurrentUserMiddleware(config *config.ApplicationConfig) (func(http.Handler) http.Handler, error) {
|
||||
middleware := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := config.CookieStore.Get(r, config.SessionName)
|
||||
rawIDToken, ok := session.Values["id_token"].(string)
|
||||
|
||||
if !ok {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// In test environment, bypass OIDC verification and use mock claims
|
||||
if config.Environment == "test" || config.Environment == "testEnvironment" {
|
||||
mockClaims := Claims{
|
||||
Sub: "test-user-id",
|
||||
Name: "Test User",
|
||||
Email: "test@example.com",
|
||||
Groups: []string{"calculate-negative-points-users"},
|
||||
}
|
||||
serveWithTokenAndClaims(next, r, w, mockClaims, nil)
|
||||
return
|
||||
}
|
||||
|
||||
idToken, claims, err := verifyTokenAndGetClaims(config, r.Context(), rawIDToken)
|
||||
|
||||
if err == nil {
|
||||
serveWithTokenAndClaims(next, r, w, claims, idToken)
|
||||
return
|
||||
}
|
||||
|
||||
refreshToken, ok := session.Values["refresh_token"].(string)
|
||||
if !ok {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt refresh
|
||||
tokenSrc := config.OidcConfig.OAuth2.TokenSource(r.Context(), &oauth2.Token{
|
||||
RefreshToken: refreshToken,
|
||||
})
|
||||
|
||||
newToken, err := tokenSrc.Token()
|
||||
if err != nil {
|
||||
http.Error(w, "failed to refresh token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Save new tokens
|
||||
newRawIDToken, ok := newToken.Extra("id_token").(string)
|
||||
if !ok {
|
||||
http.Error(w, "missing id_token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
idToken, claims, err = verifyTokenAndGetClaims(config, r.Context(), newRawIDToken)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "invalid refreshed token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
session.Values["id_token"] = newRawIDToken
|
||||
if newToken.RefreshToken != "" {
|
||||
session.Values["refresh_token"] = newToken.RefreshToken
|
||||
}
|
||||
if scope, ok := newToken.Extra("scope").(string); ok {
|
||||
session.Values["scope"] = scope
|
||||
}
|
||||
session.Save(r, w)
|
||||
|
||||
serveWithTokenAndClaims(next, r, w, claims, idToken)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
return middleware, nil
|
||||
}
|
||||
64
internal/api/middleware/error.go
Normal file
64
internal/api/middleware/error.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
// "github.com/getsentry/sentry-go"
|
||||
"clintonambulance.com/calculate_negative_points/internal/utils"
|
||||
"github.com/swaggest/rest"
|
||||
"github.com/swaggest/rest/nethttp"
|
||||
)
|
||||
|
||||
func ErrorResponder(w http.ResponseWriter, error string, code int) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(code)
|
||||
_ = json.NewEncoder(w).Encode(utils.NewApplicationError(error, code))
|
||||
}
|
||||
|
||||
type errorWithFields interface {
|
||||
error
|
||||
Fields() map[string]interface{}
|
||||
}
|
||||
|
||||
// ErrorHandler middleware centralizes error handling and translation of errors between different error types
|
||||
func ErrorHandler(next http.Handler) http.Handler {
|
||||
var h *nethttp.Handler
|
||||
if nethttp.HandlerAs(next, &h) {
|
||||
resp := func(ctx context.Context, err error) (int, interface{}) {
|
||||
// Handle AWS errors
|
||||
|
||||
// Application-specific error handling: if this error has a marker interface, serialize it as JSON
|
||||
var customErr utils.CustomApplicationError
|
||||
if errors.As(err, &customErr) {
|
||||
return customErr.HTTPStatus(), customErr
|
||||
}
|
||||
|
||||
// More detailed validation error messages
|
||||
var validationErr errorWithFields
|
||||
if errors.As(err, &validationErr) {
|
||||
detailedErrorMessages := make([]string, len(validationErr.Fields()))
|
||||
i := 0
|
||||
for k, v := range validationErr.Fields() {
|
||||
detailedErrorMessages[i] = fmt.Sprintf("%s: %v", k, v)
|
||||
i++
|
||||
}
|
||||
return http.StatusUnprocessableEntity, utils.ApplicationError{
|
||||
Message: fmt.Sprintf("Validation errors: %s", strings.Join(detailedErrorMessages, ", ")),
|
||||
}
|
||||
}
|
||||
|
||||
code, er := rest.Err(err)
|
||||
return code, utils.ApplicationError{
|
||||
Message: er.ErrorText,
|
||||
}
|
||||
}
|
||||
h.MakeErrResp = resp
|
||||
}
|
||||
return next
|
||||
}
|
||||
77
internal/api/middleware/jwt_middleware.go
Normal file
77
internal/api/middleware/jwt_middleware.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"clintonambulance.com/calculate_negative_points/internal/config"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const userContextKey = contextKey("user")
|
||||
|
||||
func audMatch(aud interface{}, expected string) bool {
|
||||
switch v := aud.(type) {
|
||||
case string:
|
||||
return v == expected
|
||||
case []interface{}:
|
||||
for _, val := range v {
|
||||
if s, ok := val.(string); ok && s == expected {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func JWTMiddleware(config *config.ApplicationConfig) (func(http.Handler) http.Handler, error) {
|
||||
middleware := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost && r.Method != http.MethodPut && r.Method != http.MethodDelete {
|
||||
// Skip auth for safe methods like GET/HEAD
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
|
||||
// Check if the header exists and starts with "Bearer "
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
http.Error(w, "Unauthorized: Bearer token missing or invalid", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the token by removing the "Bearer " prefix
|
||||
//tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
|
||||
//token, err := jwt.Parse(tokenStr, config.Jwt.KeySet.Keyfunc)
|
||||
//if err != nil || !token.Valid {
|
||||
// http.Error(w, "Invalid or expired token", http.StatusUnauthorized)
|
||||
// return
|
||||
//}
|
||||
|
||||
//claims, ok := token.Claims.(jwt.MapClaims)
|
||||
//if !ok {
|
||||
// http.Error(w, "Invalid token claims", http.StatusUnauthorized)
|
||||
// return
|
||||
//}
|
||||
|
||||
// Check `iss` and `aud`
|
||||
// if claims["iss"] != config.Jwt.Issuer {
|
||||
// http.Error(w, "Invalid issuer", http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
// audClaim := claims["aud"]
|
||||
// if !audMatch(audClaim, config.Jwt.Audience) {
|
||||
// http.Error(w, "Invalid audience", http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
|
||||
//ctx := context.WithValue(r.Context(), userContextKey, claims)
|
||||
next.ServeHTTP(w, r.WithContext(r.Context()))
|
||||
})
|
||||
}
|
||||
|
||||
return middleware, nil
|
||||
}
|
||||
50
internal/api/middleware/logging.go
Normal file
50
internal/api/middleware/logging.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
|
||||
const (
|
||||
LoggerContext = ContextKey("logger")
|
||||
)
|
||||
|
||||
func LoggingMiddleware(logger *zap.Logger) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
if ww.Status() < 300 && strings.HasPrefix(r.URL.Path, "/health") {
|
||||
// Don't log health checks unless they fail (ping endpoint returns empty response, HTTP status 204).
|
||||
return
|
||||
}
|
||||
var body map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
fields := []zapcore.Field{
|
||||
zap.Any("body", r.Body),
|
||||
zap.String("remote_ip", r.RemoteAddr),
|
||||
zap.String("method", r.Method),
|
||||
zap.String("uri", r.URL.Path),
|
||||
zap.String("request_id", middleware.GetReqID(r.Context())),
|
||||
zap.Int("status", ww.Status()),
|
||||
zap.Float64("latency_ms", float64(time.Since(start))/float64(time.Millisecond)),
|
||||
zap.Int("size", ww.BytesWritten()),
|
||||
}
|
||||
logger.Info("HTTP request processed", fields...)
|
||||
}()
|
||||
r = r.WithContext(context.WithValue(r.Context(), LoggerContext, logger))
|
||||
next.ServeHTTP(ww, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
28
internal/api/middleware/logout.go
Normal file
28
internal/api/middleware/logout.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"clintonambulance.com/calculate_negative_points/internal/config"
|
||||
)
|
||||
|
||||
func Logout(config *config.ApplicationConfig) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := config.CookieStore.Get(r, config.SessionName)
|
||||
session.Options.MaxAge = -1
|
||||
|
||||
err := session.Save(r, w)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to delete session", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
36
internal/api/middleware/oidc.go
Normal file
36
internal/api/middleware/oidc.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"clintonambulance.com/calculate_negative_points/internal/config"
|
||||
)
|
||||
|
||||
func OidcMiddleware(config *config.ApplicationConfig) (func(http.Handler) http.Handler, error) {
|
||||
middleware := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := config.CookieStore.Get(r, config.SessionName)
|
||||
rawIDToken, ok := session.Values["id_token"].(string)
|
||||
if !ok {
|
||||
// Not authenticated; redirect to login
|
||||
http.Redirect(w, r, "/auth/login", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
idToken, _, err := verifyTokenAndGetClaims(config, r.Context(), rawIDToken)
|
||||
if err != nil {
|
||||
session.Options.MaxAge = -1
|
||||
session.Save(r, w)
|
||||
http.Redirect(w, r, "/auth/login", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Add token to context
|
||||
ctx := context.WithValue(r.Context(), "id_token", idToken)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
return middleware, nil
|
||||
}
|
||||
48
internal/api/middleware/pagination.go
Normal file
48
internal/api/middleware/pagination.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Pagination struct {
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
const PaginationContextKey = "pagination"
|
||||
|
||||
func PaginationMiddleware() func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
page := 1
|
||||
pageSize := 20
|
||||
|
||||
if p := r.URL.Query().Get("page"); p != "" {
|
||||
if parsed, err := strconv.Atoi(p); err == nil && parsed >= 1 {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
|
||||
if ps := r.URL.Query().Get("page_size"); ps != "" {
|
||||
if parsed, err := strconv.Atoi(ps); err == nil && parsed >= 1 {
|
||||
pageSize = parsed
|
||||
}
|
||||
}
|
||||
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), PaginationContextKey, Pagination{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
})
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
36
internal/api/requests/module.go
Normal file
36
internal/api/requests/module.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package views_api
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"clintonambulance.com/calculate_negative_points/internal/api/middleware"
|
||||
"clintonambulance.com/calculate_negative_points/internal/api/requests/negative_points_processor"
|
||||
"clintonambulance.com/calculate_negative_points/internal/api/requests/users"
|
||||
"clintonambulance.com/calculate_negative_points/internal/config"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/swaggest/rest/nethttp"
|
||||
"github.com/swaggest/rest/web"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func MountInternalApiEndpoints(e *web.Service, config *config.ApplicationConfig, logger *zap.Logger) {
|
||||
e.Route("/api", func(r chi.Router) {
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"http://localhost:3000"},
|
||||
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
|
||||
AllowedHeaders: []string{"*"},
|
||||
AllowCredentials: true,
|
||||
}))
|
||||
currentUserMiddlware, err := middleware.CurrentUserMiddleware(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
r.Use(currentUserMiddlware)
|
||||
|
||||
r.Method(http.MethodGet, "/users/current", nethttp.NewHandler(users.GetCurrentUser()))
|
||||
r.Method(http.MethodPost, "/process", nethttp.NewHandler(negative_points_processor.NegativePointsProcessor(config, logger)))
|
||||
r.With(middleware.Logout(config)).Method(http.MethodDelete, "/users/current", nethttp.NewHandler(users.Logout()))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package negative_points_processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"clintonambulance.com/calculate_negative_points/internal/api/middleware"
|
||||
"clintonambulance.com/calculate_negative_points/internal/config"
|
||||
"clintonambulance.com/calculate_negative_points/internal/nocodb"
|
||||
"clintonambulance.com/calculate_negative_points/internal/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/swaggest/usecase"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type negativePointsProcessorInput struct {
|
||||
File *multipart.FileHeader `formData:"file" description:"XLS schedule file to process"`
|
||||
}
|
||||
|
||||
type negativePointsProcessorOutput struct {
|
||||
Employees []string `json:"employees" description:"List of employees who had negative points" nullable:"false" required:"true"`
|
||||
}
|
||||
|
||||
func hasMatch(record nocodb.NocoDBRecord, candidates []string, threshold int) bool {
|
||||
normalizedName := utils.NormalizeName(utils.ToTitleCase(record.Name))
|
||||
bestDistance := threshold + 1
|
||||
|
||||
for _, candidate := range candidates {
|
||||
normalizedCandidate := utils.NormalizeName(candidate)
|
||||
distance := utils.LevenshteinDistance(normalizedName, normalizedCandidate)
|
||||
|
||||
if distance < bestDistance {
|
||||
bestDistance = distance
|
||||
}
|
||||
}
|
||||
|
||||
if bestDistance <= threshold {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NegativePointsProcessor(config *config.ApplicationConfig, logger *zap.Logger) usecase.Interactor {
|
||||
u := usecase.NewInteractor(func(ctx context.Context, input negativePointsProcessorInput, output *negativePointsProcessorOutput) error {
|
||||
ctxUser := ctx.Value("claims").(middleware.Claims)
|
||||
|
||||
file, err := input.File.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
employees, err := utils.ParseUploadedXLSFile(input.File)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
records, err := nocodb.Fetch(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
employees = lo.Filter(employees, func(e utils.Employee, _ int) bool { return e.Worked() })
|
||||
|
||||
names := lo.Map(employees, func(e utils.Employee, _ int) string { return e.Name })
|
||||
|
||||
// Convert XLS names to same format as API names (FirstName LastName) and normalize casing
|
||||
xlsNamesConverted := lo.Map(names, func(name string, _ int) string {
|
||||
return utils.ToTitleCase(utils.ConvertNameFormat(name))
|
||||
})
|
||||
|
||||
overlaps := lo.Filter(records, func(r nocodb.NocoDBRecord, _ int) bool {
|
||||
return hasMatch(r, xlsNamesConverted, config.MatchThreshold)
|
||||
})
|
||||
|
||||
currentNocodbUser, found := lo.Find(records, func(r nocodb.NocoDBRecord) bool { return hasMatch(r, []string{ctxUser.Name}, config.MatchThreshold) })
|
||||
|
||||
if !found {
|
||||
return errors.New("Unable to match API user to NocoDB user")
|
||||
}
|
||||
|
||||
requestObjects := lo.Map(overlaps, func(r nocodb.NocoDBRecord, _ int) nocodb.NocoDBRequest {
|
||||
return nocodb.NocoDBRequest{
|
||||
EmployeeId: r.ID,
|
||||
ReportedBy: currentNocodbUser.ID,
|
||||
InfractionId: config.NocoDBConfig.NegativeInfractionId,
|
||||
Date: utils.FirstDayOfMonth(),
|
||||
}
|
||||
})
|
||||
|
||||
// Marshal request objects to JSON
|
||||
jsonData, err := json.Marshal(requestObjects)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create POST request
|
||||
req, err := http.NewRequest("POST", nocodb.AddInfractionsUrl(config), strings.NewReader(string(jsonData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add authorization header
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", config.NocoDBConfig.ApiToken.Value()))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Make the request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check response status
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Printf("Request failed with status %d: %s\n", resp.StatusCode, string(body))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
output.Employees = lo.Map(overlaps, func(r nocodb.NocoDBRecord, _ int) string { return r.Name })
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
u.SetDescription("Process Negative Points")
|
||||
u.SetTags("Negative Points Processor")
|
||||
|
||||
return u
|
||||
}
|
||||
31
internal/api/requests/users/current.go
Normal file
31
internal/api/requests/users/current.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/types"
|
||||
|
||||
"clintonambulance.com/calculate_negative_points/internal/api/middleware"
|
||||
internal_types "clintonambulance.com/calculate_negative_points/internal/types"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/samber/lo"
|
||||
"github.com/swaggest/usecase"
|
||||
)
|
||||
|
||||
func GetCurrentUser() usecase.Interactor {
|
||||
u := usecase.NewInteractor(func(ctx context.Context, input types.Nil, output *internal_types.UiUserResponse) error {
|
||||
ctxId := ctx.Value("user").(*oidc.IDToken)
|
||||
ctxUser := ctx.Value("claims").(middleware.Claims)
|
||||
|
||||
if lo.IsNotEmpty(ctxId.Issuer) {
|
||||
output.Item = internal_types.UiUser{
|
||||
Name: ctxUser.Name,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
u.SetDescription("Retrieve the current user")
|
||||
u.SetTags("Users")
|
||||
return u
|
||||
}
|
||||
17
internal/api/requests/users/logout.go
Normal file
17
internal/api/requests/users/logout.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/types"
|
||||
|
||||
"github.com/swaggest/usecase"
|
||||
)
|
||||
|
||||
func Logout() usecase.Interactor {
|
||||
u := usecase.NewInteractor(func(ctx context.Context, input types.Nil, output *map[string]interface{}) error {
|
||||
return nil
|
||||
})
|
||||
u.SetDescription("Logout current user")
|
||||
u.SetTags("Users")
|
||||
return u
|
||||
}
|
||||
Reference in New Issue
Block a user