mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 07:42:26 -05:00 
			
		
		
		
	some lil fixes for kibou compatibility
This commit is contained in:
		
					parent
					
						
							
								6994859d03
							
						
					
				
			
			
				commit
				
					
						d9d9a7a626
					
				
			
		
					 12 changed files with 102 additions and 36 deletions
				
			
		|  | @ -47,12 +47,13 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) { | ||||||
| 			c.JSON(withCode.Code(), withCode.Safe()) | 			c.JSON(withCode.Code(), withCode.Safe()) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		l.Debug(err) | 		l.Debugf("InboxPOSTHandler: error processing request: %s", err) | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !posted { | 	if !posted { | ||||||
|  | 		l.Debugf("request could not be handled as an AP request; headers were: %+v", c.Request.Header) | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -24,42 +24,53 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org | // WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org | ||||||
| func (m *Module) WebfingerGETRequest(c *gin.Context) { | func (m *Module) WebfingerGETRequest(c *gin.Context) { | ||||||
|  | 	l := m.log.WithFields(logrus.Fields{ | ||||||
|  | 		"func": "WebfingerGETRequest", | ||||||
|  | 		"user-agent": c.Request.UserAgent(), | ||||||
|  | 	}) | ||||||
| 
 | 
 | ||||||
| 	q, set := c.GetQuery("resource") | 	q, set := c.GetQuery("resource") | ||||||
| 	if !set || q == "" { | 	if !set || q == "" { | ||||||
|  | 		l.Debug("aborting request because no resource was set in query") | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "no 'resource' in request query"}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": "no 'resource' in request query"}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	withAcct := strings.Split(q, "acct:") | 	withAcct := strings.Split(q, "acct:") | ||||||
| 	if len(withAcct) != 2 { | 	if len(withAcct) != 2 { | ||||||
|  | 		l.Debugf("aborting request because resource query %s could not be split by 'acct:'", q) | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	usernameDomain := strings.Split(withAcct[1], "@") | 	usernameDomain := strings.Split(withAcct[1], "@") | ||||||
| 	if len(usernameDomain) != 2 { | 	if len(usernameDomain) != 2 { | ||||||
|  | 		l.Debugf("aborting request because username and domain could not be parsed from %s", withAcct[1]) | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	username := strings.ToLower(usernameDomain[0]) | 	username := strings.ToLower(usernameDomain[0]) | ||||||
| 	domain := strings.ToLower(usernameDomain[1]) | 	domain := strings.ToLower(usernameDomain[1]) | ||||||
| 	if username == "" || domain == "" { | 	if username == "" || domain == "" { | ||||||
|  | 		l.Debug("aborting request because username or domain was empty") | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if domain != m.config.Host { | 	if domain != m.config.Host { | ||||||
|  | 		l.Debug("aborting request because domain %s does not belong to this instance", domain) | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("domain %s does not belong to this instance", domain)}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("domain %s does not belong to this instance", domain)}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	resp, err := m.processor.GetWebfingerAccount(username, c.Request) | 	resp, err := m.processor.GetWebfingerAccount(username, c.Request) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		l.Debugf("aborting request with an error: %s", err.Error()) | ||||||
| 		c.JSON(err.Code(), gin.H{"error": err.Safe()}) | 		c.JSON(err.Code(), gin.H{"error": err.Safe()}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								internal/api/security/robots.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								internal/api/security/robots.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | package security | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const robotsString = `User-agent: * | ||||||
|  | Disallow: / | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | // RobotsGETHandler returns the most restrictive possible robots.txt file in response to a call to /robots.txt. | ||||||
|  | // The response instructs bots with *any* user agent not to index the instance at all. | ||||||
|  | func (m *Module) RobotsGETHandler(c *gin.Context) { | ||||||
|  | 	c.String(http.StatusOK, robotsString) | ||||||
|  | } | ||||||
|  | @ -19,12 +19,16 @@ | ||||||
| package security | package security | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api" | 	"github.com/superseriousbusiness/gotosocial/internal/api" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/router" | 	"github.com/superseriousbusiness/gotosocial/internal/router" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | const robotsPath = "/robots.txt" | ||||||
|  | 
 | ||||||
| // Module implements the ClientAPIModule interface for security middleware | // Module implements the ClientAPIModule interface for security middleware | ||||||
| type Module struct { | type Module struct { | ||||||
| 	config *config.Config | 	config *config.Config | ||||||
|  | @ -44,5 +48,6 @@ func (m *Module) Route(s router.Router) error { | ||||||
| 	s.AttachMiddleware(m.FlocBlock) | 	s.AttachMiddleware(m.FlocBlock) | ||||||
| 	s.AttachMiddleware(m.ExtraHeaders) | 	s.AttachMiddleware(m.ExtraHeaders) | ||||||
| 	s.AttachMiddleware(m.UserAgentBlock) | 	s.AttachMiddleware(m.UserAgentBlock) | ||||||
|  | 	s.AttachHandler(http.MethodGet, robotsPath, m.RobotsGETHandler) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -23,20 +23,24 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // UserAgentBlock is a middleware that prevents google chrome cohort tracking by | // UserAgentBlock blocks requests with undesired, empty, or invalid user-agent strings. | ||||||
| // writing the Permissions-Policy header after all other parts of the request have been completed. |  | ||||||
| // See: https://plausible.io/blog/google-floc |  | ||||||
| func (m *Module) UserAgentBlock(c *gin.Context) { | func (m *Module) UserAgentBlock(c *gin.Context) { | ||||||
|  | 	l := m.log.WithFields(logrus.Fields{ | ||||||
|  | 		"func": "UserAgentBlock", | ||||||
|  | 	}) | ||||||
| 
 | 
 | ||||||
| 	ua := c.Request.UserAgent() | 	ua := c.Request.UserAgent() | ||||||
| 	if ua == "" { | 	if ua == "" { | ||||||
|  | 		l.Debug("aborting request because there's no user-agent set") | ||||||
| 		c.AbortWithStatus(http.StatusTeapot) | 		c.AbortWithStatus(http.StatusTeapot) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if strings.Contains(strings.ToLower(c.Request.UserAgent()), strings.ToLower("friendica")) { | 	if strings.Contains(strings.ToLower(ua), strings.ToLower("friendica")) { | ||||||
|  | 		l.Debugf("aborting request with user-agent %s because it contains 'friendica'", ua) | ||||||
| 		c.AbortWithStatus(http.StatusTeapot) | 		c.AbortWithStatus(http.StatusTeapot) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import ( | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/go-fed/activity/streams/vocab" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
|  | @ -58,7 +59,43 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for iter := acceptObject.Begin(); iter != acceptObject.End(); iter = iter.Next() { | 	for iter := acceptObject.Begin(); iter != acceptObject.End(); iter = iter.Next() { | ||||||
|  | 		// check if the object is an IRI | ||||||
|  | 		if iter.IsIRI() { | ||||||
|  | 			// we have just the URI of whatever is being accepted, so we need to find out what it is | ||||||
|  | 			acceptedObjectIRI := iter.GetIRI() | ||||||
|  | 			if util.IsFollowPath(acceptedObjectIRI) { | ||||||
|  | 				// ACCEPT FOLLOW | ||||||
|  | 				gtsFollowRequest := >smodel.FollowRequest{} | ||||||
|  | 				if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: acceptedObjectIRI.String()}}, gtsFollowRequest); err != nil { | ||||||
|  | 					return fmt.Errorf("ACCEPT: couldn't get follow request with id %s from the database: %s", acceptedObjectIRI.String(), err) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// make sure the addressee of the original follow is the same as whatever inbox this landed in | ||||||
|  | 				if gtsFollowRequest.AccountID != inboxAcct.ID { | ||||||
|  | 					return errors.New("ACCEPT: follow object account and inbox account were not the same") | ||||||
|  | 				} | ||||||
|  | 				follow, err := f.db.AcceptFollowRequest(gtsFollowRequest.AccountID, gtsFollowRequest.TargetAccountID) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				fromFederatorChan <- gtsmodel.FromFederator{ | ||||||
|  | 					APObjectType:     gtsmodel.ActivityStreamsFollow, | ||||||
|  | 					APActivityType:   gtsmodel.ActivityStreamsAccept, | ||||||
|  | 					GTSModel:         follow, | ||||||
|  | 					ReceivingAccount: inboxAcct, | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// check if iter is an AP object / type | ||||||
|  | 		if iter.GetType() == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		switch iter.GetType().GetTypeName() { | 		switch iter.GetType().GetTypeName() { | ||||||
|  | 			// we have the whole object so we can figure out what we're accepting | ||||||
| 		case string(gtsmodel.ActivityStreamsFollow): | 		case string(gtsmodel.ActivityStreamsFollow): | ||||||
| 			// ACCEPT FOLLOW | 			// ACCEPT FOLLOW | ||||||
| 			asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) | 			asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) | ||||||
|  |  | ||||||
|  | @ -164,7 +164,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 			return fmt.Errorf("could not convert Like to fave: %s", err) | 			return fmt.Errorf("could not convert Like to fave: %s", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		newID, err := id.NewULIDFromTime(fave.CreatedAt) | 		newID, err := id.NewULID() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -48,6 +48,9 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() { | 	for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() { | ||||||
|  | 		if iter.GetType() == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		switch iter.GetType().GetTypeName() { | 		switch iter.GetType().GetTypeName() { | ||||||
| 		case string(gtsmodel.ActivityStreamsFollow): | 		case string(gtsmodel.ActivityStreamsFollow): | ||||||
| 			// UNDO FOLLOW | 			// UNDO FOLLOW | ||||||
|  |  | ||||||
|  | @ -34,15 +34,6 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) { | func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) { | ||||||
| 	l := p.log.WithFields(logrus.Fields{ |  | ||||||
| 		"func":    "HomeTimelineGet", |  | ||||||
| 		"maxID":   maxID, |  | ||||||
| 		"sinceID": sinceID, |  | ||||||
| 		"minID":   minID, |  | ||||||
| 		"limit":   limit, |  | ||||||
| 		"local":   local, |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	resp := &apimodel.StatusTimelineResponse{ | 	resp := &apimodel.StatusTimelineResponse{ | ||||||
| 		Statuses: []*apimodel.Status{}, | 		Statuses: []*apimodel.Status{}, | ||||||
| 	} | 	} | ||||||
|  | @ -53,9 +44,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st | ||||||
| 	sinceIDMarker := sinceID | 	sinceIDMarker := sinceID | ||||||
| 	minIDMarker := minID | 	minIDMarker := minID | ||||||
| 
 | 
 | ||||||
| 	l.Debugf("\n entering grabloop \n") |  | ||||||
| 
 |  | ||||||
| 	l.Debugf("\n querying the db \n") |  | ||||||
| 	gtsStatuses, err := p.db.GetStatusesWhereFollowing(authed.Account.ID, maxIDMarker, sinceIDMarker, minIDMarker, limit, local) | 	gtsStatuses, err := p.db.GetStatusesWhereFollowing(authed.Account.ID, maxIDMarker, sinceIDMarker, minIDMarker, limit, local) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { | 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||||
|  | @ -64,18 +52,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, gtsStatus := range gtsStatuses { | 	for _, gtsStatus := range gtsStatuses { | ||||||
| 		// haveAlready := false |  | ||||||
| 		// for _, apiStatus := range apiStatuses { |  | ||||||
| 		// 	if apiStatus.ID == gtsStatus.ID { |  | ||||||
| 		// 		haveAlready = true |  | ||||||
| 		// 		break |  | ||||||
| 		// 	} |  | ||||||
| 		// } |  | ||||||
| 		// if haveAlready { |  | ||||||
| 		// 	l.Debugf("\n we have status with id %d already so continuing past this iteration of the loop \n", gtsStatus.ID) |  | ||||||
| 		// 	continue |  | ||||||
| 		// } |  | ||||||
| 
 |  | ||||||
| 		// pull relevant accounts from the status -- we need this both for checking visibility and for serializing | 		// pull relevant accounts from the status -- we need this both for checking visibility and for serializing | ||||||
| 		relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(gtsStatus) | 		relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(gtsStatus) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -103,7 +79,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			l.Debug("\n appending to the statuses slice \n") |  | ||||||
| 			apiStatuses = append(apiStatuses, apiStatus) | 			apiStatuses = append(apiStatuses, apiStatus) | ||||||
| 			sort.Slice(apiStatuses, func(i int, j int) bool { | 			sort.Slice(apiStatuses, func(i int, j int) bool { | ||||||
| 				is, err := time.Parse(time.RFC3339, apiStatuses[i].CreatedAt) | 				is, err := time.Parse(time.RFC3339, apiStatuses[i].CreatedAt) | ||||||
|  | @ -120,7 +95,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			if len(apiStatuses) == limit { | 			if len(apiStatuses) == limit { | ||||||
| 				l.Debugf("\n we have enough statuses, returning \n") |  | ||||||
| 				// we have enough | 				// we have enough | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
|  | @ -143,7 +117,7 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st | ||||||
| 			Host:     p.config.Host, | 			Host:     p.config.Host, | ||||||
| 			Path:     "/api/v1/timelines/home", | 			Path:     "/api/v1/timelines/home", | ||||||
| 			RawPath:  url.PathEscape("api/v1/timelines/home"), | 			RawPath:  url.PathEscape("api/v1/timelines/home"), | ||||||
| 			RawQuery: url.QueryEscape(fmt.Sprintf("limit=%d&max_id=%s", limit, apiStatuses[len(apiStatuses)-1].ID)), | 			RawQuery: fmt.Sprintf("limit=%d&max_id=%s", limit, apiStatuses[len(apiStatuses)-1].ID), | ||||||
| 		} | 		} | ||||||
| 		next := fmt.Sprintf("<%s>; rel=\"next\"", nextLink.String()) | 		next := fmt.Sprintf("<%s>; rel=\"next\"", nextLink.String()) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -117,10 +117,14 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo | ||||||
| 
 | 
 | ||||||
| 	// url property | 	// url property | ||||||
| 	url, err := extractURL(accountable) | 	url, err := extractURL(accountable) | ||||||
| 	if err != nil { | 	if err == nil { | ||||||
| 		return nil, fmt.Errorf("could not extract url for person with id %s: %s", uri.String(), err) | 		// take the URL if we can find it | ||||||
|  | 		acct.URL = url.String() | ||||||
|  | 	} else { | ||||||
|  | 		// otherwise just take the account URI as the URL | ||||||
|  | 		acct.URL = uri.String() | ||||||
| 	} | 	} | ||||||
| 	acct.URL = url.String() | 
 | ||||||
| 
 | 
 | ||||||
| 	// InboxURI | 	// InboxURI | ||||||
| 	if accountable.GetActivityStreamsInbox() != nil && accountable.GetActivityStreamsInbox().GetIRI() != nil { | 	if accountable.GetActivityStreamsInbox() != nil && accountable.GetActivityStreamsInbox().GetIRI() != nil { | ||||||
|  |  | ||||||
|  | @ -85,6 +85,11 @@ var ( | ||||||
| 	// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following | 	// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following | ||||||
| 	followingPathRegex = regexp.MustCompile(followingPathRegexString) | 	followingPathRegex = regexp.MustCompile(followingPathRegexString) | ||||||
| 
 | 
 | ||||||
|  | 	followPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, FollowPath, ulidRegexString) | ||||||
|  | 	// followPathRegex parses a path that validates and captures the username part and the ulid part | ||||||
|  | 	// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH | ||||||
|  | 	followPathRegex = regexp.MustCompile(followPathRegexString) | ||||||
|  | 
 | ||||||
| 	ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}` | 	ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}` | ||||||
| 
 | 
 | ||||||
| 	likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath) | 	likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath) | ||||||
|  |  | ||||||
|  | @ -189,6 +189,11 @@ func IsFollowingPath(id *url.URL) bool { | ||||||
| 	return followingPathRegex.MatchString(id.Path) | 	return followingPathRegex.MatchString(id.Path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // IsFollowPath returns true if the given URL path corresponds to eg /users/example_username/follow/SOME_ULID_OF_A_FOLLOW | ||||||
|  | func IsFollowPath(id *url.URL) bool { | ||||||
|  | 	return followPathRegex.MatchString(id.Path) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked | // IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked | ||||||
| func IsLikedPath(id *url.URL) bool { | func IsLikedPath(id *url.URL) bool { | ||||||
| 	return likedPathRegex.MatchString(id.Path) | 	return likedPathRegex.MatchString(id.Path) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue