wip 2
This commit is contained in:
65
api/v1/activity.go
Normal file
65
api/v1/activity.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"reichard.io/antholume/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GET /activity
|
||||||
|
func (s *Server) GetActivity(ctx context.Context, request GetActivityRequestObject) (GetActivityResponseObject, error) {
|
||||||
|
auth, ok := s.getSessionFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return GetActivity401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
docFilter := false
|
||||||
|
if request.Params.DocFilter != nil {
|
||||||
|
docFilter = *request.Params.DocFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
documentID := ""
|
||||||
|
if request.Params.DocumentId != nil {
|
||||||
|
documentID = *request.Params.DocumentId
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := int64(0)
|
||||||
|
if request.Params.Offset != nil {
|
||||||
|
offset = *request.Params.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := int64(100)
|
||||||
|
if request.Params.Limit != nil {
|
||||||
|
limit = *request.Params.Limit
|
||||||
|
}
|
||||||
|
|
||||||
|
activities, err := s.db.Queries.GetActivity(ctx, database.GetActivityParams{
|
||||||
|
UserID: auth.UserName,
|
||||||
|
DocFilter: docFilter,
|
||||||
|
DocumentID: documentID,
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return GetActivity500JSONResponse{Code: 500, Message: err.Error()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
apiActivities := make([]Activity, len(activities))
|
||||||
|
for i, a := range activities {
|
||||||
|
apiActivities[i] = Activity{
|
||||||
|
ActivityType: a.DeviceID,
|
||||||
|
DocumentId: a.DocumentID,
|
||||||
|
Id: strconv.Itoa(i),
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
UserId: auth.UserName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := ActivityResponse{
|
||||||
|
Activities: apiActivities,
|
||||||
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
||||||
|
}
|
||||||
|
return GetActivity200JSONResponse(response), nil
|
||||||
|
}
|
||||||
1103
api/v1/api.gen.go
Normal file
1103
api/v1/api.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
223
api/v1/auth.go
223
api/v1/auth.go
@@ -3,7 +3,6 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,24 +12,125 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authData represents session authentication data
|
// POST /auth/login
|
||||||
type authData struct {
|
func (s *Server) Login(ctx context.Context, request LoginRequestObject) (LoginResponseObject, error) {
|
||||||
UserName string
|
if request.Body == nil {
|
||||||
IsAdmin bool
|
return Login400JSONResponse{Code: 400, Message: "Invalid request body"}, nil
|
||||||
AuthHash string
|
}
|
||||||
|
|
||||||
|
req := *request.Body
|
||||||
|
if req.Username == "" || req.Password == "" {
|
||||||
|
return Login400JSONResponse{Code: 400, Message: "Invalid credentials"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MD5 - KOSync compatibility
|
||||||
|
password := fmt.Sprintf("%x", md5.Sum([]byte(req.Password)))
|
||||||
|
|
||||||
|
// Verify credentials
|
||||||
|
user, err := s.db.Queries.GetUser(ctx, req.Username)
|
||||||
|
if err != nil {
|
||||||
|
return Login401JSONResponse{Code: 401, Message: "Invalid credentials"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if match, err := argon2.ComparePasswordAndHash(password, *user.Pass); err != nil || !match {
|
||||||
|
return Login401JSONResponse{Code: 401, Message: "Invalid credentials"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get request and response from context (set by middleware)
|
||||||
|
r := s.getRequestFromContext(ctx)
|
||||||
|
w := s.getResponseWriterFromContext(ctx)
|
||||||
|
|
||||||
|
if r == nil || w == nil {
|
||||||
|
return Login500JSONResponse{Code: 500, Message: "Internal context error"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create session
|
||||||
|
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
||||||
|
if s.cfg.CookieEncKey != "" {
|
||||||
|
if len(s.cfg.CookieEncKey) == 16 || len(s.cfg.CookieEncKey) == 32 {
|
||||||
|
store = sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey), []byte(s.cfg.CookieEncKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session, _ := store.Get(r, "token")
|
||||||
|
session.Values["authorizedUser"] = user.ID
|
||||||
|
session.Values["isAdmin"] = user.Admin
|
||||||
|
session.Values["expiresAt"] = time.Now().Unix() + (60 * 60 * 24 * 7)
|
||||||
|
session.Values["authHash"] = *user.AuthHash
|
||||||
|
|
||||||
|
if err := session.Save(r, w); err != nil {
|
||||||
|
return Login500JSONResponse{Code: 500, Message: "Failed to create session"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Login200JSONResponse{
|
||||||
|
Username: user.ID,
|
||||||
|
IsAdmin: user.Admin,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// withAuth wraps a handler with session authentication
|
// POST /auth/logout
|
||||||
func (s *Server) withAuth(handler http.HandlerFunc) http.HandlerFunc {
|
func (s *Server) Logout(ctx context.Context, request LogoutRequestObject) (LogoutResponseObject, error) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
_, ok := s.getSessionFromContext(ctx)
|
||||||
auth, ok := s.getSession(r)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
writeJSONError(w, http.StatusUnauthorized, "Unauthorized")
|
return Logout401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
ctx := context.WithValue(r.Context(), "auth", auth)
|
|
||||||
handler(w, r.WithContext(ctx))
|
r := s.getRequestFromContext(ctx)
|
||||||
|
w := s.getResponseWriterFromContext(ctx)
|
||||||
|
|
||||||
|
if r == nil || w == nil {
|
||||||
|
return Logout401JSONResponse{Code: 401, Message: "Internal context error"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
||||||
|
session, _ := store.Get(r, "token")
|
||||||
|
session.Values = make(map[any]any)
|
||||||
|
|
||||||
|
if err := session.Save(r, w); err != nil {
|
||||||
|
return Logout401JSONResponse{Code: 401, Message: "Failed to logout"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Logout200Response{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /auth/me
|
||||||
|
func (s *Server) GetMe(ctx context.Context, request GetMeRequestObject) (GetMeResponseObject, error) {
|
||||||
|
auth, ok := s.getSessionFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return GetMe401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetMe200JSONResponse{
|
||||||
|
Username: auth.UserName,
|
||||||
|
IsAdmin: auth.IsAdmin,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSessionFromContext extracts authData from context
|
||||||
|
func (s *Server) getSessionFromContext(ctx context.Context) (authData, bool) {
|
||||||
|
auth, ok := ctx.Value("auth").(authData)
|
||||||
|
if !ok {
|
||||||
|
return authData{}, false
|
||||||
|
}
|
||||||
|
return auth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRequestFromContext extracts the HTTP request from context
|
||||||
|
func (s *Server) getRequestFromContext(ctx context.Context) *http.Request {
|
||||||
|
r, ok := ctx.Value("request").(*http.Request)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// getResponseWriterFromContext extracts the response writer from context
|
||||||
|
func (s *Server) getResponseWriterFromContext(ctx context.Context) http.ResponseWriter {
|
||||||
|
w, ok := ctx.Value("response").(http.ResponseWriter)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSession retrieves auth data from the session cookie
|
// getSession retrieves auth data from the session cookie
|
||||||
@@ -86,94 +186,9 @@ func (s *Server) getUserAuthHash(ctx context.Context, username string) (string,
|
|||||||
return *user.AuthHash, nil
|
return *user.AuthHash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiLogin handles POST /api/v1/auth/login
|
// authData represents authenticated user information
|
||||||
func (s *Server) apiLogin(w http.ResponseWriter, r *http.Request) {
|
type authData struct {
|
||||||
if r.Method != http.MethodPost {
|
UserName string
|
||||||
writeJSONError(w, http.StatusMethodNotAllowed, "Method not allowed")
|
IsAdmin bool
|
||||||
return
|
AuthHash string
|
||||||
}
|
|
||||||
|
|
||||||
var req LoginRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
writeJSONError(w, http.StatusBadRequest, "Invalid JSON")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Username == "" || req.Password == "" {
|
|
||||||
writeJSONError(w, http.StatusBadRequest, "Invalid credentials")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MD5 - KOSync compatibility
|
|
||||||
password := fmt.Sprintf("%x", md5.Sum([]byte(req.Password)))
|
|
||||||
|
|
||||||
// Verify credentials
|
|
||||||
user, err := s.db.Queries.GetUser(r.Context(), req.Username)
|
|
||||||
if err != nil {
|
|
||||||
writeJSONError(w, http.StatusUnauthorized, "Invalid credentials")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if match, err := argon2.ComparePasswordAndHash(password, *user.Pass); err != nil || !match {
|
|
||||||
writeJSONError(w, http.StatusUnauthorized, "Invalid credentials")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create session
|
|
||||||
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
|
||||||
if s.cfg.CookieEncKey != "" {
|
|
||||||
if len(s.cfg.CookieEncKey) == 16 || len(s.cfg.CookieEncKey) == 32 {
|
|
||||||
store = sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey), []byte(s.cfg.CookieEncKey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session, _ := store.Get(r, "token")
|
|
||||||
session.Values["authorizedUser"] = user.ID
|
|
||||||
session.Values["isAdmin"] = user.Admin
|
|
||||||
session.Values["expiresAt"] = time.Now().Unix() + (60 * 60 * 24 * 7)
|
|
||||||
session.Values["authHash"] = *user.AuthHash
|
|
||||||
|
|
||||||
if err := session.Save(r, w); err != nil {
|
|
||||||
writeJSONError(w, http.StatusInternalServerError, "Failed to create session")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, LoginResponse{
|
|
||||||
Username: user.ID,
|
|
||||||
IsAdmin: user.Admin,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiLogout handles POST /api/v1/auth/logout
|
|
||||||
func (s *Server) apiLogout(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
writeJSONError(w, http.StatusMethodNotAllowed, "Method not allowed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
|
||||||
session, _ := store.Get(r, "token")
|
|
||||||
session.Values = make(map[any]any)
|
|
||||||
|
|
||||||
if err := session.Save(r, w); err != nil {
|
|
||||||
writeJSONError(w, http.StatusInternalServerError, "Failed to logout")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string]string{"status": "logged out"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// apiGetMe handles GET /api/v1/auth/me
|
|
||||||
func (s *Server) apiGetMe(w http.ResponseWriter, r *http.Request) {
|
|
||||||
auth, ok := r.Context().Value("auth").(authData)
|
|
||||||
if !ok {
|
|
||||||
writeJSONError(w, http.StatusUnauthorized, "Unauthorized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, UserData{
|
|
||||||
Username: auth.UserName,
|
|
||||||
IsAdmin: auth.IsAdmin,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,90 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
argon2 "github.com/alexedwards/argon2id"
|
argon2 "github.com/alexedwards/argon2id"
|
||||||
|
"reichard.io/antholume/config"
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPILogin(t *testing.T) {
|
type AuthTestSuite struct {
|
||||||
db := setupTestDB(t)
|
suite.Suite
|
||||||
cfg := testConfig()
|
db *database.DBManager
|
||||||
server := NewServer(db, cfg)
|
cfg *config.Config
|
||||||
|
srv *Server
|
||||||
|
}
|
||||||
|
|
||||||
// First, create a user
|
func (suite *AuthTestSuite) setupConfig() *config.Config {
|
||||||
createTestUser(t, db, "testuser", "testpass")
|
return &config.Config{
|
||||||
|
ListenPort: "8080",
|
||||||
|
DBType: "memory",
|
||||||
|
DBName: "test",
|
||||||
|
ConfigPath: "/tmp",
|
||||||
|
CookieAuthKey: "test-auth-key-32-bytes-long-enough",
|
||||||
|
CookieEncKey: "0123456789abcdef",
|
||||||
|
CookieSecure: false,
|
||||||
|
CookieHTTPOnly: true,
|
||||||
|
Version: "test",
|
||||||
|
DemoMode: false,
|
||||||
|
RegistrationEnabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth(t *testing.T) {
|
||||||
|
suite.Run(t, new(AuthTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AuthTestSuite) SetupTest() {
|
||||||
|
suite.cfg = suite.setupConfig()
|
||||||
|
suite.db = database.NewMgr(suite.cfg)
|
||||||
|
suite.srv = NewServer(suite.db, suite.cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AuthTestSuite) createTestUser(username, password string) {
|
||||||
|
md5Hash := fmt.Sprintf("%x", md5.Sum([]byte(password)))
|
||||||
|
|
||||||
|
hashedPassword, err := argon2.CreateHash(md5Hash, argon2.DefaultParams)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
authHash := "test-auth-hash"
|
||||||
|
|
||||||
|
_, err = suite.db.Queries.CreateUser(suite.T().Context(), database.CreateUserParams{
|
||||||
|
ID: username,
|
||||||
|
Pass: &hashedPassword,
|
||||||
|
AuthHash: &authHash,
|
||||||
|
Admin: true,
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AuthTestSuite) login(username, password string) *http.Cookie {
|
||||||
|
reqBody := LoginRequest{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(reqBody)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
suite.Equal(http.StatusOK, w.Code, "login should return 200")
|
||||||
|
|
||||||
|
var resp LoginResponse
|
||||||
|
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
|
||||||
|
cookies := w.Result().Cookies()
|
||||||
|
suite.Require().Len(cookies, 1, "should have session cookie")
|
||||||
|
|
||||||
|
return cookies[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AuthTestSuite) TestAPILogin() {
|
||||||
|
suite.createTestUser("testuser", "testpass")
|
||||||
|
|
||||||
// Test login
|
|
||||||
reqBody := LoginRequest{
|
reqBody := LoginRequest{
|
||||||
Username: "testuser",
|
Username: "testuser",
|
||||||
Password: "testpass",
|
Password: "testpass",
|
||||||
@@ -31,27 +102,16 @@ func TestAPILogin(t *testing.T) {
|
|||||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
suite.Equal(http.StatusOK, w.Code)
|
||||||
t.Fatalf("Expected 200, got %d: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp LoginResponse
|
var resp LoginResponse
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
suite.Equal("testuser", resp.Username)
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Username != "testuser" {
|
|
||||||
t.Errorf("Expected username 'testuser', got '%s'", resp.Username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPILoginInvalidCredentials(t *testing.T) {
|
func (suite *AuthTestSuite) TestAPILoginInvalidCredentials() {
|
||||||
db := setupTestDB(t)
|
|
||||||
cfg := testConfig()
|
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
reqBody := LoginRequest{
|
reqBody := LoginRequest{
|
||||||
Username: "testuser",
|
Username: "testuser",
|
||||||
Password: "wrongpass",
|
Password: "wrongpass",
|
||||||
@@ -61,124 +121,46 @@ func TestAPILoginInvalidCredentials(t *testing.T) {
|
|||||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusUnauthorized {
|
suite.Equal(http.StatusUnauthorized, w.Code)
|
||||||
t.Fatalf("Expected 401, got %d", w.Code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPILogout(t *testing.T) {
|
func (suite *AuthTestSuite) TestAPILogout() {
|
||||||
db := setupTestDB(t)
|
suite.createTestUser("testuser", "testpass")
|
||||||
cfg := testConfig()
|
cookie := suite.login("testuser", "testpass")
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
// Create user and login
|
|
||||||
createTestUser(t, db, "testuser", "testpass")
|
|
||||||
|
|
||||||
// Login first
|
|
||||||
reqBody := LoginRequest{Username: "testuser", Password: "testpass"}
|
|
||||||
body, _ := json.Marshal(reqBody)
|
|
||||||
loginReq := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
|
||||||
loginResp := httptest.NewRecorder()
|
|
||||||
server.ServeHTTP(loginResp, loginReq)
|
|
||||||
|
|
||||||
// Get session cookie
|
|
||||||
cookies := loginResp.Result().Cookies()
|
|
||||||
if len(cookies) == 0 {
|
|
||||||
t.Fatal("No session cookie returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
|
||||||
req.AddCookie(cookies[0])
|
req.AddCookie(cookie)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
suite.Equal(http.StatusOK, w.Code)
|
||||||
t.Fatalf("Expected 200, got %d", w.Code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGetMe(t *testing.T) {
|
func (suite *AuthTestSuite) TestAPIGetMe() {
|
||||||
db := setupTestDB(t)
|
suite.createTestUser("testuser", "testpass")
|
||||||
cfg := testConfig()
|
cookie := suite.login("testuser", "testpass")
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
// Create user and login
|
|
||||||
createTestUser(t, db, "testuser", "testpass")
|
|
||||||
|
|
||||||
// Login first
|
|
||||||
reqBody := LoginRequest{Username: "testuser", Password: "testpass"}
|
|
||||||
body, _ := json.Marshal(reqBody)
|
|
||||||
loginReq := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
|
||||||
loginResp := httptest.NewRecorder()
|
|
||||||
server.ServeHTTP(loginResp, loginReq)
|
|
||||||
|
|
||||||
// Get session cookie
|
|
||||||
cookies := loginResp.Result().Cookies()
|
|
||||||
if len(cookies) == 0 {
|
|
||||||
t.Fatal("No session cookie returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get me
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
|
||||||
req.AddCookie(cookies[0])
|
req.AddCookie(cookie)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
suite.Equal(http.StatusOK, w.Code)
|
||||||
t.Fatalf("Expected 200, got %d", w.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp UserData
|
var resp UserData
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
suite.Equal("testuser", resp.Username)
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Username != "testuser" {
|
|
||||||
t.Errorf("Expected username 'testuser', got '%s'", resp.Username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGetMeUnauthenticated(t *testing.T) {
|
func (suite *AuthTestSuite) TestAPIGetMeUnauthenticated() {
|
||||||
db := setupTestDB(t)
|
|
||||||
cfg := testConfig()
|
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusUnauthorized {
|
suite.Equal(http.StatusUnauthorized, w.Code)
|
||||||
t.Fatalf("Expected 401, got %d", w.Code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestUser(t *testing.T, db *database.DBManager, username, password string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
// MD5 hash for KOSync compatibility (matches existing system)
|
|
||||||
md5Hash := fmt.Sprintf("%x", md5.Sum([]byte(password)))
|
|
||||||
|
|
||||||
// Then argon2 hash the MD5
|
|
||||||
hashedPassword, err := argon2.CreateHash(md5Hash, argon2.DefaultParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to hash password: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
authHash := "test-auth-hash"
|
|
||||||
|
|
||||||
_, err = db.Queries.CreateUser(t.Context(), database.CreateUserParams{
|
|
||||||
ID: username,
|
|
||||||
Pass: &hashedPassword,
|
|
||||||
AuthHash: &authHash,
|
|
||||||
Admin: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create user: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,141 +1,130 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"context"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
"reichard.io/antholume/pkg/ptr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// apiGetDocuments handles GET /api/v1/documents
|
// GET /documents
|
||||||
// Deprecated: Use GetDocuments with DocumentListRequest instead
|
func (s *Server) GetDocuments(ctx context.Context, request GetDocumentsRequestObject) (GetDocumentsResponseObject, error) {
|
||||||
func (s *Server) apiGetDocuments(w http.ResponseWriter, r *http.Request) {
|
auth, ok := s.getSessionFromContext(ctx)
|
||||||
// Parse query params
|
|
||||||
query := r.URL.Query()
|
|
||||||
page, _ := strconv.ParseInt(query.Get("page"), 10, 64)
|
|
||||||
if page == 0 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
limit, _ := strconv.ParseInt(query.Get("limit"), 10, 64)
|
|
||||||
if limit == 0 {
|
|
||||||
limit = 9
|
|
||||||
}
|
|
||||||
search := query.Get("search")
|
|
||||||
|
|
||||||
// Get auth from context
|
|
||||||
auth, ok := r.Context().Value("auth").(authData)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
writeJSONError(w, http.StatusUnauthorized, "Unauthorized")
|
return GetDocuments401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build query
|
page := int64(1)
|
||||||
var queryPtr *string
|
if request.Params.Page != nil {
|
||||||
if search != "" {
|
page = *request.Params.Page
|
||||||
queryPtr = ptr.Of("%" + search + "%")
|
}
|
||||||
|
|
||||||
|
limit := int64(9)
|
||||||
|
if request.Params.Limit != nil {
|
||||||
|
limit = *request.Params.Limit
|
||||||
|
}
|
||||||
|
|
||||||
|
search := ""
|
||||||
|
if request.Params.Search != nil {
|
||||||
|
search = "%" + *request.Params.Search + "%"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query database
|
|
||||||
rows, err := s.db.Queries.GetDocumentsWithStats(
|
rows, err := s.db.Queries.GetDocumentsWithStats(
|
||||||
r.Context(),
|
ctx,
|
||||||
database.GetDocumentsWithStatsParams{
|
database.GetDocumentsWithStatsParams{
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
Query: queryPtr,
|
Query: &search,
|
||||||
Deleted: ptr.Of(false),
|
Deleted: ptrOf(false),
|
||||||
Offset: (page - 1) * limit,
|
Offset: (page - 1) * limit,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
return GetDocuments500JSONResponse{Code: 500, Message: err.Error()}, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate pagination
|
|
||||||
total := int64(len(rows))
|
total := int64(len(rows))
|
||||||
var nextPage *int64
|
var nextPage *int64
|
||||||
var previousPage *int64
|
var previousPage *int64
|
||||||
if page*limit < total {
|
if page*limit < total {
|
||||||
nextPage = ptr.Of(page + 1)
|
nextPage = ptrOf(page + 1)
|
||||||
}
|
}
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
previousPage = ptr.Of(page - 1)
|
previousPage = ptrOf(page - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get word counts
|
apiDocuments := make([]Document, len(rows))
|
||||||
wordCounts := make([]WordCount, 0, len(rows))
|
wordCounts := make([]WordCount, 0, len(rows))
|
||||||
for _, row := range rows {
|
for i, row := range rows {
|
||||||
|
apiDocuments[i] = Document{
|
||||||
|
Id: row.ID,
|
||||||
|
Title: *row.Title,
|
||||||
|
Author: *row.Author,
|
||||||
|
Words: row.Words,
|
||||||
|
}
|
||||||
if row.Words != nil {
|
if row.Words != nil {
|
||||||
wordCounts = append(wordCounts, WordCount{
|
wordCounts = append(wordCounts, WordCount{
|
||||||
DocumentID: row.ID,
|
DocumentId: row.ID,
|
||||||
Count: *row.Words,
|
Count: *row.Words,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return response
|
response := DocumentsResponse{
|
||||||
writeJSON(w, http.StatusOK, DocumentsResponse{
|
Documents: apiDocuments,
|
||||||
Documents: rows,
|
|
||||||
Total: total,
|
Total: total,
|
||||||
Page: page,
|
Page: page,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
NextPage: nextPage,
|
NextPage: nextPage,
|
||||||
PreviousPage: previousPage,
|
PreviousPage: previousPage,
|
||||||
Search: ptr.Of(search),
|
Search: request.Params.Search,
|
||||||
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
||||||
WordCounts: wordCounts,
|
WordCounts: wordCounts,
|
||||||
})
|
}
|
||||||
|
return GetDocuments200JSONResponse(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiGetDocument handles GET /api/v1/documents/:id
|
// GET /documents/{id}
|
||||||
// Deprecated: Use GetDocument with DocumentRequest instead
|
func (s *Server) GetDocument(ctx context.Context, request GetDocumentRequestObject) (GetDocumentResponseObject, error) {
|
||||||
func (s *Server) apiGetDocument(w http.ResponseWriter, r *http.Request) {
|
auth, ok := s.getSessionFromContext(ctx)
|
||||||
// Extract ID from URL path
|
|
||||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/documents/")
|
|
||||||
id := strings.TrimPrefix(path, "/")
|
|
||||||
|
|
||||||
if id == "" {
|
|
||||||
writeJSONError(w, http.StatusBadRequest, "Document ID required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get auth from context
|
|
||||||
auth, ok := r.Context().Value("auth").(authData)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
writeJSONError(w, http.StatusUnauthorized, "Unauthorized")
|
return GetDocument401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query database
|
doc, err := s.db.Queries.GetDocument(ctx, request.Id)
|
||||||
doc, err := s.db.Queries.GetDocument(r.Context(), id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeJSONError(w, http.StatusNotFound, "Document not found")
|
return GetDocument404JSONResponse{Code: 404, Message: "Document not found"}, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get progress
|
progressRow, err := s.db.Queries.GetDocumentProgress(ctx, database.GetDocumentProgressParams{
|
||||||
progressRow, err := s.db.Queries.GetDocumentProgress(r.Context(), database.GetDocumentProgressParams{
|
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
DocumentID: id,
|
DocumentID: request.Id,
|
||||||
})
|
})
|
||||||
var progress *Progress
|
var progress *Progress
|
||||||
if err == nil {
|
if err == nil {
|
||||||
progress = &Progress{
|
progress = &Progress{
|
||||||
UserID: progressRow.UserID,
|
UserId: progressRow.UserID,
|
||||||
DocumentID: progressRow.DocumentID,
|
DocumentId: progressRow.DocumentID,
|
||||||
DeviceID: progressRow.DeviceID,
|
DeviceId: progressRow.DeviceID,
|
||||||
Percentage: progressRow.Percentage,
|
Percentage: progressRow.Percentage,
|
||||||
Progress: progressRow.Progress,
|
Progress: progressRow.Progress,
|
||||||
CreatedAt: progressRow.CreatedAt,
|
CreatedAt: parseTime(progressRow.CreatedAt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return response
|
apiDoc := Document{
|
||||||
writeJSON(w, http.StatusOK, DocumentResponse{
|
Id: doc.ID,
|
||||||
Document: doc,
|
Title: *doc.Title,
|
||||||
|
Author: *doc.Author,
|
||||||
|
CreatedAt: parseTime(doc.CreatedAt),
|
||||||
|
UpdatedAt: parseTime(doc.UpdatedAt),
|
||||||
|
Deleted: doc.Deleted,
|
||||||
|
Words: doc.Words,
|
||||||
|
}
|
||||||
|
|
||||||
|
response := DocumentResponse{
|
||||||
|
Document: apiDoc,
|
||||||
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
||||||
Progress: progress,
|
Progress: progress,
|
||||||
})
|
}
|
||||||
|
return GetDocument200JSONResponse(response), nil
|
||||||
}
|
}
|
||||||
@@ -2,163 +2,160 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
argon2 "github.com/alexedwards/argon2id"
|
||||||
|
"reichard.io/antholume/config"
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
"reichard.io/antholume/pkg/ptr"
|
"reichard.io/antholume/pkg/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIGetDocuments(t *testing.T) {
|
type DocumentsTestSuite struct {
|
||||||
db := setupTestDB(t)
|
suite.Suite
|
||||||
cfg := testConfig()
|
db *database.DBManager
|
||||||
server := NewServer(db, cfg)
|
cfg *config.Config
|
||||||
|
srv *Server
|
||||||
|
}
|
||||||
|
|
||||||
// Create user and login
|
func (suite *DocumentsTestSuite) setupConfig() *config.Config {
|
||||||
createTestUser(t, db, "testuser", "testpass")
|
return &config.Config{
|
||||||
|
ListenPort: "8080",
|
||||||
// Login first
|
DBType: "memory",
|
||||||
reqBody := LoginRequest{Username: "testuser", Password: "testpass"}
|
DBName: "test",
|
||||||
body, _ := json.Marshal(reqBody)
|
ConfigPath: "/tmp",
|
||||||
loginReq := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
CookieAuthKey: "test-auth-key-32-bytes-long-enough",
|
||||||
loginResp := httptest.NewRecorder()
|
CookieEncKey: "0123456789abcdef",
|
||||||
server.ServeHTTP(loginResp, loginReq)
|
CookieSecure: false,
|
||||||
|
CookieHTTPOnly: true,
|
||||||
// Get session cookie
|
Version: "test",
|
||||||
cookies := loginResp.Result().Cookies()
|
DemoMode: false,
|
||||||
if len(cookies) == 0 {
|
RegistrationEnabled: true,
|
||||||
t.Fatal("No session cookie returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get documents
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents?page=1&limit=9", nil)
|
|
||||||
req.AddCookie(cookies[0])
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("Expected 200, got %d: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp DocumentsResponse
|
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Page != 1 {
|
|
||||||
t.Errorf("Expected page 1, got %d", resp.Page)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Limit != 9 {
|
|
||||||
t.Errorf("Expected limit 9, got %d", resp.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.User.Username != "testuser" {
|
|
||||||
t.Errorf("Expected username 'testuser', got '%s'", resp.User.Username)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGetDocumentsUnauthenticated(t *testing.T) {
|
func TestDocuments(t *testing.T) {
|
||||||
db := setupTestDB(t)
|
suite.Run(t, new(DocumentsTestSuite))
|
||||||
cfg := testConfig()
|
}
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
|
func (suite *DocumentsTestSuite) SetupTest() {
|
||||||
|
suite.cfg = suite.setupConfig()
|
||||||
|
suite.db = database.NewMgr(suite.cfg)
|
||||||
|
suite.srv = NewServer(suite.db, suite.cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DocumentsTestSuite) createTestUser(username, password string) {
|
||||||
|
suite.authTestSuiteHelper(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DocumentsTestSuite) login(username, password string) *http.Cookie {
|
||||||
|
return suite.authLoginHelper(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DocumentsTestSuite) authTestSuiteHelper(username, password string) {
|
||||||
|
// MD5 hash for KOSync compatibility (matches existing system)
|
||||||
|
md5Hash := fmt.Sprintf("%x", md5.Sum([]byte(password)))
|
||||||
|
|
||||||
|
// Then argon2 hash the MD5
|
||||||
|
hashedPassword, err := argon2.CreateHash(md5Hash, argon2.DefaultParams)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
_, err = suite.db.Queries.CreateUser(suite.T().Context(), database.CreateUserParams{
|
||||||
|
ID: username,
|
||||||
|
Pass: &hashedPassword,
|
||||||
|
AuthHash: ptr.Of("test-auth-hash"),
|
||||||
|
Admin: true,
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DocumentsTestSuite) authLoginHelper(username, password string) *http.Cookie {
|
||||||
|
reqBody := LoginRequest{Username: username, Password: password}
|
||||||
|
body, err := json.Marshal(reqBody)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
suite.Equal(http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
cookies := w.Result().Cookies()
|
||||||
|
suite.Require().Len(cookies, 1)
|
||||||
|
|
||||||
|
return cookies[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DocumentsTestSuite) TestAPIGetDocuments() {
|
||||||
|
suite.createTestUser("testuser", "testpass")
|
||||||
|
cookie := suite.login("testuser", "testpass")
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents?page=1&limit=9", nil)
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
suite.Equal(http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
var resp DocumentsResponse
|
||||||
|
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
suite.Equal(int64(1), resp.Page)
|
||||||
|
suite.Equal(int64(9), resp.Limit)
|
||||||
|
suite.Equal("testuser", resp.User.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DocumentsTestSuite) TestAPIGetDocumentsUnauthenticated() {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents", nil)
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusUnauthorized {
|
suite.Equal(http.StatusUnauthorized, w.Code)
|
||||||
t.Fatalf("Expected 401, got %d", w.Code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGetDocument(t *testing.T) {
|
func (suite *DocumentsTestSuite) TestAPIGetDocument() {
|
||||||
db := setupTestDB(t)
|
suite.createTestUser("testuser", "testpass")
|
||||||
cfg := testConfig()
|
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
// Create user
|
|
||||||
createTestUser(t, db, "testuser", "testpass")
|
|
||||||
|
|
||||||
// Create a document using UpsertDocument
|
|
||||||
docID := "test-doc-1"
|
docID := "test-doc-1"
|
||||||
_, err := db.Queries.UpsertDocument(t.Context(), database.UpsertDocumentParams{
|
_, err := suite.db.Queries.UpsertDocument(suite.T().Context(), database.UpsertDocumentParams{
|
||||||
ID: docID,
|
ID: docID,
|
||||||
Title: ptr.Of("Test Document"),
|
Title: ptr.Of("Test Document"),
|
||||||
Author: ptr.Of("Test Author"),
|
Author: ptr.Of("Test Author"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
suite.Require().NoError(err)
|
||||||
t.Fatalf("Failed to create document: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login
|
cookie := suite.login("testuser", "testpass")
|
||||||
reqBody := LoginRequest{Username: "testuser", Password: "testpass"}
|
|
||||||
body, _ := json.Marshal(reqBody)
|
|
||||||
loginReq := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
|
||||||
loginResp := httptest.NewRecorder()
|
|
||||||
server.ServeHTTP(loginResp, loginReq)
|
|
||||||
|
|
||||||
cookies := loginResp.Result().Cookies()
|
|
||||||
if len(cookies) == 0 {
|
|
||||||
t.Fatal("No session cookie returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get document
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents/"+docID, nil)
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents/"+docID, nil)
|
||||||
req.AddCookie(cookies[0])
|
req.AddCookie(cookie)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
suite.Equal(http.StatusOK, w.Code)
|
||||||
t.Fatalf("Expected 200, got %d: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp DocumentResponse
|
var resp DocumentResponse
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
suite.Equal(docID, resp.Document.Id)
|
||||||
}
|
suite.Equal("Test Document", resp.Document.Title)
|
||||||
|
|
||||||
if resp.Document.ID != docID {
|
|
||||||
t.Errorf("Expected document ID '%s', got '%s'", docID, resp.Document.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *resp.Document.Title != "Test Document" {
|
|
||||||
t.Errorf("Expected title 'Test Document', got '%s'", *resp.Document.Title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGetDocumentNotFound(t *testing.T) {
|
func (suite *DocumentsTestSuite) TestAPIGetDocumentNotFound() {
|
||||||
db := setupTestDB(t)
|
suite.createTestUser("testuser", "testpass")
|
||||||
cfg := testConfig()
|
cookie := suite.login("testuser", "testpass")
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
// Create user and login
|
|
||||||
createTestUser(t, db, "testuser", "testpass")
|
|
||||||
|
|
||||||
reqBody := LoginRequest{Username: "testuser", Password: "testpass"}
|
|
||||||
body, _ := json.Marshal(reqBody)
|
|
||||||
loginReq := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
|
||||||
loginResp := httptest.NewRecorder()
|
|
||||||
server.ServeHTTP(loginResp, loginReq)
|
|
||||||
|
|
||||||
cookies := loginResp.Result().Cookies()
|
|
||||||
if len(cookies) == 0 {
|
|
||||||
t.Fatal("No session cookie returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get non-existent document
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents/non-existent", nil)
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/documents/non-existent", nil)
|
||||||
req.AddCookie(cookies[0])
|
req.AddCookie(cookie)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusNotFound {
|
suite.Equal(http.StatusNotFound, w.Code)
|
||||||
t.Fatalf("Expected 404, got %d", w.Code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
3
api/v1/generate.go
Normal file
3
api/v1/generate.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
//go:generate oapi-codegen -config oapi-codegen.yaml openapi.yaml
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"reichard.io/antholume/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DocumentRequest represents a request for a single document
|
|
||||||
type DocumentRequest struct {
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DocumentListRequest represents a request for listing documents
|
|
||||||
type DocumentListRequest struct {
|
|
||||||
Page int64
|
|
||||||
Limit int64
|
|
||||||
Search *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProgressRequest represents a request for document progress
|
|
||||||
type ProgressRequest struct {
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActivityRequest represents a request for activity data
|
|
||||||
type ActivityRequest struct {
|
|
||||||
DocFilter bool
|
|
||||||
DocumentID string
|
|
||||||
Offset int64
|
|
||||||
Limit int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettingsRequest represents a request for settings data
|
|
||||||
type SettingsRequest struct{}
|
|
||||||
|
|
||||||
// GetDocument handles GET /api/v1/documents/:id
|
|
||||||
func (s *Server) GetDocument(ctx context.Context, req DocumentRequest) (DocumentResponse, error) {
|
|
||||||
auth := getAuthFromContext(ctx)
|
|
||||||
if auth == nil {
|
|
||||||
return DocumentResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
||||||
}
|
|
||||||
|
|
||||||
doc, err := s.db.Queries.GetDocument(ctx, req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return DocumentResponse{}, &apiError{status: http.StatusNotFound, message: "Document not found"}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressRow, err := s.db.Queries.GetDocumentProgress(ctx, database.GetDocumentProgressParams{
|
|
||||||
UserID: auth.UserName,
|
|
||||||
DocumentID: req.ID,
|
|
||||||
})
|
|
||||||
var progress *Progress
|
|
||||||
if err == nil {
|
|
||||||
progress = &Progress{
|
|
||||||
UserID: progressRow.UserID,
|
|
||||||
DocumentID: progressRow.DocumentID,
|
|
||||||
DeviceID: progressRow.DeviceID,
|
|
||||||
Percentage: progressRow.Percentage,
|
|
||||||
Progress: progressRow.Progress,
|
|
||||||
CreatedAt: progressRow.CreatedAt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DocumentResponse{
|
|
||||||
Document: doc,
|
|
||||||
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
||||||
Progress: progress,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDocuments handles GET /api/v1/documents
|
|
||||||
func (s *Server) GetDocuments(ctx context.Context, req DocumentListRequest) (DocumentsResponse, error) {
|
|
||||||
auth := getAuthFromContext(ctx)
|
|
||||||
if auth == nil {
|
|
||||||
return DocumentsResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := s.db.Queries.GetDocumentsWithStats(
|
|
||||||
ctx,
|
|
||||||
database.GetDocumentsWithStatsParams{
|
|
||||||
UserID: auth.UserName,
|
|
||||||
Query: req.Search,
|
|
||||||
Deleted: ptrOf(false),
|
|
||||||
Offset: (req.Page - 1) * req.Limit,
|
|
||||||
Limit: req.Limit,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return DocumentsResponse{}, &apiError{status: http.StatusInternalServerError, message: err.Error()}
|
|
||||||
}
|
|
||||||
|
|
||||||
total := int64(len(rows))
|
|
||||||
var nextPage *int64
|
|
||||||
var previousPage *int64
|
|
||||||
if req.Page*req.Limit < total {
|
|
||||||
nextPage = ptrOf(req.Page + 1)
|
|
||||||
}
|
|
||||||
if req.Page > 1 {
|
|
||||||
previousPage = ptrOf(req.Page - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
wordCounts := make([]WordCount, 0, len(rows))
|
|
||||||
for _, row := range rows {
|
|
||||||
if row.Words != nil {
|
|
||||||
wordCounts = append(wordCounts, WordCount{
|
|
||||||
DocumentID: row.ID,
|
|
||||||
Count: *row.Words,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DocumentsResponse{
|
|
||||||
Documents: rows,
|
|
||||||
Total: total,
|
|
||||||
Page: req.Page,
|
|
||||||
Limit: req.Limit,
|
|
||||||
NextPage: nextPage,
|
|
||||||
PreviousPage: previousPage,
|
|
||||||
Search: req.Search,
|
|
||||||
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
||||||
WordCounts: wordCounts,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProgress handles GET /api/v1/progress/:id
|
|
||||||
func (s *Server) GetProgress(ctx context.Context, req ProgressRequest) (Progress, error) {
|
|
||||||
auth := getAuthFromContext(ctx)
|
|
||||||
if auth == nil {
|
|
||||||
return Progress{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.ID == "" {
|
|
||||||
return Progress{}, &apiError{status: http.StatusBadRequest, message: "Document ID required"}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressRow, err := s.db.Queries.GetDocumentProgress(ctx, database.GetDocumentProgressParams{
|
|
||||||
UserID: auth.UserName,
|
|
||||||
DocumentID: req.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return Progress{}, &apiError{status: http.StatusNotFound, message: "Progress not found"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Progress{
|
|
||||||
UserID: progressRow.UserID,
|
|
||||||
DocumentID: progressRow.DocumentID,
|
|
||||||
DeviceID: progressRow.DeviceID,
|
|
||||||
Percentage: progressRow.Percentage,
|
|
||||||
Progress: progressRow.Progress,
|
|
||||||
CreatedAt: progressRow.CreatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetActivity handles GET /api/v1/activity
|
|
||||||
func (s *Server) GetActivity(ctx context.Context, req ActivityRequest) (ActivityResponse, error) {
|
|
||||||
auth := getAuthFromContext(ctx)
|
|
||||||
if auth == nil {
|
|
||||||
return ActivityResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
||||||
}
|
|
||||||
|
|
||||||
activities, err := s.db.Queries.GetActivity(ctx, database.GetActivityParams{
|
|
||||||
UserID: auth.UserName,
|
|
||||||
DocFilter: req.DocFilter,
|
|
||||||
DocumentID: req.DocumentID,
|
|
||||||
Offset: req.Offset,
|
|
||||||
Limit: req.Limit,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return ActivityResponse{}, &apiError{status: http.StatusInternalServerError, message: err.Error()}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ActivityResponse{
|
|
||||||
Activities: activities,
|
|
||||||
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSettings handles GET /api/v1/settings
|
|
||||||
func (s *Server) GetSettings(ctx context.Context, req SettingsRequest) (SettingsResponse, error) {
|
|
||||||
auth := getAuthFromContext(ctx)
|
|
||||||
if auth == nil {
|
|
||||||
return SettingsResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := s.db.Queries.GetUser(ctx, auth.UserName)
|
|
||||||
if err != nil {
|
|
||||||
return SettingsResponse{}, &apiError{status: http.StatusInternalServerError, message: err.Error()}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SettingsResponse{
|
|
||||||
Settings: []database.Setting{},
|
|
||||||
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
||||||
Timezone: user.Timezone,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAuthFromContext extracts authData from context
|
|
||||||
func getAuthFromContext(ctx context.Context) *authData {
|
|
||||||
auth, ok := ctx.Value("auth").(authData)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &auth
|
|
||||||
}
|
|
||||||
|
|
||||||
// apiError represents an API error with status code
|
|
||||||
type apiError struct {
|
|
||||||
status int
|
|
||||||
message string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements error interface
|
|
||||||
func (e *apiError) Error() string {
|
|
||||||
return e.message
|
|
||||||
}
|
|
||||||
|
|
||||||
// handlerFunc is a generic API handler function
|
|
||||||
type handlerFunc[T, R any] func(context.Context, T) (R, error)
|
|
||||||
|
|
||||||
// requestParser parses an HTTP request into a request struct
|
|
||||||
type requestParser[T any] func(*http.Request) T
|
|
||||||
|
|
||||||
// handle wraps an API handler function with HTTP response writing
|
|
||||||
func handle[T, R any](fn handlerFunc[T, R], parser requestParser[T]) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
req := parser(r)
|
|
||||||
resp, err := fn(r.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
if apiErr, ok := err.(*apiError); ok {
|
|
||||||
writeJSONError(w, apiErr.status, apiErr.message)
|
|
||||||
} else {
|
|
||||||
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, resp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDocumentRequest extracts document request from HTTP request
|
|
||||||
func parseDocumentRequest(r *http.Request) DocumentRequest {
|
|
||||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/documents/")
|
|
||||||
id := strings.TrimPrefix(path, "/")
|
|
||||||
return DocumentRequest{ID: id}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDocumentListRequest extracts document list request from URL query
|
|
||||||
func parseDocumentListRequest(r *http.Request) DocumentListRequest {
|
|
||||||
query := r.URL.Query()
|
|
||||||
page, _ := strconv.ParseInt(query.Get("page"), 10, 64)
|
|
||||||
if page == 0 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
limit, _ := strconv.ParseInt(query.Get("limit"), 10, 64)
|
|
||||||
if limit == 0 {
|
|
||||||
limit = 9
|
|
||||||
}
|
|
||||||
search := query.Get("search")
|
|
||||||
var searchPtr *string
|
|
||||||
if search != "" {
|
|
||||||
searchPtr = ptrOf("%" + search + "%")
|
|
||||||
}
|
|
||||||
return DocumentListRequest{
|
|
||||||
Page: page,
|
|
||||||
Limit: limit,
|
|
||||||
Search: searchPtr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseProgressRequest extracts progress request from HTTP request
|
|
||||||
func parseProgressRequest(r *http.Request) ProgressRequest {
|
|
||||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/progress/")
|
|
||||||
id := strings.TrimPrefix(path, "/")
|
|
||||||
return ProgressRequest{ID: id}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseActivityRequest extracts activity request from HTTP request
|
|
||||||
func parseActivityRequest(r *http.Request) ActivityRequest {
|
|
||||||
return ActivityRequest{
|
|
||||||
DocFilter: false,
|
|
||||||
DocumentID: "",
|
|
||||||
Offset: 0,
|
|
||||||
Limit: 100,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSettingsRequest extracts settings request from HTTP request
|
|
||||||
func parseSettingsRequest(r *http.Request) SettingsRequest {
|
|
||||||
return SettingsRequest{}
|
|
||||||
}
|
|
||||||
6
api/v1/oapi-codegen.yaml
Normal file
6
api/v1/oapi-codegen.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package: v1
|
||||||
|
generate:
|
||||||
|
std-http-server: true
|
||||||
|
strict-server: true
|
||||||
|
models: true
|
||||||
|
output: api.gen.go
|
||||||
526
api/v1/openapi.yaml
Normal file
526
api/v1/openapi.yaml
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: AnthoLume API v1
|
||||||
|
version: 1.0.0
|
||||||
|
description: REST API for AnthoLume document management system
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: /api/v1
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Document:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
author:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
deleted:
|
||||||
|
type: boolean
|
||||||
|
words:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- author
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- deleted
|
||||||
|
|
||||||
|
UserData:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
is_admin:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- is_admin
|
||||||
|
|
||||||
|
WordCount:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
document_id:
|
||||||
|
type: string
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required:
|
||||||
|
- document_id
|
||||||
|
- count
|
||||||
|
|
||||||
|
Progress:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
document_id:
|
||||||
|
type: string
|
||||||
|
device_id:
|
||||||
|
type: string
|
||||||
|
percentage:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
progress:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
required:
|
||||||
|
- user_id
|
||||||
|
- document_id
|
||||||
|
- device_id
|
||||||
|
- percentage
|
||||||
|
- progress
|
||||||
|
- created_at
|
||||||
|
|
||||||
|
Activity:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
document_id:
|
||||||
|
type: string
|
||||||
|
activity_type:
|
||||||
|
type: string
|
||||||
|
timestamp:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- user_id
|
||||||
|
- document_id
|
||||||
|
- activity_type
|
||||||
|
- timestamp
|
||||||
|
|
||||||
|
Setting:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- user_id
|
||||||
|
- key
|
||||||
|
- value
|
||||||
|
|
||||||
|
DocumentsResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
documents:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Document'
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
limit:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
next_page:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
previous_page:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
search:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/UserData'
|
||||||
|
word_counts:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/WordCount'
|
||||||
|
required:
|
||||||
|
- documents
|
||||||
|
- total
|
||||||
|
- page
|
||||||
|
- limit
|
||||||
|
- user
|
||||||
|
- word_counts
|
||||||
|
|
||||||
|
DocumentResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
document:
|
||||||
|
$ref: '#/components/schemas/Document'
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/UserData'
|
||||||
|
progress:
|
||||||
|
$ref: '#/components/schemas/Progress'
|
||||||
|
required:
|
||||||
|
- document
|
||||||
|
- user
|
||||||
|
|
||||||
|
ProgressResponse:
|
||||||
|
$ref: '#/components/schemas/Progress'
|
||||||
|
|
||||||
|
ActivityResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
activities:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Activity'
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/UserData'
|
||||||
|
required:
|
||||||
|
- activities
|
||||||
|
- user
|
||||||
|
|
||||||
|
SettingsResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
settings:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Setting'
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/UserData'
|
||||||
|
timezone:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- settings
|
||||||
|
- user
|
||||||
|
|
||||||
|
LoginRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
|
||||||
|
LoginResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
is_admin:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- is_admin
|
||||||
|
|
||||||
|
ErrorResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
|
||||||
|
securitySchemes:
|
||||||
|
BearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/documents:
|
||||||
|
get:
|
||||||
|
summary: List documents
|
||||||
|
operationId: getDocuments
|
||||||
|
tags:
|
||||||
|
- Documents
|
||||||
|
parameters:
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
default: 1
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
default: 9
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/DocumentsResponse'
|
||||||
|
401:
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
500:
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/documents/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get a single document
|
||||||
|
operationId: getDocument
|
||||||
|
tags:
|
||||||
|
- Documents
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/DocumentResponse'
|
||||||
|
401:
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
404:
|
||||||
|
description: Document not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
500:
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/progress/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get document progress
|
||||||
|
operationId: getProgress
|
||||||
|
tags:
|
||||||
|
- Progress
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ProgressResponse'
|
||||||
|
401:
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
404:
|
||||||
|
description: Progress not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
500:
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/activity:
|
||||||
|
get:
|
||||||
|
summary: Get activity data
|
||||||
|
operationId: getActivity
|
||||||
|
tags:
|
||||||
|
- Activity
|
||||||
|
parameters:
|
||||||
|
- name: doc_filter
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: document_id
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
default: 0
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
default: 100
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ActivityResponse'
|
||||||
|
401:
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
500:
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/settings:
|
||||||
|
get:
|
||||||
|
summary: Get user settings
|
||||||
|
operationId: getSettings
|
||||||
|
tags:
|
||||||
|
- Settings
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SettingsResponse'
|
||||||
|
401:
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
500:
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/auth/login:
|
||||||
|
post:
|
||||||
|
summary: User login
|
||||||
|
operationId: login
|
||||||
|
tags:
|
||||||
|
- Auth
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginRequest'
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful login
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginResponse'
|
||||||
|
400:
|
||||||
|
description: Bad request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
401:
|
||||||
|
description: Invalid credentials
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
500:
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/auth/logout:
|
||||||
|
post:
|
||||||
|
summary: User logout
|
||||||
|
operationId: logout
|
||||||
|
tags:
|
||||||
|
- Auth
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful logout
|
||||||
|
401:
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/auth/me:
|
||||||
|
get:
|
||||||
|
summary: Get current user info
|
||||||
|
operationId: getMe
|
||||||
|
tags:
|
||||||
|
- Auth
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginResponse'
|
||||||
|
401:
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
38
api/v1/progress.go
Normal file
38
api/v1/progress.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"reichard.io/antholume/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GET /progress/{id}
|
||||||
|
func (s *Server) GetProgress(ctx context.Context, request GetProgressRequestObject) (GetProgressResponseObject, error) {
|
||||||
|
auth, ok := s.getSessionFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return GetProgress401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Id == "" {
|
||||||
|
return GetProgress404JSONResponse{Code: 404, Message: "Document ID required"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
progressRow, err := s.db.Queries.GetDocumentProgress(ctx, database.GetDocumentProgressParams{
|
||||||
|
UserID: auth.UserName,
|
||||||
|
DocumentID: request.Id,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return GetProgress404JSONResponse{Code: 404, Message: "Progress not found"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := Progress{
|
||||||
|
UserId: progressRow.UserID,
|
||||||
|
DocumentId: progressRow.DocumentID,
|
||||||
|
DeviceId: progressRow.DeviceID,
|
||||||
|
Percentage: progressRow.Percentage,
|
||||||
|
Progress: progressRow.Progress,
|
||||||
|
CreatedAt: parseTime(progressRow.CreatedAt),
|
||||||
|
}
|
||||||
|
return GetProgress200JSONResponse(response), nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"reichard.io/antholume/config"
|
"reichard.io/antholume/config"
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ StrictServerInterface = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
db *database.DBManager
|
db *database.DBManager
|
||||||
@@ -20,7 +24,11 @@ func NewServer(db *database.DBManager, cfg *config.Config) *Server {
|
|||||||
db: db,
|
db: db,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
s.registerRoutes()
|
|
||||||
|
// Create strict handler with authentication middleware
|
||||||
|
strictHandler := NewStrictHandler(s, []StrictMiddlewareFunc{s.authMiddleware})
|
||||||
|
|
||||||
|
s.mux = HandlerFromMuxWithBaseURL(strictHandler, s.mux, "/api/v1").(*http.ServeMux)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,23 +36,31 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
s.mux.ServeHTTP(w, r)
|
s.mux.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerRoutes sets up all API routes
|
// authMiddleware adds authentication context to requests
|
||||||
func (s *Server) registerRoutes() {
|
func (s *Server) authMiddleware(handler StrictHandlerFunc, operationID string) StrictHandlerFunc {
|
||||||
// Documents endpoints
|
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, request any) (any, error) {
|
||||||
s.mux.HandleFunc("/api/v1/documents", s.withAuth(wrapRequest(s.GetDocuments, parseDocumentListRequest)))
|
// Store request and response in context for all handlers
|
||||||
s.mux.HandleFunc("/api/v1/documents/", s.withAuth(wrapRequest(s.GetDocument, parseDocumentRequest)))
|
ctx = context.WithValue(ctx, "request", r)
|
||||||
|
ctx = context.WithValue(ctx, "response", w)
|
||||||
|
|
||||||
// Progress endpoints
|
// Skip auth for login endpoint
|
||||||
s.mux.HandleFunc("/api/v1/progress/", s.withAuth(wrapRequest(s.GetProgress, parseProgressRequest)))
|
if operationID == "Login" {
|
||||||
|
return handler(ctx, w, r, request)
|
||||||
|
}
|
||||||
|
|
||||||
// Activity endpoints
|
auth, ok := s.getSession(r)
|
||||||
s.mux.HandleFunc("/api/v1/activity", s.withAuth(wrapRequest(s.GetActivity, parseActivityRequest)))
|
if !ok {
|
||||||
|
// Write 401 response directly
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(401)
|
||||||
|
json.NewEncoder(w).Encode(ErrorResponse{Code: 401, Message: "Unauthorized"})
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Settings endpoints
|
// Store auth in context for handlers to access
|
||||||
s.mux.HandleFunc("/api/v1/settings", s.withAuth(wrapRequest(s.GetSettings, parseSettingsRequest)))
|
ctx = context.WithValue(ctx, "auth", auth)
|
||||||
|
|
||||||
// Auth endpoints
|
return handler(ctx, w, r, request)
|
||||||
s.mux.HandleFunc("/api/v1/auth/login", s.apiLogin)
|
}
|
||||||
s.mux.HandleFunc("/api/v1/auth/logout", s.withAuth(s.apiLogout))
|
|
||||||
s.mux.HandleFunc("/api/v1/auth/me", s.withAuth(s.apiGetMe))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,70 +5,54 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"reichard.io/antholume/config"
|
"reichard.io/antholume/config"
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewServer(t *testing.T) {
|
type ServerTestSuite struct {
|
||||||
db := setupTestDB(t)
|
suite.Suite
|
||||||
cfg := testConfig()
|
db *database.DBManager
|
||||||
|
cfg *config.Config
|
||||||
server := NewServer(db, cfg)
|
srv *Server
|
||||||
|
|
||||||
if server == nil {
|
|
||||||
t.Fatal("NewServer returned nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if server.mux == nil {
|
|
||||||
t.Fatal("Server mux is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if server.db == nil {
|
|
||||||
t.Fatal("Server db is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if server.cfg == nil {
|
|
||||||
t.Fatal("Server cfg is nil")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerServeHTTP(t *testing.T) {
|
func TestServer(t *testing.T) {
|
||||||
db := setupTestDB(t)
|
suite.Run(t, new(ServerTestSuite))
|
||||||
cfg := testConfig()
|
|
||||||
|
|
||||||
server := NewServer(db, cfg)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
|
|
||||||
server.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusUnauthorized {
|
|
||||||
t.Fatalf("Expected 401 for unauthenticated request, got %d", w.Code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTestDB(t *testing.T) *database.DBManager {
|
func (suite *ServerTestSuite) SetupTest() {
|
||||||
t.Helper()
|
suite.cfg = &config.Config{
|
||||||
|
|
||||||
cfg := testConfig()
|
|
||||||
cfg.DBType = "memory"
|
|
||||||
|
|
||||||
return database.NewMgr(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testConfig() *config.Config {
|
|
||||||
return &config.Config{
|
|
||||||
ListenPort: "8080",
|
ListenPort: "8080",
|
||||||
DBType: "memory",
|
DBType: "memory",
|
||||||
DBName: "test",
|
DBName: "test",
|
||||||
ConfigPath: "/tmp",
|
ConfigPath: "/tmp",
|
||||||
CookieAuthKey: "test-auth-key-32-bytes-long-enough",
|
CookieAuthKey: "test-auth-key-32-bytes-long-enough",
|
||||||
CookieEncKey: "0123456789abcdef", // Exactly 16 bytes
|
CookieEncKey: "0123456789abcdef",
|
||||||
CookieSecure: false,
|
CookieSecure: false,
|
||||||
CookieHTTPOnly: true,
|
CookieHTTPOnly: true,
|
||||||
Version: "test",
|
Version: "test",
|
||||||
DemoMode: false,
|
DemoMode: false,
|
||||||
RegistrationEnabled: true,
|
RegistrationEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suite.db = database.NewMgr(suite.cfg)
|
||||||
|
suite.srv = NewServer(suite.db, suite.cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ServerTestSuite) TestNewServer() {
|
||||||
|
suite.NotNil(suite.srv)
|
||||||
|
suite.NotNil(suite.srv.mux)
|
||||||
|
suite.NotNil(suite.srv.db)
|
||||||
|
suite.NotNil(suite.srv.cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ServerTestSuite) TestServerServeHTTP() {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
suite.srv.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
suite.Equal(http.StatusUnauthorized, w.Code)
|
||||||
}
|
}
|
||||||
26
api/v1/settings.go
Normal file
26
api/v1/settings.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GET /settings
|
||||||
|
func (s *Server) GetSettings(ctx context.Context, request GetSettingsRequestObject) (GetSettingsResponseObject, error) {
|
||||||
|
auth, ok := s.getSessionFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return GetSettings401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := s.db.Queries.GetUser(ctx, auth.UserName)
|
||||||
|
if err != nil {
|
||||||
|
return GetSettings500JSONResponse{Code: 500, Message: err.Error()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := SettingsResponse{
|
||||||
|
Settings: []Setting{},
|
||||||
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
||||||
|
Timezone: user.Timezone,
|
||||||
|
}
|
||||||
|
return GetSettings200JSONResponse(response), nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import "reichard.io/antholume/database"
|
|
||||||
|
|
||||||
// DocumentsResponse is the API response for document list endpoints
|
|
||||||
type DocumentsResponse struct {
|
|
||||||
Documents []database.GetDocumentsWithStatsRow `json:"documents"`
|
|
||||||
Total int64 `json:"total"`
|
|
||||||
Page int64 `json:"page"`
|
|
||||||
Limit int64 `json:"limit"`
|
|
||||||
NextPage *int64 `json:"next_page"`
|
|
||||||
PreviousPage *int64 `json:"previous_page"`
|
|
||||||
Search *string `json:"search"`
|
|
||||||
User UserData `json:"user"`
|
|
||||||
WordCounts []WordCount `json:"word_counts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DocumentResponse is the API response for single document endpoints
|
|
||||||
type DocumentResponse struct {
|
|
||||||
Document database.Document `json:"document"`
|
|
||||||
User UserData `json:"user"`
|
|
||||||
Progress *Progress `json:"progress"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserData represents authenticated user context
|
|
||||||
type UserData struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
IsAdmin bool `json:"is_admin"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// WordCount represents computed word count statistics
|
|
||||||
type WordCount struct {
|
|
||||||
DocumentID string `json:"document_id"`
|
|
||||||
Count int64 `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress represents reading progress for a document
|
|
||||||
type Progress struct {
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
DocumentID string `json:"document_id"`
|
|
||||||
DeviceID string `json:"device_id"`
|
|
||||||
Percentage float64 `json:"percentage"`
|
|
||||||
Progress string `json:"progress"`
|
|
||||||
CreatedAt string `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActivityResponse is the API response for activity endpoints
|
|
||||||
type ActivityResponse struct {
|
|
||||||
Activities []database.GetActivityRow `json:"activities"`
|
|
||||||
User UserData `json:"user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettingsResponse is the API response for settings endpoints
|
|
||||||
type SettingsResponse struct {
|
|
||||||
Settings []database.Setting `json:"settings"`
|
|
||||||
User UserData `json:"user"`
|
|
||||||
Timezone *string `json:"timezone"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginRequest is the request body for login
|
|
||||||
type LoginRequest struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginResponse is the response for successful login
|
|
||||||
type LoginResponse struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
IsAdmin bool `json:"is_admin"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorResponse represents an API error
|
|
||||||
type ErrorResponse struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// writeJSON writes a JSON response
|
// writeJSON writes a JSON response (deprecated - used by tests only)
|
||||||
func writeJSON(w http.ResponseWriter, status int, data any) {
|
func writeJSON(w http.ResponseWriter, status int, data any) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
@@ -16,7 +17,7 @@ func writeJSON(w http.ResponseWriter, status int, data any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeJSONError writes a JSON error response
|
// writeJSONError writes a JSON error response (deprecated - used by tests only)
|
||||||
func writeJSONError(w http.ResponseWriter, status int, message string) {
|
func writeJSONError(w http.ResponseWriter, status int, message string) {
|
||||||
writeJSON(w, status, ErrorResponse{
|
writeJSON(w, status, ErrorResponse{
|
||||||
Code: status,
|
Code: status,
|
||||||
@@ -24,14 +25,14 @@ func writeJSONError(w http.ResponseWriter, status int, message string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryParams represents parsed query parameters
|
// QueryParams represents parsed query parameters (deprecated - used by tests only)
|
||||||
type QueryParams struct {
|
type QueryParams struct {
|
||||||
Page int64
|
Page int64
|
||||||
Limit int64
|
Limit int64
|
||||||
Search *string
|
Search *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseQueryParams parses URL query parameters
|
// parseQueryParams parses URL query parameters (deprecated - used by tests only)
|
||||||
func parseQueryParams(query url.Values, defaultLimit int64) QueryParams {
|
func parseQueryParams(query url.Values, defaultLimit int64) QueryParams {
|
||||||
page, _ := strconv.ParseInt(query.Get("page"), 10, 64)
|
page, _ := strconv.ParseInt(query.Get("page"), 10, 64)
|
||||||
if page == 0 {
|
if page == 0 {
|
||||||
@@ -57,3 +58,12 @@ func parseQueryParams(query url.Values, defaultLimit int64) QueryParams {
|
|||||||
func ptrOf[T any](v T) *T {
|
func ptrOf[T any](v T) *T {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseTime parses a string to time.Time
|
||||||
|
func parseTime(s string) time.Time {
|
||||||
|
t, _ := time.Parse(time.RFC3339, s)
|
||||||
|
if t.IsZero() {
|
||||||
|
t, _ = time.Parse("2006-01-02T15:04:05", s)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
@@ -5,56 +5,46 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWriteJSON(t *testing.T) {
|
type UtilsTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUtils(t *testing.T) {
|
||||||
|
suite.Run(t, new(UtilsTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UtilsTestSuite) TestWriteJSON() {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]string{"test": "value"}
|
data := map[string]string{"test": "value"}
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, data)
|
writeJSON(w, http.StatusOK, data)
|
||||||
|
|
||||||
if w.Header().Get("Content-Type") != "application/json" {
|
suite.Equal("application/json", w.Header().Get("Content-Type"))
|
||||||
t.Errorf("Expected Content-Type 'application/json', got '%s'", w.Header().Get("Content-Type"))
|
suite.Equal(http.StatusOK, w.Code)
|
||||||
}
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Errorf("Expected status 200, got %d", w.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp map[string]string
|
var resp map[string]string
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
suite.Equal("value", resp["test"])
|
||||||
}
|
|
||||||
|
|
||||||
if resp["test"] != "value" {
|
|
||||||
t.Errorf("Expected 'value', got '%s'", resp["test"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteJSONError(t *testing.T) {
|
func (suite *UtilsTestSuite) TestWriteJSONError() {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
writeJSONError(w, http.StatusBadRequest, "test error")
|
writeJSONError(w, http.StatusBadRequest, "test error")
|
||||||
|
|
||||||
if w.Code != http.StatusBadRequest {
|
suite.Equal(http.StatusBadRequest, w.Code)
|
||||||
t.Errorf("Expected status 400, got %d", w.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp ErrorResponse
|
var resp ErrorResponse
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
suite.Equal(http.StatusBadRequest, resp.Code)
|
||||||
}
|
suite.Equal("test error", resp.Message)
|
||||||
|
|
||||||
if resp.Code != http.StatusBadRequest {
|
|
||||||
t.Errorf("Expected code 400, got %d", resp.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Message != "test error" {
|
|
||||||
t.Errorf("Expected message 'test error', got '%s'", resp.Message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseQueryParams(t *testing.T) {
|
func (suite *UtilsTestSuite) TestParseQueryParams() {
|
||||||
query := make(map[string][]string)
|
query := make(map[string][]string)
|
||||||
query["page"] = []string{"2"}
|
query["page"] = []string{"2"}
|
||||||
query["limit"] = []string{"15"}
|
query["limit"] = []string{"15"}
|
||||||
@@ -62,46 +52,25 @@ func TestParseQueryParams(t *testing.T) {
|
|||||||
|
|
||||||
params := parseQueryParams(query, 9)
|
params := parseQueryParams(query, 9)
|
||||||
|
|
||||||
if params.Page != 2 {
|
suite.Equal(int64(2), params.Page)
|
||||||
t.Errorf("Expected page 2, got %d", params.Page)
|
suite.Equal(int64(15), params.Limit)
|
||||||
}
|
suite.NotNil(params.Search)
|
||||||
|
|
||||||
if params.Limit != 15 {
|
|
||||||
t.Errorf("Expected limit 15, got %d", params.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.Search == nil {
|
|
||||||
t.Fatal("Expected search to be set")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseQueryParamsDefaults(t *testing.T) {
|
func (suite *UtilsTestSuite) TestParseQueryParamsDefaults() {
|
||||||
query := make(map[string][]string)
|
query := make(map[string][]string)
|
||||||
|
|
||||||
params := parseQueryParams(query, 9)
|
params := parseQueryParams(query, 9)
|
||||||
|
|
||||||
if params.Page != 1 {
|
suite.Equal(int64(1), params.Page)
|
||||||
t.Errorf("Expected page 1, got %d", params.Page)
|
suite.Equal(int64(9), params.Limit)
|
||||||
}
|
suite.Nil(params.Search)
|
||||||
|
|
||||||
if params.Limit != 9 {
|
|
||||||
t.Errorf("Expected limit 9, got %d", params.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.Search != nil {
|
|
||||||
t.Errorf("Expected search to be nil, got '%v'", params.Search)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPtrOf(t *testing.T) {
|
func (suite *UtilsTestSuite) TestPtrOf() {
|
||||||
value := "test"
|
value := "test"
|
||||||
ptr := ptrOf(value)
|
ptr := ptrOf(value)
|
||||||
|
|
||||||
if ptr == nil {
|
suite.NotNil(ptr)
|
||||||
t.Fatal("Expected non-nil pointer")
|
suite.Equal("test", *ptr)
|
||||||
}
|
|
||||||
|
|
||||||
if *ptr != "test" {
|
|
||||||
t.Errorf("Expected 'test', got '%s'", *ptr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1769089682,
|
"lastModified": 1773524153,
|
||||||
"narHash": "sha256-9yA/LIuAVQq0lXelrZPjLuLVuZdm03p8tfmHhnDIkms=",
|
"narHash": "sha256-Jms57zzlFf64ayKzzBWSE2SGvJmK+NGt8Gli71d9kmY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "078d69f03934859a181e81ba987c2bb033eebfc5",
|
"rev": "e9f278faa1d0c2fc835bd331d4666b59b505a410",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
16
go.mod
16
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module reichard.io/antholume
|
module reichard.io/antholume
|
||||||
|
|
||||||
go 1.24
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.10.3
|
github.com/PuerkitoBio/goquery v1.10.3
|
||||||
@@ -9,9 +9,11 @@ require (
|
|||||||
github.com/gin-contrib/multitemplate v1.1.1
|
github.com/gin-contrib/multitemplate v1.1.1
|
||||||
github.com/gin-contrib/sessions v1.0.4
|
github.com/gin-contrib/sessions v1.0.4
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
|
github.com/gorilla/sessions v1.4.0
|
||||||
github.com/itchyny/gojq v0.12.17
|
github.com/itchyny/gojq v0.12.17
|
||||||
github.com/jarcoal/httpmock v1.3.1
|
github.com/jarcoal/httpmock v1.3.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
|
github.com/oapi-codegen/runtime v1.2.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pressly/goose/v3 v3.24.3
|
github.com/pressly/goose/v3 v3.24.3
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
@@ -25,11 +27,10 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/bytedance/sonic v1.14.0 // indirect
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
@@ -43,10 +44,8 @@ require (
|
|||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
|
||||||
github.com/itchyny/timefmt-go v0.1.6 // indirect
|
github.com/itchyny/timefmt-go v0.1.6 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
@@ -65,7 +64,6 @@ require (
|
|||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/arch v0.20.0 // indirect
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
golang.org/x/crypto v0.41.0 // indirect
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/mod v0.27.0 // indirect
|
|
||||||
golang.org/x/net v0.43.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
@@ -73,13 +71,7 @@ require (
|
|||||||
golang.org/x/tools v0.36.0 // indirect
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.7 // indirect
|
google.golang.org/protobuf v1.36.7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/uint128 v1.3.0 // indirect
|
|
||||||
modernc.org/cc/v3 v3.41.0 // indirect
|
|
||||||
modernc.org/ccgo/v3 v3.17.0 // indirect
|
|
||||||
modernc.org/libc v1.66.6 // indirect
|
modernc.org/libc v1.66.6 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/opt v0.1.4 // indirect
|
|
||||||
modernc.org/strutil v1.2.1 // indirect
|
|
||||||
modernc.org/token v1.1.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
319
go.sum
319
go.sum
@@ -1,147 +1,56 @@
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
|
||||||
github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0=
|
|
||||||
github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw=
|
|
||||||
github.com/ClickHouse/ch-go v0.65.1 h1:SLuxmLl5Mjj44/XbINsK2HFvzqup0s6rwKLFH347ZhU=
|
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.16.0 h1:rhMfnPewXPnY4Q4lQRGdYuTLRBRKJEIEYHtbUMrzmvI=
|
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.16.0/go.mod h1:J7SPfIxwR+x4mQ+o8MLSe0oY50NNntEqCIjFe/T1VPM=
|
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.34.0 h1:Y4rqkdrRHgExvC4o/NTbLdY5LFQ3LHS77/RNFxFX3Co=
|
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
|
||||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
|
||||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
|
||||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||||
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
||||||
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
|
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
|
||||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
|
||||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
|
||||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
|
||||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
|
||||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
|
||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
|
||||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
|
||||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
|
||||||
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
|
|
||||||
github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
|
||||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
|
||||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn0jg4=
|
|
||||||
github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
|
||||||
github.com/elastic/go-sysinfo v1.15.3 h1:W+RnmhKFkqPTCRoFq2VCTmsT4p/fwpo+3gKNQsn1XU0=
|
|
||||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
|
||||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
|
||||||
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/gin-contrib/multitemplate v0.0.0-20231230012943-32b233489a81 h1:hQ/WeoPMTbN8NHk5i96dWy3D4uF7yCU+kORyWG+P4oU=
|
|
||||||
github.com/gin-contrib/multitemplate v0.0.0-20231230012943-32b233489a81/go.mod h1:XLLtIXoP9+9zGcEDc7gAGV3AksGPO+vzv4kXHMJSdU0=
|
|
||||||
github.com/gin-contrib/multitemplate v1.1.1 h1:uzhT/ZWS9nBd1h6P+AaxWaVSVAJRAcKH4yafrBU8sPc=
|
github.com/gin-contrib/multitemplate v1.1.1 h1:uzhT/ZWS9nBd1h6P+AaxWaVSVAJRAcKH4yafrBU8sPc=
|
||||||
github.com/gin-contrib/multitemplate v1.1.1/go.mod h1:1Sa4984P8+x87U0cg5yWxK4jpbK1cXMYegUCZK6XT/M=
|
github.com/gin-contrib/multitemplate v1.1.1/go.mod h1:1Sa4984P8+x87U0cg5yWxK4jpbK1cXMYegUCZK6XT/M=
|
||||||
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
|
|
||||||
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
|
|
||||||
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
||||||
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
|
||||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
|
||||||
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
|
|
||||||
github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
|
|
||||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
|
||||||
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
|
|
||||||
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
|
||||||
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
|
|
||||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
|
||||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
@@ -150,82 +59,29 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
|||||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
|
||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
|
||||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
|
||||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
|
||||||
github.com/itchyny/gojq v0.12.14 h1:6k8vVtsrhQSYgSGg827AD+PVVaB1NLXEdX+dda2oZCc=
|
|
||||||
github.com/itchyny/gojq v0.12.14/go.mod h1:y1G7oO7XkcR1LPZO59KyoCRy08T3j9vDYRV0GgYSS+s=
|
|
||||||
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
|
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
|
||||||
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
|
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
|
||||||
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
|
|
||||||
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
|
|
||||||
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
|
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
|
||||||
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
|
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
|
||||||
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
|
|
||||||
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
|
||||||
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
||||||
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
|
||||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
|
||||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
|
||||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
|
||||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
|
||||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
|
||||||
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||||
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -233,154 +89,74 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0=
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
|
||||||
github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40=
|
|
||||||
github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M=
|
|
||||||
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
|
||||||
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
|
||||||
github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s=
|
|
||||||
github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
|
||||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pressly/goose/v3 v3.17.0 h1:fT4CL3LRm4kfyLuPWzDFAoxjR5ZHjeJ6uQhibQtBaIs=
|
|
||||||
github.com/pressly/goose/v3 v3.17.0/go.mod h1:22aw7NpnCPlS86oqkO/+3+o9FuCaJg4ZVWRUO3oGzHQ=
|
|
||||||
github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM=
|
github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM=
|
||||||
github.com/pressly/goose/v3 v3.24.3/go.mod h1:v9zYL4xdViLHCUUJh/mhjnm6JrK7Eul8AS93IxiZM4E=
|
github.com/pressly/goose/v3 v3.24.3/go.mod h1:v9zYL4xdViLHCUUJh/mhjnm6JrK7Eul8AS93IxiZM4E=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
|
||||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
|
||||||
github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec=
|
|
||||||
github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw=
|
|
||||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/taylorskalyo/goreader v0.0.0-20230626212555-e7f5644f8115 h1:OEAIMYp5l9kJ2kT9UPL5QSUriKIIDhnLmpJTy69sltA=
|
|
||||||
github.com/taylorskalyo/goreader v0.0.0-20230626212555-e7f5644f8115/go.mod h1:AIVbkIe1G7fpFHiKOdxZnU5p9tFPYNTQyH3H5IrRkGw=
|
|
||||||
github.com/taylorskalyo/goreader v1.0.1 h1:eS9SYiHai2aAHhm+YMGRTqrvNt2aoRMTd7p6ftm0crY=
|
github.com/taylorskalyo/goreader v1.0.1 h1:eS9SYiHai2aAHhm+YMGRTqrvNt2aoRMTd7p6ftm0crY=
|
||||||
github.com/taylorskalyo/goreader v1.0.1/go.mod h1:JrUsWCgnk4C3P5Jsr7Pf2mFrMpsR0ls/0bjR5aorYTI=
|
github.com/taylorskalyo/goreader v1.0.1/go.mod h1:JrUsWCgnk4C3P5Jsr7Pf2mFrMpsR0ls/0bjR5aorYTI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
|
||||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
|
||||||
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
|
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
|
||||||
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
|
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
|
||||||
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
|
|
||||||
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
|
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
|
||||||
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd h1:dzWP1Lu+A40W883dK/Mr3xyDSM/2MggS8GtHT0qgAnE=
|
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 h1:LY6cI8cP4B9rrpTleZk95+08kl2gF4rixG7+V/dwL6Q=
|
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2 h1:E0yUuuX7UmPxXm92+yQCjMveLFO3zfvYFIJVuAqsVRA=
|
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2/go.mod h1:fjBLQ2TdQNl4bMjuWl9adoTGBypwUTPoGC+EqYqiIcU=
|
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1 h1:ixAiqjj2S/dNuJqrz4AxSqgw2P5OBMXp68hB5nNriUk=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc=
|
|
||||||
go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs=
|
|
||||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
|
||||||
go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ=
|
|
||||||
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU=
|
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
|
||||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|
||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
@@ -390,7 +166,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
@@ -398,21 +173,15 @@ golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
|||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
@@ -422,7 +191,6 @@ golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXct
|
|||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
@@ -431,12 +199,10 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
|||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
@@ -447,83 +213,42 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
|
||||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
|
||||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
|
||||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||||
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
|
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/ccgo/v3 v3.17.0 h1:o3OmOqx4/OFnl4Vm3G8Bgmqxnvxnh0nbxeT5p/dWChA=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I=
|
|
||||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
|
||||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
|
||||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|
||||||
modernc.org/libc v1.40.7 h1:oeLS0G067ZqUu+v143Dqad0btMfKmNS7SuOsnkq0Ysg=
|
|
||||||
modernc.org/libc v1.40.7/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
|
||||||
modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
|
modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
|
||||||
modernc.org/libc v1.66.6/go.mod h1:j8z0EYAuumoMQ3+cWXtmw6m+LYn3qm8dcZDFtFTSq+M=
|
modernc.org/libc v1.66.6/go.mod h1:j8z0EYAuumoMQ3+cWXtmw6m+LYn3qm8dcZDFtFTSq+M=
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
|
||||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
|
||||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
|
||||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
||||||
|
|||||||
Reference in New Issue
Block a user