Add google authentication
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"ersteller-lib"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo/v4"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const oauthGoogleUrlAPI = "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
|
||||
|
||||
type AuthEnv struct {
|
||||
GoogleClientId string
|
||||
GoogleClientSecret string
|
||||
GoogleRedirectUrl string
|
||||
IsLocal bool
|
||||
EmailSessionKey string
|
||||
UserIdSessionKey string
|
||||
SessionName string
|
||||
}
|
||||
|
||||
type UserRepository interface {
|
||||
GetUserId(email string) (int, error)
|
||||
Create(email string) (int, error)
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Config oauth2.Config
|
||||
repo *GoogleAuthRepository
|
||||
isLocal bool
|
||||
userRepo UserRepository
|
||||
sessionStore *sessions.CookieStore
|
||||
GoogleLoginRoute ersteller_lib.Route
|
||||
environment AuthEnv
|
||||
}
|
||||
|
||||
func NewAuth(env AuthEnv, repo *GoogleAuthRepository, userRepo UserRepository, sessionStore *sessions.CookieStore) *Auth {
|
||||
config := oauth2.Config{
|
||||
ClientID: env.GoogleClientId,
|
||||
ClientSecret: env.GoogleClientSecret,
|
||||
Endpoint: google.Endpoint,
|
||||
RedirectURL: env.GoogleRedirectUrl,
|
||||
Scopes: []string{"https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/userinfo.email"},
|
||||
}
|
||||
return &Auth{
|
||||
repo: repo,
|
||||
Config: config,
|
||||
isLocal: env.IsLocal,
|
||||
userRepo: userRepo,
|
||||
sessionStore: sessionStore,
|
||||
environment: env,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Auth) GetAuthURL(state string) string {
|
||||
return a.Config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.ApprovalForce)
|
||||
}
|
||||
|
||||
func (a *Auth) ParseUserData(code string) (GoogleUserData, error) {
|
||||
data, err := a.getUserDataFromGoogle(code)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed getting user data from Google: %v", err)
|
||||
return GoogleUserData{}, err
|
||||
}
|
||||
ersteller_lib.LogDebug("user data: %v", data)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (a *Auth) getUserDataFromGoogle(code string) (GoogleUserData, error) {
|
||||
// Use code to get token and get user info from Google.
|
||||
token, err := a.Config.Exchange(context.Background(), code)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("code exchange wrong: %v", err)
|
||||
return GoogleUserData{}, err
|
||||
}
|
||||
|
||||
response, err := http.Get(oauthGoogleUrlAPI + token.AccessToken)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed getting user info: %v", err)
|
||||
return GoogleUserData{}, nil
|
||||
}
|
||||
defer response.Body.Close()
|
||||
contents, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed read response: %v", err)
|
||||
return GoogleUserData{}, nil
|
||||
}
|
||||
userData := GoogleUserData{}
|
||||
err = json.Unmarshal(contents, &userData)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed to unmarshal user data: %v", err)
|
||||
return GoogleUserData{}, nil
|
||||
}
|
||||
userData.Token = token
|
||||
|
||||
return userData, nil
|
||||
}
|
||||
|
||||
func (a *Auth) SaveCredentials(userId int, token *oauth2.Token) error {
|
||||
ersteller_lib.Debug("saving google credentials for user ", userId)
|
||||
googleAuth, err := a.repo.ReadByUserId(userId)
|
||||
if err != nil {
|
||||
ersteller_lib.LogDebug("no GoogleAuth found for user %d, creating new one", userId)
|
||||
_, err = a.repo.Create(GoogleAuth{
|
||||
UserId: userId,
|
||||
Credentials: Credentials{
|
||||
Token: *token,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
googleAuth.Credentials.Token = *token
|
||||
return a.repo.Update(userId, googleAuth)
|
||||
}
|
||||
|
||||
func (a *Auth) GetCredentials(userId int) (*oauth2.Token, error) {
|
||||
credentials, err := a.repo.ReadByUserId(userId)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed to get credentials for user %d: %v", userId, err)
|
||||
return nil, err
|
||||
}
|
||||
if credentials.Credentials.Token.AccessToken == "" {
|
||||
ersteller_lib.LogError("no credentials found for user %d", userId)
|
||||
return nil, errors.New("no credentials found")
|
||||
}
|
||||
return &credentials.Credentials.Token, nil
|
||||
}
|
||||
|
||||
const oAuthStateCookieName = "oauthstate"
|
||||
const GoogleLogin = "/login/google"
|
||||
const GoogleLoginCallback = "/email/authenticated"
|
||||
|
||||
func (a *Auth) AddRoutes(e *echo.Echo) []ersteller_lib.Route {
|
||||
googleLoginRoute := ersteller_lib.NewGetRoute(GoogleLogin, func(c echo.Context) error {
|
||||
state := a.generateStateOauthCookie(c.Response())
|
||||
ersteller_lib.LogDebug("Value: %v", state)
|
||||
authenticationUrl := a.GetAuthURL(state)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, authenticationUrl)
|
||||
})
|
||||
googleLoginRoute.Add(e)
|
||||
a.GoogleLoginRoute = googleLoginRoute
|
||||
|
||||
authenticatedRoute := ersteller_lib.NewGetRoute(GoogleLoginCallback, func(c echo.Context) error {
|
||||
ersteller_lib.LogDebug("email authenticated called")
|
||||
oauthstate, err := c.Cookie(oAuthStateCookieName)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to get cookie: %v", err)
|
||||
return err
|
||||
}
|
||||
if c.FormValue("state") != oauthstate.Value {
|
||||
ersteller_lib.LogError("Failed to verify google oauth state")
|
||||
return err
|
||||
}
|
||||
code := c.FormValue("code")
|
||||
data, err := a.ParseUserData(code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userId, err := a.userRepo.GetUserId(data.Email)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to get user id: %v", err)
|
||||
userId, err = a.userRepo.Create(data.Email)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to create user: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = a.saveEmailToSessionStore(c, data.Email, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = a.SaveCredentials(userId, data.Token)
|
||||
if err != nil {
|
||||
ersteller_lib.Error("failed to save credentials for user ", userId, ": ", err)
|
||||
return err
|
||||
}
|
||||
ersteller_lib.Debug("saved credentials for user", userId)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
})
|
||||
authenticatedRoute.Add(e)
|
||||
|
||||
logoutRoute := ersteller_lib.NewGetRoute("/logout", func(c echo.Context) error {
|
||||
// Clear the session
|
||||
session, err := a.sessionStore.Get(c.Request(), a.environment.SessionName)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to get session: %v", err)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
}
|
||||
session.Options.MaxAge = -1
|
||||
err = session.Save(c.Request(), c.Response())
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to save session: %v", err)
|
||||
return err
|
||||
}
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
})
|
||||
logoutRoute.Add(e)
|
||||
return []ersteller_lib.Route{googleLoginRoute, authenticatedRoute, logoutRoute}
|
||||
}
|
||||
func (a *Auth) saveEmailToSessionStore(c echo.Context, email string, userId int) error {
|
||||
session, err := a.sessionStore.New(c.Request(), a.environment.SessionName)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to create session: %v", err)
|
||||
return err
|
||||
}
|
||||
session.Values = map[interface{}]interface{}{
|
||||
a.environment.EmailSessionKey: email,
|
||||
a.environment.UserIdSessionKey: userId,
|
||||
}
|
||||
err = session.Save(c.Request(), c.Response())
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to save session: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Auth) generateStateOauthCookie(w http.ResponseWriter) string {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("Failed to read random state: %v", err)
|
||||
}
|
||||
state := base64.URLEncoding.EncodeToString(b)
|
||||
|
||||
var expiration = time.Now().Add(time.Hour)
|
||||
cookie := http.Cookie{Name: oAuthStateCookieName, Value: state, Expires: expiration, HttpOnly: true, Path: "/", Secure: false}
|
||||
if a.isLocal {
|
||||
cookie.SameSite = http.SameSiteLaxMode
|
||||
cookie.Secure = false
|
||||
}
|
||||
http.SetCookie(w, &cookie)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
type GoogleUserData struct {
|
||||
Id string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
VerifiedEmail bool `json:"verified_email"`
|
||||
Picture string `json:"picture"`
|
||||
Hd string `json:"hd"`
|
||||
Token *oauth2.Token
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package google
|
||||
|
||||
// AUTO GENERATED
|
||||
// DO NOT EDIT
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/oauth2"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Credentials struct {
|
||||
Token oauth2.Token `json:"token"`
|
||||
}
|
||||
|
||||
type GoogleAuth struct {
|
||||
Id int `db:"id"`
|
||||
Credentials Credentials `db:"credentials"`
|
||||
UserId int `db:"user_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
func (s GoogleAuth) String() string {
|
||||
return fmt.Sprint("GoogleAuth{ ",
|
||||
"Id: ", s.Id, ", ",
|
||||
"Credentials: ", s.Credentials, ", ",
|
||||
"UserId: ", s.UserId, ", ",
|
||||
"CreatedAt: ", s.CreatedAt, ", ",
|
||||
"UpdatedAt: ", s.UpdatedAt, ", ",
|
||||
"}")
|
||||
}
|
||||
|
||||
func (s GoogleAuth) GetId() int {
|
||||
return s.Id
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"ersteller-lib"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
)
|
||||
|
||||
func (r *GoogleAuthRepository) ReadByUserId(userId int) (GoogleAuth, error) {
|
||||
ersteller_lib.Debug("Getting GoogleAuth by userId", userId)
|
||||
sql, args, _ := r.dialect.From("googleAuth").
|
||||
Prepared(true).
|
||||
Select(r.getSelectColumns()...).
|
||||
Where(goqu.Ex{
|
||||
"user_id": userId,
|
||||
}).
|
||||
ToSQL()
|
||||
|
||||
rows, err := r.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.Error("Failed to get GoogleAuth: ", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
item, _, err := r.rowToItem(rows, false)
|
||||
return item, err
|
||||
}
|
||||
return GoogleAuth{}, errors.New("no rows found")
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package google
|
||||
|
||||
// GENERATED FILE
|
||||
// DO NOT EDIT
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"ersteller-lib"
|
||||
"fmt"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
|
||||
"github.com/doug-martin/goqu/v9/exp"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GoogleAuthRepository struct {
|
||||
connPool *pgxpool.Pool
|
||||
dialect goqu.DialectWrapper
|
||||
}
|
||||
|
||||
func NewGoogleAuthRepository(connPool *pgxpool.Pool) *GoogleAuthRepository {
|
||||
return &GoogleAuthRepository{
|
||||
connPool: connPool,
|
||||
dialect: goqu.Dialect("postgres"),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) Create(googleAuth GoogleAuth) (int, error) {
|
||||
sql, args, err := r.dialect.Insert("googleAuth").
|
||||
Prepared(true).
|
||||
Rows(goqu.Record{
|
||||
|
||||
"updated_at": time.Now(),
|
||||
"credentials": r.jsonToString(googleAuth.Credentials),
|
||||
"user_id": googleAuth.UserId,
|
||||
}).
|
||||
Returning("id").
|
||||
ToSQL()
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating create GoogleAuth sql: %v", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
rows, err := r.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating GoogleAuth: %v", err)
|
||||
return -1, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var id int
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error scanning User: %v", err)
|
||||
return -1, err
|
||||
}
|
||||
} else {
|
||||
ersteller_lib.Error("GoogleAuth already exists")
|
||||
return -1, GoogleAuthAlreadyExistsError{GoogleAuth: googleAuth}
|
||||
}
|
||||
|
||||
return id, nil
|
||||
|
||||
}
|
||||
|
||||
type GoogleAuthAlreadyExistsError struct {
|
||||
GoogleAuth GoogleAuth
|
||||
}
|
||||
|
||||
func (e GoogleAuthAlreadyExistsError) Error() string {
|
||||
return fmt.Sprint("GoogleAuth ", e.GoogleAuth, " already exists")
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) getSelectColumns() []any {
|
||||
return []any{"id", "created_at", "updated_at",
|
||||
"credentials", "user_id",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) Read(userId int, id int) (GoogleAuth, error) {
|
||||
ersteller_lib.Debug("Getting GoogleAuth by id ", id)
|
||||
sql, args, _ := r.dialect.From("googleAuth").
|
||||
Prepared(true).
|
||||
Select(r.getSelectColumns()...).
|
||||
Where(goqu.Ex{
|
||||
"id": id,
|
||||
"user_id": userId,
|
||||
}).
|
||||
ToSQL()
|
||||
|
||||
rows, err := r.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.Error("Failed to get GoogleAuth: ", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
item, _, err := r.rowToItem(rows, false)
|
||||
return item, err
|
||||
}
|
||||
return GoogleAuth{}, errors.New("no rows found")
|
||||
}
|
||||
|
||||
type GoogleAuthItemScan struct {
|
||||
GoogleAuth
|
||||
RowId int
|
||||
Count int
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) rowToItem(rows pgx.Rows, rowId bool) (GoogleAuth, int, error) {
|
||||
var item GoogleAuthItemScan
|
||||
if rowId {
|
||||
err := rows.Scan(
|
||||
&item.RowId,
|
||||
&item.Count,
|
||||
&item.Id,
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
&item.Credentials,
|
||||
&item.UserId,
|
||||
)
|
||||
if err != nil {
|
||||
return GoogleAuth{}, -1, err
|
||||
}
|
||||
} else {
|
||||
err := rows.Scan(
|
||||
&item.Id,
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
&item.Credentials,
|
||||
&item.UserId,
|
||||
)
|
||||
if err != nil {
|
||||
return GoogleAuth{}, -1, err
|
||||
}
|
||||
}
|
||||
return GoogleAuth{
|
||||
Id: item.Id,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
Credentials: item.Credentials,
|
||||
UserId: item.UserId,
|
||||
}, item.Count, nil
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) Update(userId int, googleAuth GoogleAuth) error {
|
||||
sql, args, err := r.dialect.Update("googleAuth").
|
||||
Prepared(true).
|
||||
Set(goqu.Record{
|
||||
|
||||
"updated_at": time.Now(),
|
||||
"credentials": r.jsonToString(googleAuth.Credentials),
|
||||
"user_id": googleAuth.UserId,
|
||||
}).
|
||||
Where(goqu.Ex{
|
||||
"id": googleAuth.Id,
|
||||
"user_id": userId,
|
||||
}).
|
||||
ToSQL()
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating update GoogleAuth sql: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.connPool.Exec(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error updating GoogleAuth: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) Delete(userId int, id int) error {
|
||||
sql, args, err := r.dialect.Delete("googleAuth").
|
||||
Prepared(true).
|
||||
Where(goqu.Ex{
|
||||
"id": id,
|
||||
"user_id": userId,
|
||||
}).
|
||||
ToSQL()
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating delete GoogleAuth sql: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.connPool.Exec(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error deleting GoogleAuth: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type GoogleAuthField string
|
||||
|
||||
const (
|
||||
GoogleAuthFieldCredentials GoogleAuthField = "credentials"
|
||||
)
|
||||
|
||||
type GoogleAuthOrderDirection string
|
||||
|
||||
const (
|
||||
GoogleAuthOrderDirectionAsc GoogleAuthOrderDirection = "asc"
|
||||
GoogleAuthOrderDirectionDesc GoogleAuthOrderDirection = "desc"
|
||||
)
|
||||
|
||||
type GoogleAuthReferences struct {
|
||||
UserId int
|
||||
}
|
||||
|
||||
type GoogleAuthPaginationParams struct {
|
||||
RowId int
|
||||
PageSize int
|
||||
OrderBy GoogleAuthField
|
||||
OrderDirection GoogleAuthOrderDirection
|
||||
|
||||
References GoogleAuthReferences
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) GetPage(params GoogleAuthPaginationParams) ([]GoogleAuth, int, error) {
|
||||
var orderByWindow exp.WindowExpression
|
||||
if params.OrderDirection == GoogleAuthOrderDirectionAsc {
|
||||
orderByWindow = goqu.W().OrderBy(goqu.C(string(params.OrderBy)).Asc())
|
||||
} else {
|
||||
orderByWindow = goqu.W().OrderBy(goqu.C(string(params.OrderBy)).Desc())
|
||||
}
|
||||
selectColumns := []any{
|
||||
goqu.ROW_NUMBER().Over(orderByWindow).As("row_id"),
|
||||
goqu.COUNT("*"),
|
||||
}
|
||||
selectColumns = append(selectColumns, r.getSelectColumns()...)
|
||||
whereExpressions := []goqu.Expression{
|
||||
goqu.Ex{
|
||||
|
||||
"user_id": params.References.UserId,
|
||||
},
|
||||
}
|
||||
whereExpressions = r.addPageFilters(params, whereExpressions)
|
||||
var colOrder exp.OrderedExpression
|
||||
if params.OrderDirection == GoogleAuthOrderDirectionAsc {
|
||||
colOrder = goqu.C(string(params.OrderBy)).Asc()
|
||||
} else {
|
||||
colOrder = goqu.C(string(params.OrderBy)).Desc()
|
||||
}
|
||||
dialect := goqu.Dialect("postgres")
|
||||
innerFrom := dialect.From("googleAuth").
|
||||
Prepared(true).
|
||||
Select(selectColumns...).
|
||||
Where(whereExpressions...).
|
||||
Order(colOrder)
|
||||
|
||||
outerFrom := dialect.From(innerFrom).
|
||||
Prepared(true).
|
||||
Where(goqu.Ex{"row_id": goqu.Op{"gt": params.RowId}})
|
||||
if params.PageSize > 0 {
|
||||
outerFrom = outerFrom.Limit(uint(params.PageSize))
|
||||
}
|
||||
sql, args, _ := outerFrom.ToSQL()
|
||||
sql = strings.Replace(sql, "COUNT(*)", "COUNT(*) over()", 1)
|
||||
|
||||
rows, err := r.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed to run sql query: %v", err)
|
||||
return nil, -1, err
|
||||
}
|
||||
defer rows.Close()
|
||||
results := make([]GoogleAuth, 0)
|
||||
totalCount := 0
|
||||
for rows.Next() {
|
||||
parsed, count, err := r.rowToItem(rows, true)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
totalCount = count
|
||||
results = append(results, parsed)
|
||||
}
|
||||
return results, totalCount, nil
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) addPageFilters(params GoogleAuthPaginationParams, whereExpressions []goqu.Expression) []goqu.Expression {
|
||||
|
||||
return whereExpressions
|
||||
}
|
||||
|
||||
func (r *GoogleAuthRepository) jsonToString(jsonData any) string {
|
||||
bytes, err := json.Marshal(jsonData)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
Reference in New Issue
Block a user