| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package paging | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | // EitherMinID returns an ID boundary with given min ID value, | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | // using either the `since_id`,"DESC" name,ordering or | 
					
						
							|  |  |  | // `min_id`,"ASC" name,ordering depending on which is set. | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | func EitherMinID(minID, sinceID string) Boundary { | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 	/* | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-30 11:19:33 +01:00
										 |  |  | 				Paging with `since_id` vs `min_id`: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					limit = 4       limit = 4 | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 		  max_id--> |xxxxxxxxxx|    |          | <-- max_id | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 					|xxxxxxxxxx|    |          | | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 					|xxxxxxxxxx|    |          | | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 					|xxxxxxxxxx|    |xxxxxxxxxx| | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 					|          |    |xxxxxxxxxx| | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 					|          |    |xxxxxxxxxx| | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 		since_id--> |          |    |xxxxxxxxxx| <-- min_id | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 					|          |    |          | | 
					
						
							|  |  |  | 					+----------+    +----------+ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				To sum it up in words: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				when paging with since_id, max_id is used as | 
					
						
							|  |  |  | 				the cursor value, and since_id provides a | 
					
						
							|  |  |  | 				limiting value to the results. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				when paging with min_id, min_id is used as | 
					
						
							|  |  |  | 				the cursor value, and max_id provides a | 
					
						
							|  |  |  | 				limiting value to the results. | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-30 15:22:23 +01:00
										 |  |  | 				But to further complicate it... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				The "next" and "prev" relative links provided | 
					
						
							|  |  |  | 				in the link header are ALWAYS DESCENDING. Which | 
					
						
							|  |  |  | 				means we will ALWAYS provide next=?max_id and | 
					
						
							|  |  |  | 				prev=?min_id. *shakes fist at mastodon api* | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 	*/ | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case minID != "": | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 		return MinID(minID) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		// default min is `since_id` | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 		return SinceID(sinceID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SinceID ... | 
					
						
							|  |  |  | func SinceID(sinceID string) Boundary { | 
					
						
							|  |  |  | 	return Boundary{ | 
					
						
							| 
									
										
										
										
											2024-04-30 15:22:23 +01:00
										 |  |  | 		// even when a since_id query is | 
					
						
							|  |  |  | 		// provided, the next / prev rel | 
					
						
							|  |  |  | 		// links are DESCENDING with | 
					
						
							|  |  |  | 		// next:max_id and prev:min_id. | 
					
						
							|  |  |  | 		// so ALWAYS use min_id as name. | 
					
						
							|  |  |  | 		Name:  "min_id", | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 		Value: sinceID, | 
					
						
							|  |  |  | 		Order: OrderDescending, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MinID ... | 
					
						
							|  |  |  | func MinID(minID string) Boundary { | 
					
						
							|  |  |  | 	return Boundary{ | 
					
						
							|  |  |  | 		Name:  "min_id", | 
					
						
							|  |  |  | 		Value: minID, | 
					
						
							|  |  |  | 		Order: OrderAscending, | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MaxID returns an ID boundary with given max | 
					
						
							|  |  |  | // ID value, and the "max_id" query key set. | 
					
						
							|  |  |  | func MaxID(maxID string) Boundary { | 
					
						
							|  |  |  | 	return Boundary{ | 
					
						
							|  |  |  | 		Name:  "max_id", | 
					
						
							|  |  |  | 		Value: maxID, | 
					
						
							|  |  |  | 		Order: OrderDescending, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MinShortcodeDomain returns a boundary with the given minimum emoji | 
					
						
							|  |  |  | // shortcode@domain, and the "min_shortcode_domain" query key set. | 
					
						
							|  |  |  | func MinShortcodeDomain(min string) Boundary { | 
					
						
							|  |  |  | 	return Boundary{ | 
					
						
							|  |  |  | 		Name:  "min_shortcode_domain", | 
					
						
							|  |  |  | 		Value: min, | 
					
						
							|  |  |  | 		Order: OrderAscending, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MaxShortcodeDomain returns a boundary with the given maximum emoji | 
					
						
							|  |  |  | // shortcode@domain, and the "max_shortcode_domain" query key set. | 
					
						
							|  |  |  | func MaxShortcodeDomain(max string) Boundary { | 
					
						
							|  |  |  | 	return Boundary{ | 
					
						
							|  |  |  | 		Name:  "max_shortcode_domain", | 
					
						
							|  |  |  | 		Value: max, | 
					
						
							|  |  |  | 		Order: OrderDescending, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Boundary represents the upper or lower limit in a page slice. | 
					
						
							|  |  |  | type Boundary struct { | 
					
						
							|  |  |  | 	Name  string // i.e. query key | 
					
						
							|  |  |  | 	Value string | 
					
						
							|  |  |  | 	Order Order // NOTE: see Order type for explanation | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // new creates a new Boundary with the same ordering and name | 
					
						
							|  |  |  | // as the original (receiving), but with the new provided value. | 
					
						
							|  |  |  | func (b Boundary) new(value string) Boundary { | 
					
						
							|  |  |  | 	return Boundary{ | 
					
						
							|  |  |  | 		Name:  b.Name, | 
					
						
							|  |  |  | 		Value: value, | 
					
						
							|  |  |  | 		Order: b.Order, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Find finds the boundary's set value in input slice, or returns -1. | 
					
						
							|  |  |  | func (b Boundary) Find(in []string) int { | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 	if b.Value == "" { | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 		return -1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i := range in { | 
					
						
							|  |  |  | 		if in[i] == b.Value { | 
					
						
							|  |  |  | 			return i | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return -1 | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Boundary_FindFunc is functionally equivalent to Boundary{}.Find() but for an arbitrary type with ID. | 
					
						
							|  |  |  | // Note: this is not a Boundary{} method as Go generics are not supported in method receiver functions. | 
					
						
							|  |  |  | func Boundary_FindFunc[T any](b Boundary, in []T, get func(T) string) int { //nolint:revive | 
					
						
							|  |  |  | 	if get == nil { | 
					
						
							|  |  |  | 		panic("nil function") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if b.Value == "" { | 
					
						
							|  |  |  | 		return -1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i := range in { | 
					
						
							|  |  |  | 		if get(in[i]) == b.Value { | 
					
						
							|  |  |  | 			return i | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return -1 | 
					
						
							|  |  |  | } |