Files
linuxservice/main.go

333 lines
8.9 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"strings"
"sync"
"time"
)
// Configuration holds all website configuration
type Config struct {
CompanyName string
KVK string
Email string
Phone string
Street string
PostalCode string
Village string
Domain string
Port string
TelegramBotToken string
TelegramChatID string
}
// PageData holds data for template rendering
type PageData struct {
Config
Title string
CurrentPage string
CurrentYear int
ErrorMessage string
SuccessMessage string
FormData ContactForm
}
// ContactForm holds form data
type ContactForm struct {
Name string
Email string
Phone string
Computer string
Service string
Message string
}
// Server holds the application state
type Server struct {
config Config
templates *template.Template
}
// Rate limiting
var (
mu sync.Mutex
lastSubmissionTime = make(map[string]time.Time)
submissionCooldown = 5 * time.Minute
)
// getEnv returns environment variable or default value
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// NewServer creates a new server instance
func NewServer() *Server {
// Configuration - can be overridden with environment variables
config := Config{
CompanyName: getEnv("COMPANY_NAME", "Hogeland Linux"),
KVK: getEnv("KVK", "12345678"), // Replace with actual KVK number
Email: getEnv("EMAIL", "info@hogelandlinux.nl"),
Phone: getEnv("PHONE", "+31 6 12345678"),
Street: getEnv("STREET", "Voorstraat 123"),
PostalCode: getEnv("POSTAL_CODE", "9967 AA"),
Village: getEnv("VILLAGE", "Eenrum"),
Domain: getEnv("DOMAIN", "hogelandlinux.nl"), // Replace with actual domain
Port: ":" + getEnv("PORT", "8080"),
TelegramBotToken: getEnv("TELEGRAM_BOT_TOKEN", ""), // Set this in environment
TelegramChatID: getEnv("TELEGRAM_CHAT_ID", ""), // Set this in environment
}
// Parse templates with error handling
templates, err := template.ParseGlob("templates/*.html")
if err != nil {
log.Fatalf("Failed to parse templates: %v", err)
}
return &Server{
config: config,
templates: templates,
}
}
// createPageData creates PageData with the given title and current page
func (s *Server) createPageData(title, currentPage string) PageData {
return PageData{
Config: s.config,
Title: title,
CurrentPage: currentPage,
CurrentYear: time.Now().Year(),
}
}
// renderTemplate renders a template with error handling
func (s *Server) renderTemplate(w http.ResponseWriter, templateName string, data PageData) {
if err := s.templates.ExecuteTemplate(w, templateName, data); err != nil {
log.Printf("Template execution error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
// homeHandler handles the home page
func (s *Server) homeHandler(w http.ResponseWriter, r *http.Request) {
data := s.createPageData("Linux Migratie Service - Uw Computer Nieuw Leven Geven", "home")
s.renderTemplate(w, "index.html", data)
}
// contactHandler handles the contact page
func (s *Server) contactHandler(w http.ResponseWriter, r *http.Request) {
data := s.createPageData("Contact - "+s.config.CompanyName, "contact")
if r.Method == "POST" {
s.handleContactForm(w, r, &data)
return
}
s.renderTemplate(w, "contact.html", data)
}
// handleContactForm processes the contact form submission
func (s *Server) handleContactForm(w http.ResponseWriter, r *http.Request, data *PageData) {
// Get client IP for rate limiting
ip := getClientIP(r)
// Check rate limiting
mu.Lock()
if lastTime, exists := lastSubmissionTime[ip]; exists {
if time.Since(lastTime) < submissionCooldown {
mu.Unlock()
data.ErrorMessage = "U heeft recent al een bericht verstuurd. Probeer het over een paar minuten opnieuw."
s.renderTemplate(w, "contact.html", *data)
return
}
}
mu.Unlock()
// Parse form data
err := r.ParseForm()
if err != nil {
log.Printf("Error parsing form: %v", err)
data.ErrorMessage = "Er is een fout opgetreden bij het verwerken van uw bericht."
s.renderTemplate(w, "contact.html", *data)
return
}
// Extract form data
form := ContactForm{
Name: strings.TrimSpace(r.FormValue("name")),
Email: strings.TrimSpace(r.FormValue("email")),
Phone: strings.TrimSpace(r.FormValue("phone")),
Computer: strings.TrimSpace(r.FormValue("computer")),
Service: strings.TrimSpace(r.FormValue("service")),
Message: strings.TrimSpace(r.FormValue("message")),
}
// Store form data for re-rendering on error
data.FormData = form
// Validate required fields
if form.Name == "" || form.Email == "" || form.Message == "" {
data.ErrorMessage = "Vul alle verplichte velden in (naam, email, bericht)."
s.renderTemplate(w, "contact.html", *data)
return
}
// Send to Telegram if configured
if s.config.TelegramBotToken != "" && s.config.TelegramChatID != "" {
err := s.sendToTelegram(form)
if err != nil {
log.Printf("Error sending message to Telegram: %v", err)
data.ErrorMessage = "Er is een fout opgetreden bij het versturen van uw bericht. Probeer het later opnieuw."
s.renderTemplate(w, "contact.html", *data)
return
}
}
// Update last submission time on success
mu.Lock()
lastSubmissionTime[ip] = time.Now()
mu.Unlock()
// On success, render success message
data.SuccessMessage = "Bedankt voor uw bericht! Wij nemen zo snel mogelijk contact met u op."
data.FormData = ContactForm{} // Clear form data
s.renderTemplate(w, "contact.html", *data)
}
// sendToTelegram sends the contact form data to Telegram
func (s *Server) sendToTelegram(form ContactForm) error {
// Format message
message := fmt.Sprintf(
"🔔 Nieuw contactformulier\n\n"+
"👤 *Naam:* %s\n"+
"📧 *Email:* %s\n"+
"📞 *Telefoon:* %s\n"+
"💻 *Computer:* %s\n"+
"🛠️ *Service:* %s\n\n"+
"💬 *Bericht:*\n%s",
escapeMarkdown(form.Name),
escapeMarkdown(form.Email),
escapeMarkdown(form.Phone),
escapeMarkdown(form.Computer),
escapeMarkdown(form.Service),
escapeMarkdown(form.Message),
)
// Prepare Telegram API request
telegramURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", s.config.TelegramBotToken)
payload := map[string]interface{}{
"chat_id": s.config.TelegramChatID,
"text": message,
"parse_mode": "Markdown",
}
jsonData, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal JSON: %v", err)
}
// Send HTTP request
resp, err := http.Post(telegramURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to send HTTP request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("Telegram API error: %s, Status Code: %d", string(bodyBytes), resp.StatusCode)
}
return nil
}
// escapeMarkdown escapes special characters for Telegram Markdown
func escapeMarkdown(text string) string {
if text == "" {
return "N/A"
}
// Escape special Markdown characters
replacer := strings.NewReplacer(
"*", "\\*",
"_", "\\_",
"`", "\\`",
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"~", "\\~",
">", "\\>",
"#", "\\#",
"+", "\\+",
"-", "\\-",
"=", "\\=",
"|", "\\|",
"{", "\\{",
"}", "\\}",
".", "\\.",
"!", "\\!",
)
return replacer.Replace(text)
}
// getClientIP extracts the client IP address from the request
func getClientIP(r *http.Request) string {
// Check X-Forwarded-For header first (for reverse proxies)
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// X-Forwarded-For can contain multiple IPs, take the first one
if idx := strings.Index(xff, ","); idx != -1 {
return strings.TrimSpace(xff[:idx])
}
return strings.TrimSpace(xff)
}
// Check X-Real-IP header
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return strings.TrimSpace(xri)
}
// Fall back to RemoteAddr
if idx := strings.LastIndex(r.RemoteAddr, ":"); idx != -1 {
return r.RemoteAddr[:idx]
}
return r.RemoteAddr
}
// healthHandler provides a simple health check endpoint
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"healthy","service":"linuxservice"}`))
}
// setupRoutes configures all HTTP routes
func (s *Server) setupRoutes() {
// Static files
fs := http.FileServer(http.Dir("static/"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Page routes
http.HandleFunc("/", s.homeHandler)
http.HandleFunc("/contact", s.contactHandler)
http.HandleFunc("/health", s.healthHandler)
}
func main() {
server := NewServer()
server.setupRoutes()
log.Printf("Server starting on %s", server.config.Port)
log.Fatal(http.ListenAndServe(server.config.Port, nil))
}