github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/interface/http/util.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"html/template"
     8  	"net/http"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/morikuni/failure"
    15  
    16  	"github.com/oinume/lekcije/backend/context_data"
    17  	"github.com/oinume/lekcije/backend/domain/config"
    18  	"github.com/oinume/lekcije/backend/errors"
    19  	"github.com/oinume/lekcije/backend/usecase"
    20  	"github.com/oinume/lekcije/backend/util"
    21  )
    22  
    23  const (
    24  	APITokenCookieName   = "apiToken"
    25  	TrackingIDCookieName = "trackingId"
    26  )
    27  
    28  func TemplateDir() string {
    29  	koDataPath := os.Getenv("KO_DATA_PATH")
    30  	if koDataPath != "" {
    31  		return filepath.Join(koDataPath, "html")
    32  	}
    33  	wd, err := os.Getwd()
    34  	if err != nil {
    35  		panic(err)
    36  	}
    37  	_, file := filepath.Split(wd)
    38  	switch file {
    39  	case "backend":
    40  		return filepath.Join(wd, "..", "frontend", "html")
    41  	default:
    42  		return filepath.Join(wd, "frontend", "html")
    43  	}
    44  }
    45  
    46  func TemplatePath(file string) string {
    47  	return path.Join(TemplateDir(), file)
    48  }
    49  
    50  func ParseHTMLTemplates(files ...string) *template.Template {
    51  	f := []string{
    52  		TemplatePath("_base.html"),
    53  	}
    54  	f = append(f, files...)
    55  	return template.Must(template.ParseFiles(f...))
    56  }
    57  
    58  func internalServerError(ctx context.Context, errorRecorder *usecase.ErrorRecorder, w http.ResponseWriter, err error, userID uint32) {
    59  	//switch _ := errors.Cause(err).(type) { // TODO:
    60  	//default:
    61  	// unknown error
    62  	sUserID := ""
    63  	if userID == 0 {
    64  		sUserID = fmt.Sprint(userID)
    65  	}
    66  	errorRecorder.Record(ctx, err, sUserID)
    67  	http.Error(w, fmt.Sprintf("Internal Server Error\n\n%v", err), http.StatusInternalServerError)
    68  	if !config.IsProductionEnv() {
    69  		fmt.Fprintf(w, "----- stacktrace -----\n")
    70  		if e, ok := err.(errors.StackTracer); ok {
    71  			for _, f := range e.StackTrace() {
    72  				_, _ = fmt.Fprintf(w, "%+v\n", f)
    73  			}
    74  		}
    75  		if callStack, ok := failure.CallStackOf(err); ok {
    76  			for _, f := range callStack.Frames() {
    77  				_, _ = fmt.Fprintf(w, "[%s] %v:%v\n", f.Func(), f.File(), f.Line())
    78  			}
    79  		}
    80  	}
    81  }
    82  
    83  func writeJSON(w http.ResponseWriter, code int, body interface{}) {
    84  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
    85  	//w.Header().Set("X-Content-Type-Options", "nosniff")
    86  	w.WriteHeader(code)
    87  	if err := json.NewEncoder(w).Encode(body); err != nil {
    88  		http.Error(w, `{ "status": "Failed to Encode as writeJSON" }`, http.StatusInternalServerError)
    89  		return
    90  	}
    91  }
    92  
    93  type commonTemplateData struct {
    94  	StaticURL         string
    95  	GoogleAnalyticsID string
    96  	CurrentURL        string
    97  	CanonicalURL      string
    98  	TrackingID        string
    99  	IsUserAgentPC     bool
   100  	IsUserAgentSP     bool
   101  	IsUserAgentTablet bool
   102  	UserID            string
   103  	NavigationItems   []navigationItem
   104  	ServiceEnv        string
   105  }
   106  
   107  type navigationItem struct {
   108  	Text      string
   109  	URL       string
   110  	NewWindow bool
   111  }
   112  
   113  var loggedInNavigationItems = []navigationItem{
   114  	{"ホーム", "/me", false},
   115  	{"設定", "/me/setting", false},
   116  	{"お問い合わせ", "https://goo.gl/forms/CIGO3kpiQCGjtFD42", true},
   117  	{"ログアウト", "/me/logout", false},
   118  }
   119  
   120  var loggedOutNavigationItems = []navigationItem{
   121  	{"ホーム", "/", false},
   122  }
   123  
   124  func (s *server) getCommonTemplateData(req *http.Request, loggedIn bool, userID uint32) commonTemplateData {
   125  	canonicalURL := fmt.Sprintf("%s://%s%s", config.WebURLScheme(req), req.Host, req.RequestURI)
   126  	canonicalURL = (strings.SplitN(canonicalURL, "?", 2))[0] // TODO: use url.Parse
   127  	data := commonTemplateData{
   128  		StaticURL:         config.StaticURL(),
   129  		GoogleAnalyticsID: config.DefaultVars.GoogleAnalyticsID,
   130  		CurrentURL:        req.RequestURI,
   131  		CanonicalURL:      canonicalURL,
   132  		IsUserAgentPC:     util.IsUserAgentPC(req),
   133  		IsUserAgentSP:     util.IsUserAgentSP(req),
   134  		IsUserAgentTablet: util.IsUserAgentTablet(req),
   135  		ServiceEnv:        config.DefaultVars.ServiceEnv,
   136  	}
   137  
   138  	if loggedIn {
   139  		data.NavigationItems = loggedInNavigationItems
   140  	} else {
   141  		data.NavigationItems = loggedOutNavigationItems
   142  	}
   143  	data.TrackingID = context_data.MustTrackingID(req.Context())
   144  	if userID != 0 {
   145  		data.UserID = fmt.Sprint(userID)
   146  	}
   147  
   148  	return data
   149  }
   150  
   151  func getRemoteAddress(req *http.Request) string {
   152  	xForwardedFor := req.Header.Get("X-Forwarded-For")
   153  	if xForwardedFor == "" {
   154  		return (strings.Split(req.RemoteAddr, ":"))[0]
   155  	}
   156  	return strings.TrimSpace((strings.Split(xForwardedFor, ","))[0])
   157  }