2020-09-12 17:09:23 +03:00
package v1
import (
"github.com/gorilla/mux"
2020-11-08 12:12:49 +03:00
conf "github.com/muety/wakapi/config"
2020-09-12 17:09:23 +03:00
"github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/shields/v1"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
"net/http"
"regexp"
2020-09-12 17:58:19 +03:00
"strings"
2021-02-07 00:32:03 +03:00
"time"
2020-09-12 17:09:23 +03:00
)
const (
intervalPattern = ` interval:([a-z0-9_]+) `
entityFilterPattern = ` (project|os|editor|language|machine):([_a-zA-Z0-9-]+) `
)
type BadgeHandler struct {
2020-11-08 12:12:49 +03:00
config * conf . Config
userSrvc services . IUserService
summarySrvc services . ISummaryService
2020-09-12 17:09:23 +03:00
}
2020-11-08 12:12:49 +03:00
func NewBadgeHandler ( summaryService services . ISummaryService , userService services . IUserService ) * BadgeHandler {
2020-09-12 17:09:23 +03:00
return & BadgeHandler {
summarySrvc : summaryService ,
userSrvc : userService ,
2020-11-08 12:12:49 +03:00
config : conf . Get ( ) ,
2020-09-12 17:09:23 +03:00
}
}
2021-02-03 23:28:02 +03:00
func ( h * BadgeHandler ) RegisterRoutes ( router * mux . Router ) {
2021-02-06 22:09:08 +03:00
// no auth middleware here, handler itself resolves the user
2021-02-07 00:40:54 +03:00
r := router . PathPrefix ( "/compat/shields/v1/{user}" ) . Subrouter ( )
2021-02-03 23:28:02 +03:00
r . Methods ( http . MethodGet ) . HandlerFunc ( h . Get )
2021-01-30 12:34:52 +03:00
}
2021-02-07 13:54:07 +03:00
// @Summary Get badge data
// @Description Retrieve total time for a given entity (e.g. a project) within a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint). Requires public data access to be allowed.
// @ID get-badge
// @Tags badges
// @Produce json
// @Param user path string true "User ID to fetch data for"
// @Param interval path string true "Interval to aggregate data for" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
// @Param filter path string true "Filter to apply (e.g. 'project:wakapi' or 'language:Go')"
// @Success 200 {object} v1.BadgeData
// @Router /compat/shields/v1/{user}/{interval}/{filter} [get]
2021-02-03 23:28:02 +03:00
func ( h * BadgeHandler ) Get ( w http . ResponseWriter , r * http . Request ) {
2020-09-12 17:09:23 +03:00
intervalReg := regexp . MustCompile ( intervalPattern )
entityFilterReg := regexp . MustCompile ( entityFilterPattern )
2020-09-12 17:58:19 +03:00
if userAgent := r . Header . Get ( "user-agent" ) ; ! strings . HasPrefix ( userAgent , "Shields.io/" ) && ! h . config . IsDev ( ) {
w . WriteHeader ( http . StatusForbidden )
return
}
2020-09-12 17:09:23 +03:00
var filterEntity , filterKey string
if groups := entityFilterReg . FindStringSubmatch ( r . URL . Path ) ; len ( groups ) > 2 {
filterEntity , filterKey = groups [ 1 ] , groups [ 2 ]
}
var interval = models . IntervalPast30Days
if groups := intervalReg . FindStringSubmatch ( r . URL . Path ) ; len ( groups ) > 1 {
2021-02-06 21:52:50 +03:00
if i , err := utils . ParseInterval ( groups [ 1 ] ) ; err == nil {
interval = i
}
2020-09-12 17:09:23 +03:00
}
2021-02-07 00:32:03 +03:00
requestedUserId := mux . Vars ( r ) [ "user" ]
user , err := h . userSrvc . GetUserById ( requestedUserId )
if err != nil {
w . WriteHeader ( http . StatusNotFound )
return
}
_ , rangeFrom , rangeTo := utils . ResolveInterval ( interval )
minStart := utils . StartOfDay ( rangeTo . Add ( - 24 * time . Hour * time . Duration ( user . ShareDataMaxDays ) ) )
2021-02-07 01:23:26 +03:00
// negative value means no limit
if rangeFrom . Before ( minStart ) && user . ShareDataMaxDays >= 0 {
2021-02-07 00:32:03 +03:00
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( "requested time range too broad" ) )
return
}
2020-11-01 14:50:59 +03:00
var filters * models . Filters
2020-09-12 17:09:23 +03:00
switch filterEntity {
case "project" :
2020-11-01 14:50:59 +03:00
filters = models . NewFiltersWith ( models . SummaryProject , filterKey )
2020-09-12 17:09:23 +03:00
case "os" :
2020-11-01 14:50:59 +03:00
filters = models . NewFiltersWith ( models . SummaryOS , filterKey )
2020-09-12 17:09:23 +03:00
case "editor" :
2020-11-01 14:50:59 +03:00
filters = models . NewFiltersWith ( models . SummaryEditor , filterKey )
2020-09-12 17:09:23 +03:00
case "language" :
2020-11-01 14:50:59 +03:00
filters = models . NewFiltersWith ( models . SummaryLanguage , filterKey )
2020-09-12 17:09:23 +03:00
case "machine" :
2020-11-01 14:50:59 +03:00
filters = models . NewFiltersWith ( models . SummaryMachine , filterKey )
2020-11-01 18:03:30 +03:00
default :
filters = & models . Filters { }
2020-09-12 17:09:23 +03:00
}
summary , err , status := h . loadUserSummary ( user , interval )
if err != nil {
w . WriteHeader ( status )
w . Write ( [ ] byte ( err . Error ( ) ) )
return
}
vm := v1 . NewBadgeDataFrom ( summary , filters )
utils . RespondJSON ( w , http . StatusOK , vm )
}
2021-02-06 21:52:50 +03:00
func ( h * BadgeHandler ) loadUserSummary ( user * models . User , interval * models . IntervalKey ) ( * models . Summary , error , int ) {
2020-09-12 17:09:23 +03:00
err , from , to := utils . ResolveInterval ( interval )
if err != nil {
return nil , err , http . StatusBadRequest
}
summaryParams := & models . SummaryParams {
From : from ,
To : to ,
User : user ,
}
2020-11-07 14:01:35 +03:00
var retrieveSummary services . SummaryRetriever = h . summarySrvc . Retrieve
if summaryParams . Recompute {
retrieveSummary = h . summarySrvc . Summarize
}
summary , err := h . summarySrvc . Aliased ( summaryParams . From , summaryParams . To , summaryParams . User , retrieveSummary )
2020-09-12 17:09:23 +03:00
if err != nil {
return nil , err , http . StatusInternalServerError
}
return summary , nil , http . StatusOK
}