mirror of
https://github.com/btouchard/ackify-ce.git
synced 2026-02-12 00:38:30 -06:00
refactor: remove redundant comments and improve code readability
Remove trivial comments that duplicate what the code already expresses clearly. Keep only essential documentation for public exports as per Go conventions. This improves code maintainability and follows the principle that code should be self-documenting.
This commit is contained in:
@@ -16,13 +16,11 @@ import (
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create server instance (Community Edition)
|
||||
server, err := web.NewServer(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create server: %v", err)
|
||||
}
|
||||
|
||||
// Start server in a goroutine
|
||||
go func() {
|
||||
log.Printf("Community Edition server starting on %s", server.GetAddr())
|
||||
if err := server.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
@@ -30,14 +28,12 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for interrupt signal for graceful shutdown
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
log.Println("Shutting down Community Edition server...")
|
||||
|
||||
// Graceful shutdown with timeout
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ func main() {
|
||||
|
||||
command := args[0]
|
||||
|
||||
// Open database connection
|
||||
db, err := sql.Open("postgres", *dbDSN)
|
||||
if err != nil {
|
||||
log.Fatal("Cannot connect to database:", err)
|
||||
@@ -41,13 +40,11 @@ func main() {
|
||||
_ = db.Close()
|
||||
}(db)
|
||||
|
||||
// Create postgres driver
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Cannot create database driver:", err)
|
||||
}
|
||||
|
||||
// Create migrator
|
||||
m, err := migrate.NewWithDatabaseInstance(*migrationsPath, "postgres", driver)
|
||||
if err != nil {
|
||||
log.Fatal("Cannot create migrator:", err)
|
||||
|
||||
@@ -258,28 +258,22 @@ func (s *SignatureService) RebuildChain(ctx context.Context) error {
|
||||
|
||||
logger.Logger.Info("Starting chain rebuild", "totalSignatures", len(signatures))
|
||||
|
||||
// First signature (genesis) should have null prev_hash
|
||||
if signatures[0].PrevHash != nil {
|
||||
// Reset genesis signature
|
||||
signatures[0].PrevHash = nil
|
||||
if err := s.repo.Create(ctx, signatures[0]); err != nil {
|
||||
logger.Logger.Warn("Failed to update genesis signature", "id", signatures[0].ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Process subsequent signatures
|
||||
for i := 1; i < len(signatures); i++ {
|
||||
current := signatures[i]
|
||||
previous := signatures[i-1]
|
||||
|
||||
expectedHash := previous.ComputeRecordHash()
|
||||
|
||||
// Update if hash is missing or incorrect
|
||||
if current.PrevHash == nil || *current.PrevHash != expectedHash {
|
||||
current.PrevHash = &expectedHash
|
||||
|
||||
// Note: This would require an UPDATE method in the repository
|
||||
// For now, we'll log what needs to be updated
|
||||
logger.Logger.Info("Chain rebuild needed for signature",
|
||||
"id", current.ID,
|
||||
"expectedHash", expectedHash[:16]+"...",
|
||||
|
||||
@@ -121,7 +121,6 @@ func (s *OauthService) GetAuthURL(nextURL string) string {
|
||||
}
|
||||
|
||||
func (s *OauthService) HandleCallback(ctx context.Context, code, state string) (*models.User, string, error) {
|
||||
// Parse state to get next URL
|
||||
parts := strings.SplitN(state, ":", 2)
|
||||
nextURL := "/"
|
||||
if len(parts) == 2 {
|
||||
@@ -130,13 +129,11 @@ func (s *OauthService) HandleCallback(ctx context.Context, code, state string) (
|
||||
}
|
||||
}
|
||||
|
||||
// Exchange code for token
|
||||
token, err := s.oauthConfig.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
return nil, nextURL, fmt.Errorf("oauth exchange failed: %w", err)
|
||||
}
|
||||
|
||||
// Get user info
|
||||
client := s.oauthConfig.Client(ctx, token)
|
||||
resp, err := client.Get(s.userInfoURL)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
@@ -151,7 +148,6 @@ func (s *OauthService) HandleCallback(ctx context.Context, code, state string) (
|
||||
return nil, nextURL, fmt.Errorf("failed to parse user info: %w", err)
|
||||
}
|
||||
|
||||
// Check domain restriction
|
||||
if !s.IsAllowedDomain(user.Email) {
|
||||
return nil, nextURL, models.ErrDomainNotAllowed
|
||||
}
|
||||
@@ -170,7 +166,6 @@ func (s *OauthService) IsAllowedDomain(email string) bool {
|
||||
)
|
||||
}
|
||||
|
||||
// parseUserInfo extracts user information from different OAuth2 providers
|
||||
func (s *OauthService) parseUserInfo(resp *http.Response) (*models.User, error) {
|
||||
var rawUser map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&rawUser); err != nil {
|
||||
@@ -181,7 +176,6 @@ func (s *OauthService) parseUserInfo(resp *http.Response) (*models.User, error)
|
||||
|
||||
user := &models.User{}
|
||||
|
||||
// Extract user ID (sub field or id field depending on provider)
|
||||
if sub, ok := rawUser["sub"].(string); ok {
|
||||
user.Sub = sub
|
||||
} else if id, ok := rawUser["id"]; ok {
|
||||
@@ -190,15 +184,12 @@ func (s *OauthService) parseUserInfo(resp *http.Response) (*models.User, error)
|
||||
return nil, fmt.Errorf("missing user ID in response")
|
||||
}
|
||||
|
||||
// Extract email
|
||||
if email, ok := rawUser["email"].(string); ok {
|
||||
user.Email = email
|
||||
} else {
|
||||
// Some providers might have email in a different field or structure
|
||||
return nil, fmt.Errorf("missing email in user info response")
|
||||
}
|
||||
|
||||
// Extract user name with fallback strategy
|
||||
var name string
|
||||
if preferredName, ok := rawUser["preferred_username"].(string); ok && preferredName != "" {
|
||||
name = preferredName
|
||||
@@ -223,7 +214,6 @@ func (s *OauthService) parseUserInfo(resp *http.Response) (*models.User, error)
|
||||
"email", user.Email,
|
||||
"name", user.Name)
|
||||
|
||||
// Validate extracted data
|
||||
if !user.IsValid() {
|
||||
return nil, fmt.Errorf("invalid user data extracted: sub=%s, email=%s", user.Sub, user.Email)
|
||||
}
|
||||
|
||||
@@ -50,21 +50,17 @@ type ServerConfig struct {
|
||||
func Load() (*Config, error) {
|
||||
config := &Config{}
|
||||
|
||||
// App config
|
||||
baseURL := mustGetEnv("ACKIFY_BASE_URL")
|
||||
config.App.BaseURL = baseURL
|
||||
config.App.Organisation = mustGetEnv("ACKIFY_ORGANISATION")
|
||||
config.App.SecureCookies = strings.HasPrefix(strings.ToLower(baseURL), "https://")
|
||||
|
||||
// Database config
|
||||
config.Database.DSN = mustGetEnv("ACKIFY_DB_DSN")
|
||||
|
||||
// OAuth config
|
||||
config.OAuth.ClientID = mustGetEnv("ACKIFY_OAUTH_CLIENT_ID")
|
||||
config.OAuth.ClientSecret = mustGetEnv("ACKIFY_OAUTH_CLIENT_SECRET")
|
||||
config.OAuth.AllowedDomain = os.Getenv("ACKIFY_OAUTH_ALLOWED_DOMAIN")
|
||||
|
||||
// Configure OAuth endpoints based on provider or use custom URLs
|
||||
provider := strings.ToLower(getEnv("ACKIFY_OAUTH_PROVIDER", ""))
|
||||
switch provider {
|
||||
case "google":
|
||||
@@ -84,7 +80,6 @@ func Load() (*Config, error) {
|
||||
config.OAuth.UserInfoURL = fmt.Sprintf("%s/api/v4/user", gitlabURL)
|
||||
config.OAuth.Scopes = []string{"read_user", "profile"}
|
||||
default:
|
||||
// Custom OAuth provider - all URLs must be explicitly set
|
||||
config.OAuth.AuthURL = mustGetEnv("ACKIFY_OAUTH_AUTH_URL")
|
||||
config.OAuth.TokenURL = mustGetEnv("ACKIFY_OAUTH_TOKEN_URL")
|
||||
config.OAuth.UserInfoURL = mustGetEnv("ACKIFY_OAUTH_USERINFO_URL")
|
||||
@@ -98,7 +93,6 @@ func Load() (*Config, error) {
|
||||
}
|
||||
config.OAuth.CookieSecret = cookieSecret
|
||||
|
||||
// Server config
|
||||
config.Server.ListenAddr = getEnv("ACKIFY_LISTEN_ADDR", ":8080")
|
||||
|
||||
return config, nil
|
||||
@@ -126,17 +120,14 @@ func getEnv(key, defaultValue string) string {
|
||||
func parseCookieSecret() ([]byte, error) {
|
||||
raw := os.Getenv("ACKIFY_OAUTH_COOKIE_SECRET")
|
||||
if raw == "" {
|
||||
// Generate random 32 bytes for development
|
||||
secret := securecookie.GenerateRandomKey(32)
|
||||
fmt.Println("[WARN] ACKIFY_OAUTH_COOKIE_SECRET not set, generated volatile secret (sessions reset on restart)")
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// Try base64 decoding first
|
||||
if decoded, err := base64.StdEncoding.DecodeString(raw); err == nil && (len(decoded) == 32 || len(decoded) == 64) {
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// Fallback to raw bytes
|
||||
return []byte(raw), nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ func InitDB(ctx context.Context, config Config) (*sql.DB, error) {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
// Test connection with timeout
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ type TestDB struct {
|
||||
func SetupTestDB(t *testing.T) *TestDB {
|
||||
t.Helper()
|
||||
|
||||
// Skip if not in integrations test mode
|
||||
if os.Getenv("INTEGRATION_TESTS") == "" {
|
||||
t.Skip("Skipping integrations test (INTEGRATION_TESTS not set)")
|
||||
}
|
||||
@@ -40,7 +39,6 @@ func SetupTestDB(t *testing.T) *TestDB {
|
||||
t.Fatalf("Failed to connect to test database: %v", err)
|
||||
}
|
||||
|
||||
// Verify connection
|
||||
if err := db.Ping(); err != nil {
|
||||
t.Fatalf("Failed to ping test database: %v", err)
|
||||
}
|
||||
@@ -51,12 +49,10 @@ func SetupTestDB(t *testing.T) *TestDB {
|
||||
dbName: fmt.Sprintf("test_%d_%d", time.Now().UnixNano(), os.Getpid()),
|
||||
}
|
||||
|
||||
// Create test schema
|
||||
if err := testDB.createSchema(); err != nil {
|
||||
t.Fatalf("Failed to create test schema: %v", err)
|
||||
}
|
||||
|
||||
// Clean up on test completion
|
||||
t.Cleanup(func() {
|
||||
testDB.Cleanup()
|
||||
})
|
||||
|
||||
@@ -58,12 +58,10 @@ func (h *AuthHandlers) HandleOAuthCallback(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// Parse and validate next URL
|
||||
if nextURL == "" {
|
||||
nextURL = "/"
|
||||
}
|
||||
|
||||
// Basic URL validation to prevent open redirects
|
||||
if parsedURL, err := url.Parse(nextURL); err != nil ||
|
||||
(parsedURL.Host != "" && parsedURL.Host != r.Host) {
|
||||
nextURL = "/"
|
||||
|
||||
@@ -82,7 +82,6 @@ var BadgeThemes = struct {
|
||||
},
|
||||
}
|
||||
|
||||
// generateBadge creates a PNG badge
|
||||
func (h *BadgeHandler) generateBadge(isSigned bool) []byte {
|
||||
img := image.NewRGBA(image.Rect(0, 0, badgeSize, badgeSize))
|
||||
|
||||
@@ -94,7 +93,6 @@ func (h *BadgeHandler) generateBadge(isSigned bool) []byte {
|
||||
return h.encodeToPNG(img)
|
||||
}
|
||||
|
||||
// getBadgeColors returns appropriate colors based on signing status
|
||||
func (h *BadgeHandler) getBadgeColors(isSigned bool) BadgeColors {
|
||||
if isSigned {
|
||||
return BadgeThemes.Success
|
||||
@@ -102,12 +100,10 @@ func (h *BadgeHandler) getBadgeColors(isSigned bool) BadgeColors {
|
||||
return BadgeThemes.Error
|
||||
}
|
||||
|
||||
// drawBackground fills the image with background color
|
||||
func (h *BadgeHandler) drawBackground(img *image.RGBA, bgColor color.RGBA) {
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{C: bgColor}, image.Point{}, draw.Src)
|
||||
}
|
||||
|
||||
// drawBorder draws a circular border around the badge
|
||||
func (h *BadgeHandler) drawBorder(img *image.RGBA, borderColor color.RGBA) {
|
||||
cx, cy, r := badgeSize/2, badgeSize/2, badgeSize/2-3
|
||||
for y := 0; y < badgeSize; y++ {
|
||||
@@ -121,7 +117,6 @@ func (h *BadgeHandler) drawBorder(img *image.RGBA, borderColor color.RGBA) {
|
||||
}
|
||||
}
|
||||
|
||||
// drawIcon draws the appropriate icon based on signing status
|
||||
func (h *BadgeHandler) drawIcon(img *image.RGBA, isSigned bool, iconColor color.RGBA) {
|
||||
if isSigned {
|
||||
h.drawCheckmark(img, badgeSize, iconColor)
|
||||
@@ -130,7 +125,6 @@ func (h *BadgeHandler) drawIcon(img *image.RGBA, isSigned bool, iconColor color.
|
||||
}
|
||||
}
|
||||
|
||||
// encodeToPNG encodes the image to PNG format
|
||||
func (h *BadgeHandler) encodeToPNG(img *image.RGBA) []byte {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_ = png.Encode(buf, img)
|
||||
@@ -142,7 +136,6 @@ func (h *BadgeHandler) drawCheckmark(img *image.RGBA, size int, col color.RGBA)
|
||||
cx, cy := size/2, size/2
|
||||
scale := float64(size) / 64.0
|
||||
|
||||
// Checkmark path points (scaled)
|
||||
points := [][2]int{
|
||||
{int(18 * scale), int(32 * scale)},
|
||||
{int(28 * scale), int(42 * scale)},
|
||||
@@ -154,11 +147,9 @@ func (h *BadgeHandler) drawCheckmark(img *image.RGBA, size int, col color.RGBA)
|
||||
thickness = 2
|
||||
}
|
||||
|
||||
// Draw first stroke (left part of check)
|
||||
h.drawThickLine(img, cx+points[0][0]-cx, cy+points[0][1]-cy,
|
||||
cx+points[1][0]-cx, cy+points[1][1]-cy, thickness, col)
|
||||
|
||||
// Draw second stroke (right part of check)
|
||||
h.drawThickLine(img, cx+points[1][0]-cx, cy+points[1][1]-cy,
|
||||
cx+points[2][0]-cx, cy+points[2][1]-cy, thickness, col)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,6 @@ type SignatoryInfo struct {
|
||||
|
||||
// HandleOEmbed handles oEmbed requests for signature lists
|
||||
func (h *OEmbedHandler) HandleOEmbed(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse query parameters
|
||||
targetURL := r.URL.Query().Get("url")
|
||||
format := r.URL.Query().Get("format")
|
||||
maxWidth := r.URL.Query().Get("maxwidth")
|
||||
@@ -75,25 +74,21 @@ func (h *OEmbedHandler) HandleOEmbed(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Default format is JSON
|
||||
if format == "" {
|
||||
format = "json"
|
||||
}
|
||||
|
||||
// Only support JSON format for now
|
||||
if format != "json" {
|
||||
http.Error(w, "Only JSON format is supported", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract document ID from URL
|
||||
docID, err := h.extractDocIDFromURL(targetURL)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid URL format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get signatures for the document
|
||||
ctx := r.Context()
|
||||
signatures, err := h.signatureService.GetDocumentSignatures(ctx, docID)
|
||||
if err != nil {
|
||||
@@ -119,7 +114,6 @@ func (h *OEmbedHandler) HandleOEmbed(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Render embedded HTML
|
||||
embedHTML, err := h.renderEmbeddedHTML(SignatoryData{
|
||||
DocID: docID,
|
||||
Signatures: signatories,
|
||||
@@ -133,7 +127,6 @@ func (h *OEmbedHandler) HandleOEmbed(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse dimensions
|
||||
width := 480 // Default width
|
||||
height := 320 // Default height
|
||||
|
||||
@@ -176,7 +169,6 @@ func (h *OEmbedHandler) HandleEmbedView(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// Get signatures for the document
|
||||
ctx := r.Context()
|
||||
signatures, err := h.signatureService.GetDocumentSignatures(ctx, docID)
|
||||
if err != nil {
|
||||
@@ -226,12 +218,10 @@ func (h *OEmbedHandler) extractDocIDFromURL(targetURL string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Try to extract from query parameter
|
||||
if docID := parsedURL.Query().Get("doc"); docID != "" {
|
||||
return docID, nil
|
||||
}
|
||||
|
||||
// Try to extract from path (e.g., /embed/doc_123 or /status/doc_123)
|
||||
pathParts := strings.Split(strings.Trim(parsedURL.Path, "/"), "/")
|
||||
if len(pathParts) >= 2 && (pathParts[0] == "embed" || pathParts[0] == "status" || pathParts[0] == "sign") {
|
||||
return pathParts[1], nil
|
||||
|
||||
@@ -118,7 +118,6 @@ func (h *SignatureHandlers) HandleSignGET(w http.ResponseWriter, r *http.Request
|
||||
signedAt = signature.SignedAtUTC.Format("02/01/2006 à 15:04:05")
|
||||
}
|
||||
|
||||
// If no service info from URL, try to get it from stored signature
|
||||
if serviceInfo == nil && signature.Referer != nil {
|
||||
if sigServiceInfo := signature.GetServiceInfo(); sigServiceInfo != nil {
|
||||
serviceInfo = &struct {
|
||||
@@ -190,7 +189,6 @@ func (h *SignatureHandlers) HandleSignPOST(w http.ResponseWriter, r *http.Reques
|
||||
err = h.signatureService.CreateSignature(ctx, request)
|
||||
if err != nil {
|
||||
if errors.Is(err, models.ErrSignatureAlreadyExists) {
|
||||
// Redirect to view existing signature
|
||||
http.Redirect(w, r, buildSignURL(h.baseURL, docID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
@@ -198,7 +196,6 @@ func (h *SignatureHandlers) HandleSignPOST(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to view the created signature
|
||||
http.Redirect(w, r, buildSignURL(h.baseURL, docID), http.StatusFound)
|
||||
}
|
||||
|
||||
@@ -228,12 +225,10 @@ func (h *SignatureHandlers) HandleStatusJSON(w http.ResponseWriter, r *http.Requ
|
||||
"signed_at": sig.SignedAtUTC,
|
||||
}
|
||||
|
||||
// Add username if available
|
||||
if sig.UserName != nil && *sig.UserName != "" {
|
||||
sigData["user_name"] = *sig.UserName
|
||||
}
|
||||
|
||||
// Add service information if available
|
||||
if serviceInfo := sig.GetServiceInfo(); serviceInfo != nil {
|
||||
sigData["service"] = map[string]interface{}{
|
||||
"name": serviceInfo.Name,
|
||||
|
||||
@@ -7,11 +7,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validateDocID extracts and validates document ID from request
|
||||
func validateDocID(r *http.Request) (string, error) {
|
||||
var docID string
|
||||
|
||||
// Try query parameter first, then form value
|
||||
docID = strings.TrimSpace(r.URL.Query().Get("doc"))
|
||||
if docID == "" {
|
||||
docID = strings.TrimSpace(r.FormValue("doc"))
|
||||
@@ -24,17 +22,14 @@ func validateDocID(r *http.Request) (string, error) {
|
||||
return docID, nil
|
||||
}
|
||||
|
||||
// buildSignURL constructs a sign URL with proper escaping
|
||||
func buildSignURL(baseURL, docID string) string {
|
||||
return fmt.Sprintf("%s/sign?doc=%s", baseURL, url.QueryEscape(docID))
|
||||
}
|
||||
|
||||
// buildLoginURL constructs a login URL with next parameter
|
||||
func buildLoginURL(nextURL string) string {
|
||||
return "/login?next=" + url.QueryEscape(nextURL)
|
||||
}
|
||||
|
||||
// validateUserIdentifier extracts and validates user identifier from request
|
||||
func validateUserIdentifier(r *http.Request) (string, error) {
|
||||
userIdentifier := strings.TrimSpace(r.URL.Query().Get("user"))
|
||||
if userIdentifier == "" {
|
||||
|
||||
@@ -46,7 +46,6 @@ func (s *Ed25519Signer) GetPublicKey() string {
|
||||
return base64.StdEncoding.EncodeToString(s.publicKey)
|
||||
}
|
||||
|
||||
// canonicalPayload creates a canonical payload for signing
|
||||
func canonicalPayload(docID string, user *models.User, timestamp time.Time, nonce string) []byte {
|
||||
return []byte(fmt.Sprintf(
|
||||
"doc_id=%s\nuser_sub=%s\nuser_email=%s\nsigned_at=%s\nnonce=%s\n",
|
||||
@@ -58,7 +57,6 @@ func canonicalPayload(docID string, user *models.User, timestamp time.Time, nonc
|
||||
))
|
||||
}
|
||||
|
||||
// loadOrGenerateKeys loads existing keys or generates new ones
|
||||
func loadOrGenerateKeys() (ed25519.PrivateKey, ed25519.PublicKey, error) {
|
||||
b64Key := strings.TrimSpace(os.Getenv("ACKIFY_ED25519_PRIVATE_KEY"))
|
||||
|
||||
@@ -74,7 +72,6 @@ func loadOrGenerateKeys() (ed25519.PrivateKey, ed25519.PublicKey, error) {
|
||||
return privateKey, publicKey, nil
|
||||
}
|
||||
|
||||
// Generate new keys
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate keys: %w", err)
|
||||
|
||||
@@ -14,9 +14,7 @@ func DetectServiceFromReferrer(referrerParam string) *ServiceInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mapping des paramètres referrer vers les services
|
||||
switch referrerParam {
|
||||
// Google services
|
||||
case "google-docs":
|
||||
return &ServiceInfo{Name: "Google Docs", Icon: "https://cdn.simpleicons.org/googledocs", Type: "docs", Referrer: referrerParam}
|
||||
case "google-sheets":
|
||||
@@ -27,57 +25,36 @@ func DetectServiceFromReferrer(referrerParam string) *ServiceInfo {
|
||||
return &ServiceInfo{Name: "Google Drive", Icon: "https://cdn.simpleicons.org/googledrive", Type: "storage", Referrer: referrerParam}
|
||||
case "google":
|
||||
return &ServiceInfo{Name: "Google", Icon: "https://cdn.simpleicons.org/google", Type: "google", Referrer: referrerParam}
|
||||
|
||||
// Notion
|
||||
case "notion":
|
||||
return &ServiceInfo{Name: "Notion", Icon: "https://cdn.simpleicons.org/notion", Type: "notes", Referrer: referrerParam}
|
||||
|
||||
// Confluence
|
||||
case "confluence":
|
||||
return &ServiceInfo{Name: "Confluence", Icon: "https://cdn.simpleicons.org/confluence", Type: "wiki", Referrer: referrerParam}
|
||||
|
||||
// Microsoft
|
||||
case "microsoft":
|
||||
return &ServiceInfo{Name: "Microsoft Office", Icon: "https://cdn.simpleicons.org/microsoft", Type: "office", Referrer: referrerParam}
|
||||
|
||||
// GitHub
|
||||
case "github":
|
||||
return &ServiceInfo{Name: "GitHub", Icon: "https://cdn.simpleicons.org/github", Type: "code", Referrer: referrerParam}
|
||||
|
||||
// GitLab
|
||||
case "gitlab":
|
||||
return &ServiceInfo{Name: "GitLab", Icon: "https://cdn.simpleicons.org/gitlab", Type: "code", Referrer: referrerParam}
|
||||
|
||||
// Outline
|
||||
case "outline":
|
||||
return &ServiceInfo{Name: "Outline", Icon: "https://cdn.simpleicons.org/outline", Type: "wiki", Referrer: referrerParam}
|
||||
|
||||
// Communication
|
||||
case "slack":
|
||||
return &ServiceInfo{Name: "Slack", Icon: "https://cdn.simpleicons.org/slack", Type: "chat", Referrer: referrerParam}
|
||||
case "discord":
|
||||
return &ServiceInfo{Name: "Discord", Icon: "https://cdn.simpleicons.org/discord", Type: "chat", Referrer: referrerParam}
|
||||
|
||||
// Project management
|
||||
case "trello":
|
||||
return &ServiceInfo{Name: "Trello", Icon: "https://cdn.simpleicons.org/trello", Type: "boards", Referrer: referrerParam}
|
||||
case "asana":
|
||||
return &ServiceInfo{Name: "Asana", Icon: "https://cdn.simpleicons.org/asana", Type: "tasks", Referrer: referrerParam}
|
||||
case "monday":
|
||||
return &ServiceInfo{Name: "Monday.com", Icon: "https://cdn.simpleicons.org/monday", Type: "project", Referrer: referrerParam}
|
||||
|
||||
// Design
|
||||
case "figma":
|
||||
return &ServiceInfo{Name: "Figma", Icon: "https://cdn.simpleicons.org/figma", Type: "design", Referrer: referrerParam}
|
||||
case "miro":
|
||||
return &ServiceInfo{Name: "Miro", Icon: "https://cdn.simpleicons.org/miro", Type: "whiteboard", Referrer: referrerParam}
|
||||
|
||||
// Storage
|
||||
case "dropbox":
|
||||
return &ServiceInfo{Name: "Dropbox", Icon: "https://cdn.simpleicons.org/dropbox", Type: "storage", Referrer: referrerParam}
|
||||
|
||||
default:
|
||||
// Paramètre referrer personnalisé - utiliser tel quel
|
||||
return &ServiceInfo{Name: referrerParam, Icon: "https://cdn.simpleicons.org/link", Type: "custom", Referrer: referrerParam}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,11 @@ type Server struct {
|
||||
|
||||
// NewServer creates a new Ackify server instance
|
||||
func NewServer(ctx context.Context) (*Server, error) {
|
||||
// Initialize infrastructure
|
||||
cfg, db, tmpl, signer, err := initInfrastructure(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize infrastructure: %w", err)
|
||||
}
|
||||
|
||||
// Initialize services
|
||||
authService := auth.NewOAuthService(auth.Config{
|
||||
BaseURL: cfg.App.BaseURL,
|
||||
ClientID: cfg.OAuth.ClientID,
|
||||
@@ -48,11 +46,9 @@ func NewServer(ctx context.Context) (*Server, error) {
|
||||
SecureCookies: cfg.App.SecureCookies,
|
||||
})
|
||||
|
||||
// Initialize signatures
|
||||
signatureRepo := database.NewSignatureRepository(db)
|
||||
signatureService := services.NewSignatureService(signatureRepo, signer)
|
||||
|
||||
// Initialize handlers
|
||||
authHandlers := handlers.NewAuthHandlers(authService, cfg.App.BaseURL)
|
||||
authMiddleware := handlers.NewAuthMiddleware(authService, cfg.App.BaseURL)
|
||||
signatureHandlers := handlers.NewSignatureHandlers(signatureService, authService, tmpl, cfg.App.BaseURL)
|
||||
@@ -60,10 +56,8 @@ func NewServer(ctx context.Context) (*Server, error) {
|
||||
oembedHandler := handlers.NewOEmbedHandler(signatureService, tmpl, cfg.App.BaseURL, cfg.App.Organisation)
|
||||
healthHandler := handlers.NewHealthHandler()
|
||||
|
||||
// Setup HTTP router
|
||||
router := setupRouter(authHandlers, authMiddleware, signatureHandlers, badgeHandler, oembedHandler, healthHandler)
|
||||
|
||||
// Create HTTP server
|
||||
httpServer := &http.Server{
|
||||
Addr: cfg.Server.ListenAddr,
|
||||
Handler: handlers.SecureHeaders(router),
|
||||
@@ -107,15 +101,12 @@ func (s *Server) RegisterRoutes(fn func(r *chi.Mux)) {
|
||||
fn(s.router)
|
||||
}
|
||||
|
||||
// initInfrastructure initializes the basic infrastructure components
|
||||
func initInfrastructure(ctx context.Context) (*config.Config, *sql.DB, *template.Template, *crypto.Ed25519Signer, error) {
|
||||
// Load configuration
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
// Initialize database
|
||||
db, err := database.InitDB(ctx, database.Config{
|
||||
DSN: cfg.Database.DSN,
|
||||
})
|
||||
@@ -123,13 +114,11 @@ func initInfrastructure(ctx context.Context) (*config.Config, *sql.DB, *template
|
||||
return nil, nil, nil, nil, fmt.Errorf("failed to initialize database: %w", err)
|
||||
}
|
||||
|
||||
// Initialize templates
|
||||
tmpl, err := initTemplates()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("failed to initialize templates: %w", err)
|
||||
}
|
||||
|
||||
// Initialize cryptographic signer
|
||||
signer, err := crypto.NewEd25519Signer()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("failed to initialize signer: %w", err)
|
||||
@@ -138,7 +127,6 @@ func initInfrastructure(ctx context.Context) (*config.Config, *sql.DB, *template
|
||||
return cfg, db, tmpl, signer, nil
|
||||
}
|
||||
|
||||
// setupRouter configures all HTTP routes
|
||||
func setupRouter(
|
||||
authHandlers *handlers.AuthHandlers,
|
||||
authMiddleware *handlers.AuthMiddleware,
|
||||
@@ -149,7 +137,6 @@ func setupRouter(
|
||||
) *chi.Mux {
|
||||
router := chi.NewRouter()
|
||||
|
||||
// Public routes
|
||||
router.Get("/", signatureHandlers.HandleIndex)
|
||||
router.Get("/login", authHandlers.HandleLogin)
|
||||
router.Get("/logout", authHandlers.HandleLogout)
|
||||
@@ -160,28 +147,23 @@ func setupRouter(
|
||||
router.Get("/embed", oembedHandler.HandleEmbedView)
|
||||
router.Get("/health", healthHandler.HandleHealth)
|
||||
|
||||
// Protected routes (require authentication)
|
||||
router.Get("/sign", authMiddleware.RequireAuth(signatureHandlers.HandleSignGET))
|
||||
router.Post("/sign", authMiddleware.RequireAuth(signatureHandlers.HandleSignPOST))
|
||||
router.Get("/signatures", authMiddleware.RequireAuth(signatureHandlers.HandleUserSignatures))
|
||||
|
||||
// Note: Enterprise routes can be added via RegisterRoutes method
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// initTemplates initializes HTML templates from filesystem
|
||||
func initTemplates() (*template.Template, error) {
|
||||
templatesDir := getTemplatesDir()
|
||||
|
||||
// Parse the base template first
|
||||
baseTemplatePath := filepath.Join(templatesDir, "base.html.tpl")
|
||||
tmpl, err := template.New("base").ParseFiles(baseTemplatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse base template: %w", err)
|
||||
}
|
||||
|
||||
// Parse the additional templates
|
||||
additionalTemplates := []string{"index.html.tpl", "sign.html.tpl", "signatures.html.tpl", "embed.html.tpl"}
|
||||
for _, templateFile := range additionalTemplates {
|
||||
templatePath := filepath.Join(templatesDir, templateFile)
|
||||
@@ -194,14 +176,11 @@ func initTemplates() (*template.Template, error) {
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
// getTemplatesDir resolves the templates directory path
|
||||
func getTemplatesDir() string {
|
||||
// Check environment variable
|
||||
if envDir := os.Getenv("ACKIFY_TEMPLATES_DIR"); envDir != "" {
|
||||
return envDir
|
||||
}
|
||||
|
||||
// Default behavior: try to resolve from executable location
|
||||
if execPath, err := os.Executable(); err == nil {
|
||||
execDir := filepath.Dir(execPath)
|
||||
defaultDir := filepath.Join(execDir, "templates")
|
||||
@@ -210,7 +189,6 @@ func getTemplatesDir() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for development: check multiple possible paths
|
||||
possiblePaths := []string{
|
||||
"templates", // When running from project root
|
||||
"./templates", // Alternative relative path
|
||||
@@ -222,6 +200,5 @@ func getTemplatesDir() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback - let the error happen in template loading
|
||||
return "templates"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user