auth/api.go
auth/api.go
}
}
+type AccessLogReq struct {
+ RemoteIP string `json:"remote_ip"`
+ RemotePort string `json:"remote_port"`
+ ClientIP string `json:"client_ip"`
+ Method string `json:"method"`
+ Host string `json:"host"`
+ Uri string `json:"uri"`
+ Headers struct {
+ UserAgent string `json:"User-Agent"`
+ Referer string `json:"Referer"`
+ } `json:"headers"`
+ Tls struct {
+ ServerName string `json:"server_name"`
+ } `json:"tls"`
+}
+
+type CaddyAccessLog struct {
+ Request AccessLogReq `json:"request"`
+ Status int `json:"status"`
+}
+
+func deserializeCaddyAccessLog(dbpool db.DB, access *CaddyAccessLog) (*db.AnalyticsVisits, error) {
+ spaceRaw := strings.SplitN(access.Request.Tls.ServerName, ".", 2)
+ space := spaceRaw[0]
+ host := access.Request.Host
+ path := access.Request.Uri
+ subdomain := ""
+
+ // grab subdomain based on host
+ if strings.HasSuffix(host, "tuns.sh") {
+ subdomain = strings.TrimSuffix(host, ".tuns.sh")
+ } else if strings.HasSuffix(host, "pgs.sh") {
+ subdomain = strings.TrimSuffix(host, ".pgs.sh")
+ } else if strings.HasSuffix(host, "prose.sh") {
+ subdomain = strings.TrimSuffix(host, ".prose.sh")
+ } else {
+ subdomain = shared.GetCustomDomain(host, space)
+ }
+
+ // get user and namespace details from subdomain
+ props, err := shared.GetProjectFromSubdomain(subdomain)
+ if err != nil {
+ return nil, err
+ }
+ // get user ID
+ user, err := dbpool.FindUserForName(props.Username)
+ if err != nil {
+ return nil, err
+ }
+
+ projectID := ""
+ postID := ""
+ if space == "pgs" { // figure out project ID
+ project, err := dbpool.FindProjectByName(user.ID, props.ProjectName)
+ if err != nil {
+ return nil, err
+ }
+ projectID = project.ID
+ } else if space == "prose" { // figure out post ID
+ if path == "" || path == "/" {
+ } else {
+ post, err := dbpool.FindPostWithSlug(path, user.ID, space)
+ if err != nil {
+ return nil, err
+ }
+ postID = post.ID
+ }
+ }
+
+ return &db.AnalyticsVisits{
+ UserID: user.ID,
+ ProjectID: projectID,
+ PostID: postID,
+ Namespace: space,
+ Host: host,
+ Path: path,
+ IpAddress: access.Request.ClientIP,
+ UserAgent: access.Request.Headers.UserAgent,
+ Referer: access.Request.Headers.Referer, // TODO: I don't see referer in the access log
+ Status: access.Status,
+ }, nil
+}
+
func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secret string) {
conn := shared.NewPicoPipeClient()
stdoutPipe, err := pubsub.RemoteSub("sub metric-drain -k", ctx, conn)
scanner := bufio.NewScanner(stdoutPipe)
for scanner.Scan() {
line := scanner.Text()
- visit := db.AnalyticsVisits{}
- err := json.Unmarshal([]byte(line), &visit)
+ accessLog := CaddyAccessLog{}
+ err := json.Unmarshal([]byte(line), &accessLog)
if err != nil {
logger.Error("json unmarshal", "err", err)
continue
}
- err = shared.AnalyticsVisitFromVisit(&visit, dbpool, secret)
+ visit, err := deserializeCaddyAccessLog(dbpool, &accessLog)
+ if err != nil {
+ logger.Error("cannot deserialize access log", "err", err)
+ continue
+ }
+ err = shared.AnalyticsVisitFromVisit(visit, dbpool, secret)
if err != nil {
if !errors.Is(err, shared.ErrAnalyticsDisabled) {
logger.Info("could not record analytics visit", "reason", err)
}
logger.Info("inserting visit", "visit", visit)
- err = dbpool.InsertVisit(&visit)
+ err = dbpool.InsertVisit(visit)
if err != nil {
logger.Error("could not insert visit record", "err", err)
}
auth/api.go
auth/api.go
"log/slog"
"net/http"
"net/url"
+ "strings"
"time"
"github.com/gorilla/feeds"
}
}
+type AccessLogReq struct {
+ RemoteIP string `json:"remote_ip"`
+ RemotePort string `json:"remote_port"`
+ ClientIP string `json:"client_ip"`
+ Method string `json:"method"`
+ Host string `json:"host"`
+ Uri string `json:"uri"`
+ Headers struct {
+ UserAgent string `json:"User-Agent"`
+ Referer string `json:"Referer"`
+ } `json:"headers"`
+ Tls struct {
+ ServerName string `json:"server_name"`
+ } `json:"tls"`
+}
+
+type CaddyAccessLog struct {
+ Request AccessLogReq `json:"request"`
+ Status int `json:"status"`
+}
+
+func deserializeCaddyAccessLog(dbpool db.DB, access *CaddyAccessLog) (*db.AnalyticsVisits, error) {
+ spaceRaw := strings.SplitN(access.Request.Tls.ServerName, ".", 2)
+ space := spaceRaw[0]
+ host := access.Request.Host
+ path := access.Request.Uri
+ subdomain := ""
+
+ // grab subdomain based on host
+ if strings.HasSuffix(host, "tuns.sh") {
+ subdomain = strings.TrimSuffix(host, ".tuns.sh")
+ } else if strings.HasSuffix(host, "pgs.sh") {
+ subdomain = strings.TrimSuffix(host, ".pgs.sh")
+ } else if strings.HasSuffix(host, "prose.sh") {
+ subdomain = strings.TrimSuffix(host, ".prose.sh")
+ } else {
+ subdomain = shared.GetCustomDomain(host, space)
+ }
+
+ // get user and namespace details from subdomain
+ props, err := shared.GetProjectFromSubdomain(subdomain)
+ if err != nil {
+ return nil, err
+ }
+ // get user ID
+ user, err := dbpool.FindUserForName(props.Username)
+ if err != nil {
+ return nil, err
+ }
+
+ projectID := ""
+ postID := ""
+ if space == "pgs" { // figure out project ID
+ project, err := dbpool.FindProjectByName(user.ID, props.ProjectName)
+ if err != nil {
+ return nil, err
+ }
+ projectID = project.ID
+ } else if space == "prose" { // figure out post ID
+ if path == "" || path == "/" {
+ } else {
+ post, err := dbpool.FindPostWithSlug(path, user.ID, space)
+ if err != nil {
+ return nil, err
+ }
+ postID = post.ID
+ }
+ }
+
+ return &db.AnalyticsVisits{
+ UserID: user.ID,
+ ProjectID: projectID,
+ PostID: postID,
+ Namespace: space,
+ Host: host,
+ Path: path,
+ IpAddress: access.Request.ClientIP,
+ UserAgent: access.Request.Headers.UserAgent,
+ Referer: access.Request.Headers.Referer, // TODO: I don't see referer in the access log
+ Status: access.Status,
+ }, nil
+}
+
func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secret string) {
drain := metrics.ReconnectReadMetrics(
ctx,
-1,
)
- for {
- scanner := bufio.NewScanner(drain)
- for scanner.Scan() {
- line := scanner.Text()
- visit := db.AnalyticsVisits{}
- err := json.Unmarshal([]byte(line), &visit)
- if err != nil {
- logger.Error("json unmarshal", "err", err)
- continue
- }
-
- user := slog.Any("userId", visit.UserID)
-
- err = shared.AnalyticsVisitFromVisit(&visit, dbpool, secret)
- if err != nil {
- if !errors.Is(err, shared.ErrAnalyticsDisabled) {
- logger.Info("could not record analytics visit", "reason", err, "visit", visit, user)
- continue
- }
- }
+ scanner := bufio.NewScanner(drain)
+ for scanner.Scan() {
+ line := scanner.Text()
+ accessLog := CaddyAccessLog{}
+ err := json.Unmarshal([]byte(line), &accessLog)
+ if err != nil {
+ logger.Error("json unmarshal", "err", err)
+ continue
+ }
- logger.Info("inserting visit", "visit", visit, user)
- err = dbpool.InsertVisit(&visit)
- if err != nil {
- logger.Error("could not insert visit record", "err", err, "visit", visit, user)
+ visit, err := deserializeCaddyAccessLog(dbpool, &accessLog)
+ if err != nil {
+ logger.Error("cannot deserialize access log", "err", err)
+ continue
+ }
+ err = shared.AnalyticsVisitFromVisit(visit, dbpool, secret)
+ if err != nil {
+ if !errors.Is(err, shared.ErrAnalyticsDisabled) {
+ logger.Info("could not record analytics visit", "reason", err)
}
}
- if scanner.Err() != nil {
- logger.Error("scanner error", "err", scanner.Err())
+ logger.Info("inserting visit", "visit", visit)
+ err = dbpool.InsertVisit(visit)
+ if err != nil {
+ logger.Error("could not insert visit record", "err", err)
}
}
}