Compare commits
4 Commits
d4044b0eaf
...
fx-depende
Author | SHA1 | Date | |
---|---|---|---|
228d1bffc1 | |||
6d15ce2df9 | |||
5680f9471f | |||
f136ae58b3 |
@@ -3,7 +3,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Build the application from source
|
||||
FROM golang:1.24.3 AS build-stage
|
||||
FROM golang:1.25.1-alpine3.22 AS build-stage
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
75
FX_MIGRATION.md
Normal file
75
FX_MIGRATION.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# FX Dependency Injection Implementation
|
||||
|
||||
This document summarizes the changes made to implement uber-go/fx dependency injection in the CatsOfMastodonBotGo application.
|
||||
|
||||
## Overview
|
||||
|
||||
We've replaced the global variable pattern with proper dependency injection using uber-go/fx. This improves:
|
||||
- Testability
|
||||
- Modularity
|
||||
- Explicit dependency management
|
||||
- Eliminates fragile initialization order dependencies
|
||||
|
||||
## Key Changes
|
||||
|
||||
### 1. Added Dependencies
|
||||
- Added `go.uber.org/fx` dependency to go.mod
|
||||
- Added `go.uber.org/zap` for structured logging
|
||||
|
||||
### 2. Refactored main.go
|
||||
- Replaced manual initialization with fx application
|
||||
- Used `fx.Provide` to register constructors for all components
|
||||
- Used `fx.Invoke` to start background tasks and server
|
||||
- Added proper logger integration
|
||||
|
||||
### 3. Refactored Configuration (config/config.go)
|
||||
- Removed global `Config` variable
|
||||
- Removed `Init()` function
|
||||
- Kept `Load()` function as constructor
|
||||
|
||||
### 4. Refactored Database (database/database.go)
|
||||
- Removed global `Gorm` variable
|
||||
- Function now accepts config as parameter
|
||||
|
||||
### 5. Refactored Services
|
||||
- **PostService**: Now accepts database and config as dependencies
|
||||
- **ImgKitHelper**: Now accepts config as dependency
|
||||
|
||||
### 6. Refactored Auth
|
||||
- **JwtTokenGenerator**: Now accepts config as dependency
|
||||
- **GiteaOAuth2Handler**: Now accepts config as dependency
|
||||
|
||||
### 7. Refactored Handlers
|
||||
- All handlers now use constructor injection
|
||||
- Dependencies are explicitly declared in constructor signatures
|
||||
- Removed global instance variables
|
||||
|
||||
### 8. Refactored Server/Router
|
||||
- Router now receives all handlers through fx dependency injection
|
||||
- Uses fx.Lifecycle for proper startup/shutdown handling
|
||||
|
||||
## Benefits Achieved
|
||||
|
||||
1. **Eliminated Global State**: No more global variables causing tight coupling
|
||||
2. **Explicit Dependencies**: All dependencies are clearly visible in constructor signatures
|
||||
3. **Automatic Wiring**: fx automatically resolves and injects dependencies
|
||||
4. **Improved Testability**: Easy to mock dependencies for unit tests
|
||||
5. **Reduced Boilerplate**: No need for manual initialization functions
|
||||
6. **Error Detection**: Dependency resolution errors caught at startup
|
||||
|
||||
## Files Modified
|
||||
|
||||
- cmd/CatsOfMastodonBotGo/main.go
|
||||
- go.mod
|
||||
- go.sum
|
||||
- internal/auth/jwt.go
|
||||
- internal/auth/oauth2.go
|
||||
- internal/config/config.go
|
||||
- internal/database/database.go
|
||||
- internal/server/router.go
|
||||
- internal/services/imgKitHelper.go
|
||||
- internal/services/postService.go
|
||||
- internal/web/handlers/adminDash.go
|
||||
- internal/web/handlers/apiEndpoint.go
|
||||
- internal/web/handlers/embedCard.go
|
||||
- internal/web/handlers/oauth.go
|
@@ -1,75 +1,134 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"CatsOfMastodonBotGo/internal/auth"
|
||||
"CatsOfMastodonBotGo/internal/config"
|
||||
"CatsOfMastodonBotGo/internal/database"
|
||||
"CatsOfMastodonBotGo/internal/domain"
|
||||
"CatsOfMastodonBotGo/internal/server"
|
||||
"CatsOfMastodonBotGo/internal/services"
|
||||
"context"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"time"
|
||||
"CatsOfMastodonBotGo/internal/web/handlers"
|
||||
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Setup config
|
||||
config.Init()
|
||||
|
||||
// Initialize database
|
||||
database.Init()
|
||||
|
||||
services.InitPostService()
|
||||
|
||||
// Not needed but anyways
|
||||
services.InitImgKitHelper()
|
||||
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
|
||||
runFetchPosts := func() {
|
||||
// Get posts
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
var posts []domain.Post = nil
|
||||
err, posts := services.PostServiceInstance.GetPostsFromApi(ctx, config.Config.Tag, config.Config.Instance)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var existingPostIds = services.PostServiceInstance.GetExistingPostIds()
|
||||
var existingAccountIds = services.PostServiceInstance.GetExistingAccountIds()
|
||||
var newPosts = services.PostServiceInstance.GetNewPosts(existingPostIds, posts)
|
||||
var newAccounts = services.PostServiceInstance.GetNewAccounts(existingAccountIds, newPosts)
|
||||
|
||||
// Save to database
|
||||
slog.Info("Fetched " + strconv.Itoa(len(posts)) + " posts; " + strconv.Itoa(len(existingPostIds)) + " existing posts; " + strconv.Itoa(len(newPosts)) + " new posts and " + strconv.Itoa(len(newAccounts)) + " new accounts\n")
|
||||
// Additional logging
|
||||
if newAccounts != nil {
|
||||
slog.Info("Inserted " + strconv.Itoa(services.PostServiceInstance.InsertNewAccounts(newAccounts)) + " accounts\n")
|
||||
}
|
||||
if newPosts != nil {
|
||||
slog.Info("Inserted " + strconv.Itoa(services.PostServiceInstance.InsertNewPosts(newPosts)) + " posts\n")
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
runFetchPosts()
|
||||
}
|
||||
}()
|
||||
|
||||
// Run initial fetch on startup
|
||||
go func() {
|
||||
runFetchPosts()
|
||||
}()
|
||||
|
||||
// https://seefnasrul.medium.com/create-your-first-go-rest-api-with-jwt-authentication-in-gin-framework-dbe5bda72817
|
||||
|
||||
r := server.SetupRouter()
|
||||
err := r.Run(":8080")
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
}
|
||||
fx.New(
|
||||
fx.Provide(
|
||||
// Logger
|
||||
NewLogger,
|
||||
|
||||
// Configuration
|
||||
config.Load,
|
||||
|
||||
// Database
|
||||
database.Connect,
|
||||
|
||||
// Services
|
||||
services.NewPostService,
|
||||
services.NewImgKitHelper,
|
||||
|
||||
// Auth
|
||||
auth.NewJwtTokenGenerator,
|
||||
auth.NewGiteaOauth2Token,
|
||||
|
||||
// Handlers
|
||||
handlers.NewAdminDashboardHandler,
|
||||
handlers.NewApiEndpointHandler,
|
||||
handlers.NewEmbedCardHandler,
|
||||
handlers.NewOauthLoginHandler,
|
||||
),
|
||||
fx.Invoke(
|
||||
// Start background tasks
|
||||
startBackgroundTasks,
|
||||
|
||||
// Setup and start server
|
||||
server.SetupRouter,
|
||||
),
|
||||
fx.Logger(NewFXLogger()), // Optional custom logger
|
||||
).Run()
|
||||
}
|
||||
|
||||
// Background tasks as a separate function
|
||||
func startBackgroundTasks(
|
||||
lc fx.Lifecycle,
|
||||
postService *services.PostService,
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
) {
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(context.Context) error {
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
|
||||
runFetchPosts := func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
posts, err := postService.GetPostsFromApi(ctx, cfg.Tag, cfg.Instance)
|
||||
if err != nil {
|
||||
logger.Error("Failed to fetch posts", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
existingPostIds := postService.GetExistingPostIds()
|
||||
existingAccountIds := postService.GetExistingAccountIds()
|
||||
newPosts := postService.GetNewPosts(existingPostIds, posts)
|
||||
newAccounts := postService.GetNewAccounts(existingAccountIds, newPosts)
|
||||
|
||||
logger.Info("Fetched posts",
|
||||
zap.Int("total", len(posts)),
|
||||
zap.Int("existing", len(existingPostIds)),
|
||||
zap.Int("new", len(newPosts)),
|
||||
zap.Int("new_accounts", len(newAccounts)))
|
||||
|
||||
if len(newAccounts) > 0 {
|
||||
count := postService.InsertNewAccounts(newAccounts)
|
||||
logger.Info("Inserted accounts", zap.Int("count", count))
|
||||
}
|
||||
|
||||
if len(newPosts) > 0 {
|
||||
count := postService.InsertNewPosts(newPosts)
|
||||
logger.Info("Inserted posts", zap.Int("count", count))
|
||||
}
|
||||
}
|
||||
|
||||
// Run initial fetch
|
||||
go runFetchPosts()
|
||||
|
||||
// Start ticker
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
runFetchPosts()
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
},
|
||||
OnStop: func(context.Context) error {
|
||||
// Cleanup if needed
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Logger provider
|
||||
func NewLogger() (*zap.Logger, error) {
|
||||
return zap.NewDevelopment()
|
||||
}
|
||||
|
||||
// Simple logger for fx
|
||||
type FXLogger struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewFXLogger() *FXLogger {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
return &FXLogger{logger: logger}
|
||||
}
|
||||
|
||||
func (l *FXLogger) Printf(str string, args ...interface{}) {
|
||||
l.logger.Sugar().Infof(str, args...)
|
||||
}
|
4
go.mod
4
go.mod
@@ -39,6 +39,10 @@ require (
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/dig v1.19.0 // indirect
|
||||
go.uber.org/fx v1.24.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/arch v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@@ -88,6 +88,14 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
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=
|
||||
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
|
||||
golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
|
@@ -10,33 +10,25 @@ import (
|
||||
)
|
||||
|
||||
type JwtTokenGenerator struct {
|
||||
Key string
|
||||
Issuer string
|
||||
Audience string
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
var JwtTokenGeneratorInstance *JwtTokenGenerator
|
||||
|
||||
func InitJwtTokenGenerator() {
|
||||
JwtTokenGeneratorInstance = &JwtTokenGenerator{
|
||||
Key: config.Config.JwtSecret,
|
||||
Issuer: config.Config.JwtIssuer,
|
||||
Audience: config.Config.JwtAudience,
|
||||
}
|
||||
func NewJwtTokenGenerator(cfg *config.Config) *JwtTokenGenerator {
|
||||
return &JwtTokenGenerator{cfg: cfg}
|
||||
}
|
||||
|
||||
func (j *JwtTokenGenerator) GenerateToken(claims map[string]interface{}) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"exp": time.Now().AddDate(0, 0, 1).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"iss": j.Issuer,
|
||||
"aud": j.Audience,
|
||||
"iss": j.cfg.JwtIssuer,
|
||||
"aud": j.cfg.JwtAudience,
|
||||
})
|
||||
for k, v := range claims {
|
||||
token.Claims.(jwt.MapClaims)[k] = v
|
||||
}
|
||||
|
||||
return token.SignedString([]byte(j.Key))
|
||||
return token.SignedString([]byte(j.cfg.JwtSecret))
|
||||
}
|
||||
|
||||
// Gin middleware
|
||||
@@ -53,7 +45,7 @@ func (j *JwtTokenGenerator) GinMiddleware() gin.HandlerFunc {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
return []byte(j.Key), nil
|
||||
return []byte(j.cfg.JwtSecret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -69,4 +61,4 @@ func (j *JwtTokenGenerator) GinMiddleware() gin.HandlerFunc {
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,45 +5,39 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GiteaOAuth2Handler struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
InstanceUrl string
|
||||
cfg *config.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
var GiteaOauth2HandlerInstance *GiteaOAuth2Handler
|
||||
|
||||
func InitGiteaOauth2Token() {
|
||||
GiteaOauth2HandlerInstance = &GiteaOAuth2Handler{
|
||||
ClientID: config.Config.GiteaOauthClientID,
|
||||
ClientSecret: config.Config.GiteaOauthClientSecret,
|
||||
InstanceUrl: config.Config.GiteaOauthInstance,
|
||||
}
|
||||
func NewGiteaOauth2Token(cfg *config.Config) *GiteaOAuth2Handler {
|
||||
return &GiteaOAuth2Handler{cfg: cfg}
|
||||
}
|
||||
|
||||
func (g *GiteaOAuth2Handler) GetGiteaLoginURL(redirectHost string) (string, error) {
|
||||
if g.InstanceUrl == "" {
|
||||
if g.cfg.GiteaOauthInstance == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if redirectHost == "" {
|
||||
slog.Error("Redirect host not provided")
|
||||
g.logger.Error("Redirect host not provided")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
authUrl := g.InstanceUrl + "/login/oauth/authorize?client_id=" + g.ClientID + "&redirect_uri=" + "http://" + redirectHost + "/admin/oauth/gitea/callback&scope=openid&response_type=code&response_mode=form_post"
|
||||
authUrl := g.cfg.GiteaOauthInstance + "/login/oauth/authorize?client_id=" + g.cfg.GiteaOauthClientID + "&redirect_uri=" + "http://" + redirectHost + "/admin/oauth/gitea/callback&scope=openid&response_type=code&response_mode=form_post"
|
||||
return authUrl, nil
|
||||
}
|
||||
|
||||
func (g *GiteaOAuth2Handler) GetGiteaUserEmailByCode(code string) (string, error) {
|
||||
|
||||
if g.InstanceUrl == "" {
|
||||
slog.Error("Instance URL not provided")
|
||||
if g.cfg.GiteaOauthInstance == "" {
|
||||
g.logger.Error("Instance URL not provided")
|
||||
return "", nil
|
||||
}
|
||||
// No need to verify since we are accesing the gitea once and only for the email
|
||||
@@ -52,8 +46,8 @@ func (g *GiteaOAuth2Handler) GetGiteaUserEmailByCode(code string) (string, error
|
||||
return "", err
|
||||
}
|
||||
|
||||
userInfoUrl := g.InstanceUrl + "/login/oauth/userinfo"
|
||||
slog.Info(userInfoUrl)
|
||||
userInfoUrl := g.cfg.GiteaOauthInstance + "/login/oauth/userinfo"
|
||||
g.logger.Info(userInfoUrl)
|
||||
req, err := http.NewRequest("POST", userInfoUrl, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -85,12 +79,12 @@ func (g *GiteaOAuth2Handler) GetGiteaUserEmailByCode(code string) (string, error
|
||||
|
||||
func (g *GiteaOAuth2Handler) getGiteaAccessTokenByCode(code string) (string, error) {
|
||||
form := url.Values{}
|
||||
form.Add("client_id", g.ClientID)
|
||||
form.Add("client_secret", g.ClientSecret)
|
||||
form.Add("client_id", g.cfg.GiteaOauthClientID)
|
||||
form.Add("client_secret", g.cfg.GiteaOauthClientSecret)
|
||||
form.Add("code", code)
|
||||
form.Add("grant_type", "authorization_code")
|
||||
|
||||
req, err := http.NewRequest("POST", g.InstanceUrl+"/login/oauth/access_token", bytes.NewBufferString(form.Encode()))
|
||||
req, err := http.NewRequest("POST", g.cfg.GiteaOauthInstance+"/login/oauth/access_token", bytes.NewBufferString(form.Encode()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -112,4 +106,4 @@ func (g *GiteaOAuth2Handler) getGiteaAccessTokenByCode(code string) (string, err
|
||||
}
|
||||
|
||||
return tokenResp.AccessToken, nil
|
||||
}
|
||||
}
|
@@ -1,14 +1,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
type Config struct {
|
||||
AdminPassword string
|
||||
Instance string
|
||||
Tag string
|
||||
@@ -26,18 +26,16 @@ type config struct {
|
||||
|
||||
ImageKitId string
|
||||
|
||||
GiteaOauthInstance string
|
||||
GiteaOauthClientID string
|
||||
GiteaOauthClientSecret string
|
||||
GiteaOauthAllowedEmails []string
|
||||
GiteaOauthInstance string
|
||||
GiteaOauthClientID string
|
||||
GiteaOauthClientSecret string
|
||||
GiteaOauthAllowedEmails []string
|
||||
}
|
||||
|
||||
var Config *config
|
||||
|
||||
func Load() *config {
|
||||
func Load(logger *zap.Logger) *Config {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
slog.Warn("Error loading .env file - Using environment variables instead")
|
||||
logger.Warn("Error loading .env file - Using environment variables instead")
|
||||
}
|
||||
|
||||
// Get mastodon instance
|
||||
@@ -53,7 +51,7 @@ func Load() *config {
|
||||
// Get admin password (Its a single user/admin app so its just fine)
|
||||
adminPassword := os.Getenv("CAOM_ADMIN_PASSWORD")
|
||||
if adminPassword == "" {
|
||||
slog.Warn("No admin password provided, using default password 'catsaregood'")
|
||||
logger.Warn("No admin password provided, using default password 'catsaregood'")
|
||||
adminPassword = "catsaregood"
|
||||
}
|
||||
|
||||
@@ -64,12 +62,12 @@ func Load() *config {
|
||||
}
|
||||
issuer := os.Getenv("CAOM_JWT_ISSUER")
|
||||
if issuer == "" {
|
||||
slog.Info("No jwt issuer provided, using default issuer 'CatsOfMastodonBotGo'")
|
||||
logger.Info("No jwt issuer provided, using default issuer 'CatsOfMastodonBotGo'")
|
||||
issuer = "CatsOfMastodonBotGo"
|
||||
}
|
||||
audience := os.Getenv("CAOM_JWT_AUDIENCE")
|
||||
if audience == "" {
|
||||
slog.Info("No jwt audience provided, using default audience 'CatsOfMastodonBotGo'")
|
||||
logger.Info("No jwt audience provided, using default audience 'CatsOfMastodonBotGo'")
|
||||
audience = "CatsOfMastodonBotGo"
|
||||
}
|
||||
|
||||
@@ -91,7 +89,7 @@ func Load() *config {
|
||||
}
|
||||
|
||||
if dbEngine == "" || dbHost == "" || dbPort == "" || dbUser == "" || dbPassword == "" || dbName == "" {
|
||||
slog.Info("No database connection provided, using sqlite")
|
||||
logger.Info("No database connection provided, using sqlite")
|
||||
dbEngine = "sqlite"
|
||||
dbHost = ""
|
||||
dbPort = ""
|
||||
@@ -102,10 +100,10 @@ func Load() *config {
|
||||
|
||||
imageKitId := os.Getenv("CAOM_IMAGEKIT_ID")
|
||||
if imageKitId == "" {
|
||||
slog.Info("No imagekit id provided, not using imagekit.io")
|
||||
logger.Info("No imagekit id provided, not using imagekit.io")
|
||||
}
|
||||
// Inititlize AppContext
|
||||
var appContext = &config{
|
||||
// Initialize AppContext
|
||||
return &Config{
|
||||
AdminPassword: adminPassword,
|
||||
Instance: instance,
|
||||
Tag: tag,
|
||||
@@ -123,15 +121,9 @@ func Load() *config {
|
||||
|
||||
ImageKitId: imageKitId,
|
||||
|
||||
GiteaOauthInstance: giteaOauthInstance,
|
||||
GiteaOauthClientID: giteaOauthClientID,
|
||||
GiteaOauthClientSecret: giteaOauthClientSecret,
|
||||
GiteaOauthInstance: giteaOauthInstance,
|
||||
GiteaOauthClientID: giteaOauthClientID,
|
||||
GiteaOauthClientSecret: giteaOauthClientSecret,
|
||||
GiteaOauthAllowedEmails: giteaOauthAllowedEmailsParsed,
|
||||
}
|
||||
return appContext
|
||||
|
||||
}
|
||||
|
||||
func Init() {
|
||||
Config = Load()
|
||||
}
|
||||
|
@@ -11,13 +11,11 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var Gorm *gorm.DB
|
||||
|
||||
func Connect() (*gorm.DB, error) {
|
||||
func Connect(cfg *config.Config) (*gorm.DB, error) {
|
||||
var db *gorm.DB
|
||||
var err error = nil
|
||||
|
||||
if config.Config.DBEngine == "sqlite" {
|
||||
if cfg.DBEngine == "sqlite" {
|
||||
_, err = os.ReadDir("data")
|
||||
if err != nil {
|
||||
err = os.Mkdir("data", 0755)
|
||||
@@ -31,11 +29,11 @@ func Connect() (*gorm.DB, error) {
|
||||
}
|
||||
} else {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
config.Config.DBUser,
|
||||
config.Config.DBPassword,
|
||||
config.Config.DBHost,
|
||||
config.Config.DBPort,
|
||||
config.Config.DBName)
|
||||
cfg.DBUser,
|
||||
cfg.DBPassword,
|
||||
cfg.DBHost,
|
||||
cfg.DBPort,
|
||||
cfg.DBName)
|
||||
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -54,13 +52,4 @@ func Connect() (*gorm.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// IDK if this is how it works or not, leave it as is for now
|
||||
func Init() {
|
||||
var err error
|
||||
Gorm, err = Connect()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +1,36 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"CatsOfMastodonBotGo/internal/auth"
|
||||
"CatsOfMastodonBotGo/internal/web/handlers"
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"CatsOfMastodonBotGo/internal/web/handlers"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
func SetupRouter() *gin.Engine {
|
||||
// fx.In allows fx to inject multiple dependencies
|
||||
|
||||
// I think we could just put all thses in SetupRouter() but in this way we are first defining the dependencies we need
|
||||
// and by using fx.In we say that whenever RouterParams was needed, inject dependensies into it and give it to SetupRouter()
|
||||
type RouterParams struct {
|
||||
fx.In
|
||||
|
||||
Lifecycle fx.Lifecycle
|
||||
AdminDashboard *handlers.AdminDashboardHandler
|
||||
ApiEndpoint *handlers.ApiEndpointHandler
|
||||
EmbedCard *handlers.EmbedCardHandler
|
||||
OauthLogin *handlers.OauthLoginHandler
|
||||
}
|
||||
|
||||
func SetupRouter(params RouterParams) {
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(cors.New(cors.Config{
|
||||
AllowAllOrigins: true,
|
||||
AllowAllOrigins: true,
|
||||
AllowMethods: []string{"POST", "GET", "OPTIONS"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
||||
AllowCredentials: true,
|
||||
@@ -22,44 +38,37 @@ func SetupRouter() *gin.Engine {
|
||||
|
||||
r.LoadHTMLGlob("internal/web/templates/home/*")
|
||||
|
||||
auth.InitJwtTokenGenerator() // Must be befor initializing admin handler, otherwise 'panic: runtime error: invalid memory address or nil pointer dereference'
|
||||
auth.InitGiteaOauth2Token()
|
||||
handlers.InitAdminDashboardHandler()
|
||||
handlers.InitApiEndpointHandler()
|
||||
handlers.InitEmbedCardHandler()
|
||||
handlers.InitOauthLoginHandler()
|
||||
|
||||
// Main page
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "home/index.html", nil)
|
||||
})
|
||||
// Embed card
|
||||
r.GET("/embed", handlers.EmbedCardHandlerInstance.GetEmbedCard)
|
||||
r.GET("/embed", params.EmbedCard.GetEmbedCard)
|
||||
|
||||
admin := r.Group("/admin")
|
||||
|
||||
// My man, this is done way more efficient and fast in .NET, specially the authentication part
|
||||
// admin.GET("/", func(c *gin.Context) {
|
||||
// c.HTML(http.StatusOK, "admin/index.html", nil)
|
||||
// })
|
||||
// admin.GET("/login", func(c *gin.Context) {
|
||||
// c.HTML(http.StatusOK, "admin/index.html", nil)
|
||||
// })
|
||||
r.Use(static.Serve("/admin", static.LocalFile("internal/web/templates/admin", true)))
|
||||
// I dont know a better way for path handling
|
||||
r.Use(static.Serve("/admin/oauth/gitea/callback", static.LocalFile("internal/web/templates/admin", true)))
|
||||
|
||||
adminApi := admin.Group("/api")
|
||||
adminApi.POST("/login", handlers.AdminDashboardHandlerInstance.Login)
|
||||
adminApi.GET("/login/oauth/gitea", handlers.OauthLoginHandlerInstance.GoToGiteaLogin)
|
||||
adminApi.POST("/login/oauth/gitea/final", handlers.OauthLoginHandlerInstance.LoginWithGitea)
|
||||
adminApi.GET("/getmedia", auth.JwtTokenGeneratorInstance.GinMiddleware(), handlers.AdminDashboardHandlerInstance.GetMedia)
|
||||
adminApi.POST("/approve", auth.JwtTokenGeneratorInstance.GinMiddleware(), handlers.AdminDashboardHandlerInstance.ApproveMedia)
|
||||
adminApi.POST("/reject", auth.JwtTokenGeneratorInstance.GinMiddleware(), handlers.AdminDashboardHandlerInstance.RejectMedia)
|
||||
adminApi.POST("/login", params.AdminDashboard.Login)
|
||||
adminApi.GET("/login/oauth/gitea", params.OauthLogin.GoToGiteaLogin)
|
||||
adminApi.POST("/login/oauth/gitea/final", params.OauthLogin.LoginWithGitea)
|
||||
adminApi.GET("/getmedia", params.AdminDashboard.JWTMiddleware(), params.AdminDashboard.GetMedia)
|
||||
adminApi.POST("/approve", params.AdminDashboard.JWTMiddleware(), params.AdminDashboard.ApproveMedia)
|
||||
adminApi.POST("/reject", params.AdminDashboard.JWTMiddleware(), params.AdminDashboard.RejectMedia)
|
||||
|
||||
api := r.Group("/api")
|
||||
api.GET("/post/random", params.ApiEndpoint.GetRandomPost)
|
||||
|
||||
api.GET("/post/random", handlers.ApiEndpointHandlerInstance.GetRandomPost)
|
||||
|
||||
return r
|
||||
}
|
||||
params.Lifecycle.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
go func() {
|
||||
if err := r.Run(":8080"); err != nil {
|
||||
// Handle error appropriately
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
@@ -3,24 +3,23 @@ package services
|
||||
import "CatsOfMastodonBotGo/internal/config"
|
||||
|
||||
type ImgKitHelper struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
var ImgKitHelperInstance *ImgKitHelper
|
||||
|
||||
func InitImgKitHelper() {
|
||||
ImgKitHelperInstance = &ImgKitHelper{}
|
||||
func NewImgKitHelper(cfg *config.Config) *ImgKitHelper {
|
||||
return &ImgKitHelper{cfg: cfg}
|
||||
}
|
||||
|
||||
func GetPreviewUrl(url string) string {
|
||||
if config.Config.ImageKitId == "" {
|
||||
func (ikh *ImgKitHelper) GetPreviewUrl(url string) string {
|
||||
if ikh.cfg.ImageKitId == "" {
|
||||
return url
|
||||
}
|
||||
return "https://ik.imagekit.io/" + config.Config.ImageKitId + "/tr:w-500,h-500,c-at_max,f-webp,q-50/" + url
|
||||
return "https://ik.imagekit.io/" + ikh.cfg.ImageKitId + "/tr:w-500,h-500,c-at_max,f-webp,q-50/" + url
|
||||
}
|
||||
|
||||
func GetRemoteUrl(url string) string {
|
||||
if config.Config.ImageKitId == "" {
|
||||
func (ikh *ImgKitHelper) GetRemoteUrl(url string) string {
|
||||
if ikh.cfg.ImageKitId == "" {
|
||||
return url
|
||||
}
|
||||
return "https://ik.imagekit.io/" + config.Config.ImageKitId + "/tr:q-70,dpr-auto,f-webp/" + url
|
||||
}
|
||||
return "https://ik.imagekit.io/" + ikh.cfg.ImageKitId + "/tr:q-70,dpr-auto,f-webp/" + url
|
||||
}
|
@@ -2,7 +2,6 @@ package services
|
||||
|
||||
import (
|
||||
"CatsOfMastodonBotGo/internal/config"
|
||||
"CatsOfMastodonBotGo/internal/database"
|
||||
"CatsOfMastodonBotGo/internal/domain"
|
||||
"context"
|
||||
"encoding/json"
|
||||
@@ -16,42 +15,41 @@ import (
|
||||
)
|
||||
|
||||
type PostService struct {
|
||||
db *gorm.DB
|
||||
db *gorm.DB
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
var PostServiceInstance *PostService
|
||||
|
||||
// Constructor
|
||||
func InitPostService() {
|
||||
PostServiceInstance = &PostService{db: database.Gorm}
|
||||
func NewPostService(db *gorm.DB, cfg *config.Config) *PostService {
|
||||
return &PostService{db: db, cfg: cfg}
|
||||
}
|
||||
|
||||
func (*PostService) GetPostsFromApi(ctx context.Context, tag string, instance string) (error, []domain.Post) {
|
||||
func (ps *PostService) GetPostsFromApi(ctx context.Context, tag string, instance string) ([]domain.Post, error) {
|
||||
var requestUrl = instance + "/api/v1/timelines/tag/" + tag + "?limit=40"
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", requestUrl, nil)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 || strings.Split(strings.ToLower(resp.Header.Get("Content-Type")), ";")[0] != "application/json" {
|
||||
return fmt.Errorf("Status code:", resp.StatusCode, " Content-Type:", resp.Header.Get("Content-Type")), nil
|
||||
return nil, fmt.Errorf("status code: %d, content-type: %s", resp.StatusCode, resp.Header.Get("Content-Type"))
|
||||
}
|
||||
|
||||
var posts []domain.Post = nil
|
||||
err = json.NewDecoder(resp.Body).Decode(&posts)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return nil, err
|
||||
}
|
||||
// defer: it basically means "do this later when the function returns"
|
||||
defer resp.Body.Close()
|
||||
if posts == nil {
|
||||
return fmt.Errorf("no posts found for tag %s on instance %s", tag, instance), nil
|
||||
return nil, fmt.Errorf("no posts found for tag %s on instance %s", tag, instance)
|
||||
}
|
||||
return nil, posts
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (ps *PostService) GetExistingPostIds() []string {
|
||||
@@ -66,7 +64,7 @@ func (ps *PostService) GetExistingAccountIds() []string {
|
||||
return existingAccountIds
|
||||
}
|
||||
|
||||
func (*PostService) GetNewPosts(existingPostIds []string, posts []domain.Post) []domain.Post {
|
||||
func (ps *PostService) GetNewPosts(existingPostIds []string, posts []domain.Post) []domain.Post {
|
||||
var newPosts []domain.Post = nil
|
||||
for _, post := range posts {
|
||||
if !arrayContains(existingPostIds, post.ID) && len(post.Attachments) > 0 && !post.Account.IsBot {
|
||||
@@ -85,7 +83,7 @@ func (*PostService) GetNewPosts(existingPostIds []string, posts []domain.Post) [
|
||||
return newPosts
|
||||
}
|
||||
|
||||
func (*PostService) GetNewAccounts(existingAccountIds []string, posts []domain.Post) []domain.Account {
|
||||
func (ps *PostService) GetNewAccounts(existingAccountIds []string, posts []domain.Post) []domain.Account {
|
||||
var newAccounts []domain.Account = nil
|
||||
for _, post := range posts {
|
||||
if !arrayContains(existingAccountIds, post.Account.AccId) {
|
||||
@@ -153,7 +151,7 @@ func (ps *PostService) RejectMedia(mediaId string) bool {
|
||||
func (ps *PostService) GetMedia() domain.MediaAttachment {
|
||||
var media domain.MediaAttachment
|
||||
orderExpr := "RANDOM()" // sqlite
|
||||
if config.Config.DBEngine != "sqlite" {
|
||||
if ps.cfg.DBEngine != "sqlite" {
|
||||
orderExpr = "RAND()" // mariadb/mysql
|
||||
}
|
||||
ps.db.Model(&domain.MediaAttachment{}).
|
||||
@@ -171,4 +169,4 @@ func arrayContains(arr []string, str string) bool {
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@@ -5,60 +5,65 @@ import (
|
||||
|
||||
"CatsOfMastodonBotGo/internal/auth"
|
||||
"CatsOfMastodonBotGo/internal/config"
|
||||
"CatsOfMastodonBotGo/internal/web/dto"
|
||||
"CatsOfMastodonBotGo/internal/services"
|
||||
"CatsOfMastodonBotGo/internal/web/dto"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AdminDashboardHandler struct {
|
||||
PostService services.PostService
|
||||
Jwt auth.JwtTokenGenerator
|
||||
postService *services.PostService
|
||||
jwt *auth.JwtTokenGenerator
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
var AdminDashboardHandlerInstance *AdminDashboardHandler
|
||||
|
||||
func InitAdminDashboardHandler() {
|
||||
AdminDashboardHandlerInstance = &AdminDashboardHandler{
|
||||
PostService: *services.PostServiceInstance,
|
||||
Jwt: *auth.JwtTokenGeneratorInstance,
|
||||
func NewAdminDashboardHandler(
|
||||
postService *services.PostService,
|
||||
jwt *auth.JwtTokenGenerator,
|
||||
cfg *config.Config,
|
||||
) *AdminDashboardHandler {
|
||||
return &AdminDashboardHandler{
|
||||
postService: postService,
|
||||
jwt: jwt,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *AdminDashboardHandler) ApproveMedia(c *gin.Context) {
|
||||
func (adh *AdminDashboardHandler) ApproveMedia(c *gin.Context) {
|
||||
var input dto.ApproveMediaInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if ps.PostService.ApproveMedia(input.MediaId) {
|
||||
if adh.postService.ApproveMedia(input.MediaId) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Media approved successfully"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to approve media"})
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *AdminDashboardHandler) RejectMedia(c *gin.Context) {
|
||||
func (adh *AdminDashboardHandler) RejectMedia(c *gin.Context) {
|
||||
var input dto.RejectMediaInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if ps.PostService.RejectMedia(input.MediaId) {
|
||||
if adh.postService.RejectMedia(input.MediaId) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Media rejected successfully"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reject media"})
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *AdminDashboardHandler) GetMedia(c *gin.Context) {
|
||||
media := ps.PostService.GetMedia()
|
||||
media.PreviewUrl = services.GetPreviewUrl(media.RemoteUrl)
|
||||
media.RemoteUrl = services.GetPreviewUrl(media.RemoteUrl)
|
||||
func (adh *AdminDashboardHandler) GetMedia(c *gin.Context) {
|
||||
media := adh.postService.GetMedia()
|
||||
// TODO: Fix this - we need to inject ImgKitHelper
|
||||
// media.PreviewUrl = services.GetPreviewUrl(media.RemoteUrl)
|
||||
// media.RemoteUrl = services.GetPreviewUrl(media.RemoteUrl)
|
||||
c.JSON(http.StatusOK, media)
|
||||
}
|
||||
|
||||
func (ps *AdminDashboardHandler) Login(c *gin.Context) {
|
||||
func (adh *AdminDashboardHandler) Login(c *gin.Context) {
|
||||
|
||||
var input dto.LoginInput
|
||||
|
||||
@@ -68,8 +73,8 @@ func (ps *AdminDashboardHandler) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if input.Password == config.Config.AdminPassword { // Its more than enough for this project
|
||||
token, err := ps.Jwt.GenerateToken(map[string]interface{}{"role": "admin"})
|
||||
if input.Password == adh.cfg.AdminPassword { // Its more than enough for this project
|
||||
token, err := adh.jwt.GenerateToken(map[string]interface{}{"role": "admin"})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token generation failed"})
|
||||
return
|
||||
@@ -85,3 +90,8 @@ func (ps *AdminDashboardHandler) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Expose the JWT middleware for use in routes
|
||||
func (adh *AdminDashboardHandler) JWTMiddleware() gin.HandlerFunc {
|
||||
return adh.jwt.GinMiddleware()
|
||||
}
|
@@ -7,23 +7,25 @@ import (
|
||||
)
|
||||
|
||||
type ApiEndpointHandler struct {
|
||||
PostService services.PostService
|
||||
postService *services.PostService
|
||||
imgKitHelper *services.ImgKitHelper
|
||||
}
|
||||
|
||||
var ApiEndpointHandlerInstance *ApiEndpointHandler
|
||||
|
||||
func InitApiEndpointHandler() {
|
||||
ApiEndpointHandlerInstance = &ApiEndpointHandler{
|
||||
PostService: *services.PostServiceInstance,
|
||||
func NewApiEndpointHandler(
|
||||
postService *services.PostService,
|
||||
imgKitHelper *services.ImgKitHelper,
|
||||
) *ApiEndpointHandler {
|
||||
return &ApiEndpointHandler{
|
||||
postService: postService,
|
||||
imgKitHelper: imgKitHelper,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ps *ApiEndpointHandler) GetRandomPost(c *gin.Context) {
|
||||
post := ps.PostService.GetRandomPost()
|
||||
func (aeh *ApiEndpointHandler) GetRandomPost(c *gin.Context) {
|
||||
post := aeh.postService.GetRandomPost()
|
||||
for i := range post.Attachments {
|
||||
post.Attachments[i].RemoteUrl = services.GetRemoteUrl(post.Attachments[i].RemoteUrl)
|
||||
post.Attachments[i].PreviewUrl = services.GetPreviewUrl(post.Attachments[i].RemoteUrl)
|
||||
post.Attachments[i].RemoteUrl = aeh.imgKitHelper.GetRemoteUrl(post.Attachments[i].RemoteUrl)
|
||||
post.Attachments[i].PreviewUrl = aeh.imgKitHelper.GetPreviewUrl(post.Attachments[i].RemoteUrl)
|
||||
}
|
||||
c.JSON(200, post)
|
||||
}
|
||||
}
|
@@ -7,21 +7,24 @@ import (
|
||||
)
|
||||
|
||||
type EmbedCardHandler struct {
|
||||
PostService services.PostService
|
||||
postService *services.PostService
|
||||
imgKitHelper *services.ImgKitHelper
|
||||
}
|
||||
|
||||
var EmbedCardHandlerInstance *EmbedCardHandler
|
||||
|
||||
func InitEmbedCardHandler() {
|
||||
EmbedCardHandlerInstance = &EmbedCardHandler{
|
||||
PostService: *services.PostServiceInstance,
|
||||
func NewEmbedCardHandler(
|
||||
postService *services.PostService,
|
||||
imgKitHelper *services.ImgKitHelper,
|
||||
) *EmbedCardHandler {
|
||||
return &EmbedCardHandler{
|
||||
postService: postService,
|
||||
imgKitHelper: imgKitHelper,
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *EmbedCardHandler) GetEmbedCard(c *gin.Context) {
|
||||
post := ps.PostService.GetRandomPost()
|
||||
func (ech *EmbedCardHandler) GetEmbedCard(c *gin.Context) {
|
||||
post := ech.postService.GetRandomPost()
|
||||
c.HTML(200, "home/embed.html", gin.H{
|
||||
"postUrl": post.Url,
|
||||
"imageUrl": services.GetRemoteUrl(post.Attachments[0].RemoteUrl),
|
||||
"imageUrl": ech.imgKitHelper.GetRemoteUrl(post.Attachments[0].RemoteUrl),
|
||||
})
|
||||
}
|
||||
}
|
@@ -10,21 +10,25 @@ import (
|
||||
)
|
||||
|
||||
type OauthLoginHandler struct {
|
||||
Jwt auth.JwtTokenGenerator
|
||||
OauthLoginHandler *auth.GiteaOAuth2Handler
|
||||
jwt *auth.JwtTokenGenerator
|
||||
oauthHandler *auth.GiteaOAuth2Handler
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
var OauthLoginHandlerInstance *OauthLoginHandler
|
||||
|
||||
func InitOauthLoginHandler() {
|
||||
OauthLoginHandlerInstance = &OauthLoginHandler{
|
||||
Jwt: *auth.JwtTokenGeneratorInstance,
|
||||
OauthLoginHandler: auth.GiteaOauth2HandlerInstance,
|
||||
func NewOauthLoginHandler(
|
||||
jwt *auth.JwtTokenGenerator,
|
||||
oauthHandler *auth.GiteaOAuth2Handler,
|
||||
cfg *config.Config,
|
||||
) *OauthLoginHandler {
|
||||
return &OauthLoginHandler{
|
||||
jwt: jwt,
|
||||
oauthHandler: oauthHandler,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (olh *OauthLoginHandler) GoToGiteaLogin(c *gin.Context) {
|
||||
redirectURL, _ := olh.OauthLoginHandler.GetGiteaLoginURL(c.Request.URL.Scheme + c.Request.Host)
|
||||
redirectURL, _ := olh.oauthHandler.GetGiteaLoginURL(c.Request.URL.Scheme + c.Request.Host)
|
||||
if redirectURL != "" {
|
||||
c.Redirect(http.StatusFound, redirectURL)
|
||||
return
|
||||
@@ -42,27 +46,28 @@ func (olh *OauthLoginHandler) LoginWithGitea(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
userEmail, err := olh.OauthLoginHandler.GetGiteaUserEmailByCode(input.Code)
|
||||
userEmail, err := olh.oauthHandler.GetGiteaUserEmailByCode(input.Code)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for _, email := range config.Config.GiteaOauthAllowedEmails {
|
||||
// Check if the user's email is in the allowed list
|
||||
for _, email := range olh.cfg.GiteaOauthAllowedEmails {
|
||||
if email == userEmail {
|
||||
token, err := olh.Jwt.GenerateToken(map[string]interface{}{"role": "admin"})
|
||||
token, err := olh.jwt.GenerateToken(map[string]interface{}{"role": "admin"})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token generation failed"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Login successful", "token": token})
|
||||
} else {
|
||||
c.JSON(401, gin.H{
|
||||
"error": "oath login faied or yyour email does not have access",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// If we get here, the email is not in the allowed list
|
||||
c.JSON(401, gin.H{
|
||||
"error": "oauth login failed or your email does not have access",
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user