Compare commits
7 Commits
a7ef859c43
...
fx-depende
Author | SHA1 | Date | |
---|---|---|---|
228d1bffc1 | |||
6d15ce2df9 | |||
5680f9471f | |||
f136ae58b3 | |||
d4044b0eaf | |||
a84156f0a0 | |||
001e3b66cb |
@@ -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...)
|
||||
}
|
11
go.mod
11
go.mod
@@ -29,19 +29,24 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.29 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
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
|
||||
golang.org/x/arch v0.19.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
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
65
go.sum
65
go.sum
@@ -1,12 +1,8 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
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/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
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/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
@@ -18,16 +14,12 @@ 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/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/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk=
|
||||
github.com/gin-contrib/cors v1.7.5/go.mod h1:4q3yi7xBEDDWKapjT2o1V7mScKDDr8k+jZ0fSquGoy0=
|
||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||
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/static v1.1.5 h1:bAPqT4KTZN+4uDY1b90eSrD1t8iNzod7Jj8njwmnzz4=
|
||||
github.com/gin-contrib/static v1.1.5/go.mod h1:8JSEXwZHcQ0uCrLPcsvnAJ4g+ODxeupP8Zetl9fd8wM=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
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/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@@ -36,23 +28,16 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
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/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
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-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
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/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
@@ -63,21 +48,17 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
|
||||
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
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/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
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-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.29 h1:1O6nRLJKvsi1H2Sj0Hzdfojwt8GiGKm+LOfLaBFaouQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.29/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -87,10 +68,12 @@ 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/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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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/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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -103,33 +86,27 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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/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/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU=
|
||||
golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
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=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -140,12 +117,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
109
internal/auth/oauth2.go
Normal file
109
internal/auth/oauth2.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"CatsOfMastodonBotGo/internal/config"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GiteaOAuth2Handler struct {
|
||||
cfg *config.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewGiteaOauth2Token(cfg *config.Config) *GiteaOAuth2Handler {
|
||||
return &GiteaOAuth2Handler{cfg: cfg}
|
||||
}
|
||||
|
||||
func (g *GiteaOAuth2Handler) GetGiteaLoginURL(redirectHost string) (string, error) {
|
||||
if g.cfg.GiteaOauthInstance == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if redirectHost == "" {
|
||||
g.logger.Error("Redirect host not provided")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
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.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
|
||||
accessToken, err := g.getGiteaAccessTokenByCode(code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
userInfoUrl := g.cfg.GiteaOauthInstance + "/login/oauth/userinfo"
|
||||
g.logger.Info(userInfoUrl)
|
||||
req, err := http.NewRequest("POST", userInfoUrl, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var userInfo struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &userInfo); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return userInfo.Email, nil
|
||||
}
|
||||
|
||||
func (g *GiteaOAuth2Handler) getGiteaAccessTokenByCode(code string) (string, error) {
|
||||
form := url.Values{}
|
||||
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.cfg.GiteaOauthInstance+"/login/oauth/access_token", bytes.NewBufferString(form.Encode()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var tokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenResp.AccessToken, nil
|
||||
}
|
@@ -1,13 +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
|
||||
@@ -24,14 +25,17 @@ type config struct {
|
||||
DBName string
|
||||
|
||||
ImageKitId 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
|
||||
@@ -47,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"
|
||||
}
|
||||
|
||||
@@ -58,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"
|
||||
}
|
||||
|
||||
@@ -74,8 +78,18 @@ func Load() *config {
|
||||
dbPassword := os.Getenv("CAOM_DB_PASSWORD")
|
||||
dbName := os.Getenv("CAOM_DB_NAME")
|
||||
|
||||
giteaOauthInstance := os.Getenv("CAOM_GITEA_OAUTH_INSTANCE")
|
||||
giteaOauthClientID := os.Getenv("CAOM_GITEA_OAUTH_CLIENT_ID")
|
||||
giteaOauthClientSecret := os.Getenv("CAOM_GITEA_OAUTH_CLIENT_SECRET")
|
||||
|
||||
giteaOauthAllowedEmails := os.Getenv("CAOM_GITEA_OAUTH_ALLOWED_EMAILS")
|
||||
var giteaOauthAllowedEmailsParsed []string
|
||||
if giteaOauthAllowedEmails != "" {
|
||||
giteaOauthAllowedEmailsParsed = strings.Split(giteaOauthAllowedEmails, ",")
|
||||
}
|
||||
|
||||
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 = ""
|
||||
@@ -86,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,
|
||||
@@ -106,11 +120,10 @@ func Load() *config {
|
||||
DBName: dbName,
|
||||
|
||||
ImageKitId: imageKitId,
|
||||
|
||||
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,38 +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'
|
||||
handlers.InitAdminDashboardHandler()
|
||||
handlers.InitApiEndpointHandler()
|
||||
handlers.InitEmbedCardHandler()
|
||||
|
||||
// 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)))
|
||||
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("/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
|
||||
}
|
||||
}
|
@@ -11,3 +11,7 @@ type LoginInput struct {
|
||||
type RejectMediaInput struct {
|
||||
MediaId string `json:"mediaId" binding:"required"`
|
||||
}
|
||||
|
||||
type GiteaLoginInput struct {
|
||||
Code string `json:"code" binding:"required"`
|
||||
}
|
@@ -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),
|
||||
})
|
||||
}
|
||||
}
|
73
internal/web/handlers/oauth.go
Normal file
73
internal/web/handlers/oauth.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"CatsOfMastodonBotGo/internal/auth"
|
||||
"CatsOfMastodonBotGo/internal/config"
|
||||
"CatsOfMastodonBotGo/internal/web/dto"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OauthLoginHandler struct {
|
||||
jwt *auth.JwtTokenGenerator
|
||||
oauthHandler *auth.GiteaOAuth2Handler
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
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.oauthHandler.GetGiteaLoginURL(c.Request.URL.Scheme + c.Request.Host)
|
||||
if redirectURL != "" {
|
||||
c.Redirect(http.StatusFound, redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get gitea login url"})
|
||||
|
||||
}
|
||||
|
||||
func (olh *OauthLoginHandler) LoginWithGitea(c *gin.Context) {
|
||||
|
||||
var input dto.GiteaLoginInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
userEmail, err := olh.oauthHandler.GetGiteaUserEmailByCode(input.Code)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 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"})
|
||||
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})
|
||||
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",
|
||||
})
|
||||
}
|
1
internal/web/templates/admin/assets/index-BvVLAYUm.css
Normal file
1
internal/web/templates/admin/assets/index-BvVLAYUm.css
Normal file
File diff suppressed because one or more lines are too long
118
internal/web/templates/admin/assets/index-D2PXWyfl.js
Normal file
118
internal/web/templates/admin/assets/index-D2PXWyfl.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -13,8 +13,8 @@
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<script type="module" crossorigin src="/admin/assets/index-JxecVd-K.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/admin/assets/index-DShmOgsI.css">
|
||||
<script type="module" crossorigin src="/admin/assets/index-D2PXWyfl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/admin/assets/index-BvVLAYUm.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
Reference in New Issue
Block a user