From 94600eaeecdc491b1d9130c8b096d1d2a075e9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Dvo=C5=99=C3=A1k?= <150935816+Dvorinka@users.noreply.github.com> Date: Thu, 22 May 2025 10:27:28 +0200 Subject: [PATCH] Add files via upload --- index.html | 256 +++++++++++---------- kontakt/Makefile | 141 ++++++++++++ kontakt/contact-scrape.go | 403 +++++++++++++++++++++++++++++++++ kontakt/contact-scrape.service | 29 +++ kontakt/contacts.xlsx | Bin 0 -> 12928 bytes kontakt/index.html | 376 ++++++++++++++++++++++++++++++ main.go | 205 ++--------------- 7 files changed, 1106 insertions(+), 304 deletions(-) create mode 100644 kontakt/Makefile create mode 100644 kontakt/contact-scrape.go create mode 100644 kontakt/contact-scrape.service create mode 100644 kontakt/contacts.xlsx create mode 100644 kontakt/index.html diff --git a/index.html b/index.html index 8f7ab0a..cecb224 100644 --- a/index.html +++ b/index.html @@ -1,123 +1,135 @@ - - - - - - Aplikační Rozcestník - - - - - -
-
- -
-

Poppe + Potthoff - Firemní Aplikace

-

Rychlý přístup ke všem důležitým systémům

-
-
-
- -
- -
-
- -
- -
-
-
- - -
- -
-
- -
-

Záznam jízdy služebního vozu

-

Systém pro evidenci služebních jízd.

- - Otevřít aplikaci - -
- - -
-
- -
-

Objednávka obědů

-

Portál pro objednávku a přehled firemních obědů

- - Otevřít aplikaci - -
- - -
-
- -
-

OSTicket

-

Systém technické podpory a hlášení problémů

- - Otevřít aplikaci - -
- - -
-
- -
-

Kanboard

-

Správa úkolů a projektů v přehledném kanban stylu

- - Otevřít aplikaci - -
-
-
- - - - - + + + + + + Aplikační Rozcestník + + + + + +
+
+ +
+

Poppe + Potthoff - Firemní Aplikace

+

Rychlý přístup ke všem důležitým systémům

+
+
+
+ +
+ +
+
+ +
+ +
+
+
+ + +
+ +
+
+ +
+

Záznam služebních jízd

+

Jednoduchý systém pro evidenci a správu jízd služebními vozidly.

+ + Otevřít aplikaci + +
+ + +
+
+ +
+

Kontaktní formulář

+

Formulář pro kontaktování vedení společnosti.

+ + Otevřít + +
+ + +
+
+ +
+

Objednávka obědů

+

Portál pro objednávku a přehled firemních obědů

+ + Otevřít aplikaci + +
+ + +
+
+ +
+

OSTicket

+

Systém technické podpory a hlášení problémů

+ + Otevřít aplikaci + +
+ + +
+
+ +
+

Kanboard

+

Správa úkolů a projektů v přehledném kanban stylu

+ + Otevřít aplikaci + +
+
+
+ + + + + \ No newline at end of file diff --git a/kontakt/Makefile b/kontakt/Makefile new file mode 100644 index 0000000..6043f99 --- /dev/null +++ b/kontakt/Makefile @@ -0,0 +1,141 @@ +# Contact Scrape Makefile + +.PHONY: build run clean install dev test help + +# Variables +BINARY_NAME=contact-scrape +BUILD_DIR=build +PORT=8080 + +help: ## Show this help message + @echo "Available commands:" + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development +dev: ## Run the application in development mode + @echo "Starting development server..." + @go run . + +run: build ## Build and run the application + @echo "Starting $(BINARY_NAME)..." + @./$(BUILD_DIR)/$(BINARY_NAME) + +test: ## Run tests + @echo "Running tests..." + @go test -v ./... + +##@ Build +build: ## Build the application + @echo "Building $(BINARY_NAME)..." + @mkdir -p $(BUILD_DIR) + @go build -o $(BUILD_DIR)/$(BINARY_NAME) . + @echo "Binary built: $(BUILD_DIR)/$(BINARY_NAME)" + +build-linux: ## Build for Linux (useful for deployment) + @echo "Building $(BINARY_NAME) for Linux..." + @mkdir -p $(BUILD_DIR) + @GOOS=linux GOARCH=amd64 go build -o $(BUILD_DIR)/$(BINARY_NAME)-linux . + @echo "Linux binary built: $(BUILD_DIR)/$(BINARY_NAME)-linux" + +##@ Dependencies +deps: ## Download dependencies + @echo "Downloading dependencies..." + @go mod download + @go mod tidy + +##@ Deployment +install: build ## Install the service (requires sudo) + @echo "Installing contact-scrape service..." + @sudo mkdir -p /opt/contact-scrape + @sudo cp $(BUILD_DIR)/$(BINARY_NAME) /opt/contact-scrape/ + @sudo cp index.html /opt/contact-scrape/ + @sudo mkdir -p /opt/contact-scrape/data + @sudo chown -R www-data:www-data /opt/contact-scrape + @sudo cp contact-scrape.service /etc/systemd/system/ + @sudo systemctl daemon-reload + @sudo systemctl enable contact-scrape + @echo "Service installed. Start with: sudo systemctl start contact-scrape" + +uninstall: ## Uninstall the service (requires sudo) + @echo "Uninstalling contact-scrape service..." + @sudo systemctl stop contact-scrape 2>/dev/null || true + @sudo systemctl disable contact-scrape 2>/dev/null || true + @sudo rm -f /etc/systemd/system/contact-scrape.service + @sudo systemctl daemon-reload + @sudo rm -rf /opt/contact-scrape + @echo "Service uninstalled." + +status: ## Check service status + @sudo systemctl status contact-scrape + +start: ## Start the service + @sudo systemctl start contact-scrape + +stop: ## Stop the service + @sudo systemctl stop contact-scrape + +restart: ## Restart the service + @sudo systemctl restart contact-scrape + +logs: ## View service logs + @sudo journalctl -u contact-scrape -f + +##@ File Management +upload-xlsx: ## Upload contacts.xlsx to server (set SERVER variable) + @if [ -z "$(SERVER)" ]; then echo "Usage: make upload-xlsx SERVER=user@hostname"; exit 1; fi + @echo "Uploading contacts.xlsx to $(SERVER)..." + @scp contacts.xlsx $(SERVER):/opt/contact-scrape/ + @ssh $(SERVER) "sudo chown www-data:www-data /opt/contact-scrape/contacts.xlsx" + @ssh $(SERVER) "sudo systemctl restart contact-scrape" + @echo "File uploaded and service restarted." + +##@ Monitoring +monitor: ## Monitor the service with file watching + @echo "Monitoring contacts.xlsx for changes..." + @while true; do \ + inotifywait -e modify contacts.xlsx 2>/dev/null && \ + echo "File changed, reloading..." && \ + curl -X POST http://localhost:$(PORT)/reload; \ + sleep 1; \ + done + +##@ Utilities +clean: ## Clean build artifacts + @echo "Cleaning build artifacts..." + @rm -rf $(BUILD_DIR) + @rm -f data/contacts.json + +setup-dirs: ## Create necessary directories + @mkdir -p data + @mkdir -p $(BUILD_DIR) + +check-file: ## Check if contacts.xlsx exists and show info + @if [ -f "contacts.xlsx" ]; then \ + echo "✓ contacts.xlsx found"; \ + ls -lh contacts.xlsx; \ + else \ + echo "✗ contacts.xlsx not found"; \ + echo "Please place your Excel file in the current directory"; \ + fi + +##@ Docker (Optional) +docker-build: ## Build Docker image + @echo "Building Docker image..." + @docker build -t contact-scrape . + +docker-run: ## Run in Docker container + @echo "Running Docker container..." + @docker run -p $(PORT):$(PORT) -v $(PWD)/contacts.xlsx:/app/contacts.xlsx -v $(PWD)/data:/app/data contact-scrape + +##@ Information +info: ## Show application information + @echo "Contact Scrape Application" + @echo "=========================" + @echo "Port: $(PORT)" + @echo "Binary: $(BINARY_NAME)" + @echo "Build dir: $(BUILD_DIR)" + @echo "" + @echo "Endpoints:" + @echo " http://localhost:$(PORT)/ - Web interface" + @echo " http://localhost:$(PORT)/contacts - JSON API" + @echo " http://localhost:$(PORT)/reload - Reload data (POST)" \ No newline at end of file diff --git a/kontakt/contact-scrape.go b/kontakt/contact-scrape.go new file mode 100644 index 0000000..945d0cc --- /dev/null +++ b/kontakt/contact-scrape.go @@ -0,0 +1,403 @@ +package main + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "regexp" + "strings" + "time" + + "github.com/xuri/excelize/v2" +) + +type Contact struct { + Name string `json:"name"` + Position string `json:"position"` + Phone string `json:"phone,omitempty"` + ServicePhone string `json:"service_phone,omitempty"` + Table int `json:"table"` // 1 for first table, 2 for second table +} + +type ContactData struct { + Contacts []Contact `json:"contacts"` + LastUpdated time.Time `json:"last_updated"` + FileHash string `json:"file_hash"` +} + +var ( + currentData *ContactData + dataFile = "data/contacts.json" + xlsxFile = "contacts.xlsx" +) + +func main() { + // Create data directory if it doesn't exist + if err := os.MkdirAll("data", 0755); err != nil { + log.Printf("Warning: Could not create data directory: %v", err) + } + + // Load existing data or parse from Excel + loadData() + + // Set up HTTP handlers + http.HandleFunc("/", serveIndex) + http.HandleFunc("/contacts", serveContacts) + http.HandleFunc("/reload", reloadData) + + // Start server + port := os.Getenv("PORT") + if port == "" { + port = "80" + } + + log.Printf("Server starting on port %s", port) + log.Printf("Access the application at: http://localhost:%s", port) + log.Fatal(http.ListenAndServe(":"+port, nil)) +} + +func loadData() { + // Check if Excel file exists + if _, err := os.Stat(xlsxFile); os.IsNotExist(err) { + log.Printf("Excel file %s not found, using empty data", xlsxFile) + currentData = &ContactData{ + Contacts: []Contact{}, + LastUpdated: time.Now(), + FileHash: "", + } + return + } + + // Calculate current file hash + currentHash, err := calculateFileHash(xlsxFile) + if err != nil { + log.Printf("Error calculating file hash: %v", err) + return + } + + // Check if cached data exists and is up to date + if cachedData, err := loadCachedData(); err == nil { + if cachedData.FileHash == currentHash { + log.Println("Using cached data (file unchanged)") + currentData = cachedData + return + } + } + + // Parse Excel file + log.Println("Parsing Excel file...") + contacts, err := parseExcelFile(xlsxFile) + if err != nil { + log.Printf("Error parsing Excel file: %v", err) + // Use empty data if parsing fails + currentData = &ContactData{ + Contacts: []Contact{}, + LastUpdated: time.Now(), + FileHash: currentHash, + } + return + } + + currentData = &ContactData{ + Contacts: contacts, + LastUpdated: time.Now(), + FileHash: currentHash, + } + + // Save to cache + if err := saveCachedData(currentData); err != nil { + log.Printf("Warning: Could not save cached data: %v", err) + } + + log.Printf("Loaded %d contacts from Excel file", len(contacts)) +} + +func calculateFileHash(filename string) (string, error) { + file, err := os.Open(filename) + if err != nil { + return "", err + } + defer file.Close() + + hash := md5.New() + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + + return fmt.Sprintf("%x", hash.Sum(nil)), nil +} + +func loadCachedData() (*ContactData, error) { + file, err := os.Open(dataFile) + if err != nil { + return nil, err + } + defer file.Close() + + var data ContactData + decoder := json.NewDecoder(file) + if err := decoder.Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} + +func saveCachedData(data *ContactData) error { + file, err := os.Create(dataFile) + if err != nil { + return err + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + return encoder.Encode(data) +} + +func parseExcelFile(filename string) ([]Contact, error) { + f, err := excelize.OpenFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to open Excel file: %v", err) + } + defer f.Close() + + // Get the first sheet name + sheets := f.GetSheetList() + if len(sheets) == 0 { + return nil, fmt.Errorf("no sheets found in Excel file") + } + + sheetName := sheets[0] + var contacts []Contact + + // Parse first table (A-D columns) + contacts = append(contacts, parseTable(f, sheetName, "A", "D", 1)...) + + // Parse second table (F-H columns) + contacts = append(contacts, parseTable(f, sheetName, "F", "H", 2)...) + + return contacts, nil +} + +func parseTable(f *excelize.File, sheetName, startCol, endCol string, tableNum int) []Contact { + var contacts []Contact + var currentContact *Contact + + // Get all rows in the sheet + rows, err := f.GetRows(sheetName) + if err != nil { + log.Printf("Error getting rows: %v", err) + return contacts + } + + // Skip header rows (first 3 rows based on your description) + startRow := 3 + if len(rows) <= startRow { + return contacts + } + + // Column indices + var nameCol, positionCol, phoneCol, servicePhoneCol int + if tableNum == 1 { + nameCol, positionCol, phoneCol, servicePhoneCol = 0, 1, 2, 3 // A, B, C, D + } else { + nameCol, positionCol, phoneCol = 5, 6, 7 // F, G, H + } + + for i := startRow; i < len(rows); i++ { + row := rows[i] + + // Skip if row is too short + if len(row) <= nameCol { + continue + } + + // Check for "Aktualizace" - end of data + if len(row) > nameCol && strings.Contains(strings.ToLower(row[nameCol]), "aktualizace") { + break + } + + // Check for special formatting rows (like "*02(xx)") + if len(row) > positionCol && strings.Contains(row[positionCol], "*") { + continue + } + + name := strings.TrimSpace(row[nameCol]) + position := "" + phone := "" + servicePhone := "" + + if len(row) > positionCol { + position = strings.TrimSpace(row[positionCol]) + } + if len(row) > phoneCol { + phone = strings.TrimSpace(row[phoneCol]) + } + if tableNum == 1 && len(row) > servicePhoneCol { + servicePhone = strings.TrimSpace(row[servicePhoneCol]) + } + + // Clean phone numbers + phone = cleanPhoneNumber(phone) + servicePhone = cleanPhoneNumber(servicePhone) + + // If we have a name, start a new contact + if name != "" && !strings.Contains(name, "(") { + currentContact = &Contact{ + Name: name, + Position: position, + Phone: phone, + ServicePhone: servicePhone, + Table: tableNum, + } + contacts = append(contacts, *currentContact) + } else if currentContact != nil { + // This is additional data for the current contact + newContact := *currentContact + if position != "" { + newContact.Position = position + } + if phone != "" { + newContact.Phone = phone + } + if servicePhone != "" { + newContact.ServicePhone = servicePhone + } + contacts = append(contacts, newContact) + } + } + + return contacts +} + +func cleanPhoneNumber(phone string) string { + if phone == "" { + return "" + } + + // Remove extra whitespace + phone = strings.TrimSpace(phone) + + // Remove common formatting characters + re := regexp.MustCompile(`[^\d+\-\s()]`) + phone = re.ReplaceAllString(phone, "") + + // If it's just a short number (internal extension), keep as is + if len(phone) <= 3 { + return phone + } + + // If it looks like a Czech number without country code, add it + if regexp.MustCompile(`^[67]\d{8}$`).MatchString(strings.ReplaceAll(phone, " ", "")) { + return "+420 " + phone + } + + return phone +} + +func serveIndex(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + + // Check if index.html exists + if _, err := os.Stat("index.html"); os.IsNotExist(err) { + // Serve embedded HTML if file doesn't exist + w.Header().Set("Content-Type", "text/html; charset=utf-8") + fmt.Fprint(w, getEmbeddedHTML()) + return + } + + http.ServeFile(w, r, "index.html") +} + +func serveContacts(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") + + if currentData == nil { + http.Error(w, `{"error": "No data available"}`, http.StatusInternalServerError) + return + } + + encoder := json.NewEncoder(w) + encoder.SetIndent("", " ") + encoder.Encode(currentData) +} + +func reloadData(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + log.Println("Manual reload requested") + loadData() + + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"status": "reloaded", "contacts_count": %d}`, len(currentData.Contacts)) +} + +func getEmbeddedHTML() string { + return ` + + + + + Kontakty + + + + +
+
+

📞 Firemní telefonní seznam

+

Poppe + Potthoff kontakty

+
+
+ +
+
+
+ +
+ +
+
+ + +
+
+ +
+
+

Načítání kontaktů...

+
+ + +
+
+ + + +` +} diff --git a/kontakt/contact-scrape.service b/kontakt/contact-scrape.service new file mode 100644 index 0000000..ff60678 --- /dev/null +++ b/kontakt/contact-scrape.service @@ -0,0 +1,29 @@ +[Unit] +Description=Contact Scrape Service +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/contact-scrape +ExecStart=/opt/contact-scrape/contact-scrape +Restart=always +RestartSec=5 +Environment=PORT=8080 + +# Security settings +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/opt/contact-scrape/data +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_BIND_SERVICE + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=contact-scrape + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/kontakt/contacts.xlsx b/kontakt/contacts.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..07b950b678627b7bafe834db18cea42ef60872a1 GIT binary patch literal 12928 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 literal 0 HcmV?d00001 diff --git a/kontakt/index.html b/kontakt/index.html new file mode 100644 index 0000000..722b6df --- /dev/null +++ b/kontakt/index.html @@ -0,0 +1,376 @@ + + + + + + Kontakty + + + + + +
+
+

📞 Firemní telefonní seznam

+

Poppe + Potthoff kontakty

+
+
+ +
+
+ +
+
+

📞 Kontakty

+

Firemní telefonní seznam

+
+ +
+ + +
+
+ + +
+
+ + +
+ + + +
+ + +
+
+

Načítání kontaktů...

+
+ + + + + + +
+
+ +
+
+

2025 Poppe + Potthoff

+
+
+ + + + \ No newline at end of file diff --git a/main.go b/main.go index fed7ed8..2539cb8 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "log" "net/http" "os" + "os/exec" "strings" "time" @@ -33,171 +34,6 @@ type GeoCoords struct { Lng string `json:"lng"` } -type Contact struct { - Name string `json:"name"` - Position string `json:"position"` - Phone string `json:"phone"` - ServicePhone string `json:"service_phone"` - Table int `json:"table"` -} - -func getEmbeddedHTML() string { - return ` - - - - - Kontakty - - - -
-
-

Kontakty

-
-
-
-
Seznam kontaktů
-
-
- Jméno - Jan Novák -
-
- Pozice - Ředitel -
-
-
-
- Telefon - +420 123 456 789 -
-
- Služební telefon - +420 987 654 321 -
-
-
- Stůl - 1 -
-
-
- -
- - - ` -} - func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) @@ -207,22 +43,26 @@ func main() { w.Write([]byte(`{"status":"ok"}`)) })) - http.HandleFunc("/contacts", enableCORS(handleContacts)) - http.HandleFunc("/reload", enableCORS(handleReload)) - http.HandleFunc("/", enableCORS(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/kontakty" { - w.Header().Set("Content-Type", "text/html") - w.Write([]byte(getEmbeddedHTML())) - return - } - http.ServeFile(w, r, r.URL.Path[1:]) + http.ServeFile(w, r, "index.html") })) http.HandleFunc("/evidence-aut", enableCORS(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "evidence-aut.html") })) + http.HandleFunc("/kontakt", enableCORS(func(w http.ResponseWriter, r *http.Request) { + // Run make dev in the kontakt directory + cmd := exec.Command("make", "dev") + cmd.Dir = "kontakt" + err := cmd.Start() + if err != nil { + log.Printf("Error running make dev: %v", err) + } + + http.ServeFile(w, r, "kontakt/index.html") + })) + port := os.Getenv("PORT") if port == "" { port = "80" @@ -299,16 +139,19 @@ func handleSubmit(w http.ResponseWriter, r *http.Request) { return } + // Formátování dat do českého formátu czechMonths := []string{ "ledna", "února", "března", "dubna", "května", "června", "července", "srpna", "září", "října", "listopadu", "prosince", } + // Zpracování začátku cesty parsedDateStart, err := time.Parse("2006-01-02", entry.DateStart) if err != nil { log.Printf("Chyba při parsování data začátku: %v", err) } + // Zpracování konce cesty parsedDateEnd, err := time.Parse("2006-01-02", entry.DateEnd) if err != nil { log.Printf("Chyba při parsování data konce: %v", err) @@ -483,6 +326,7 @@ func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechM
`) + // Formátování dat a časů pro zobrazení formattedDateStart := "" if parsedDateStart.IsZero() == false { monthNameStart := czechMonths[parsedDateStart.Month()-1] @@ -499,6 +343,7 @@ func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechM formattedDateEnd = entry.DateEnd } + // Výpočet celkové doby jízdy startDateTime, startErr := time.Parse("2006-01-02T15:04", fmt.Sprintf("%sT%s", entry.DateStart, entry.TimeStart)) endDateTime, endErr := time.Parse("2006-01-02T15:04", fmt.Sprintf("%sT%s", entry.DateEnd, entry.TimeEnd)) @@ -524,6 +369,7 @@ func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechM } } + // Vypsání informací o řidiči a vozidle htmlContent.WriteString(`
Informace o řidiči a vozidle
@@ -538,6 +384,7 @@ func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechM
`) + // Vypsání informací o trase htmlContent.WriteString(`
Informace o trase
@@ -552,6 +399,7 @@ func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechM
`) + // Vypsání informací o času htmlContent.WriteString(`
Časové údaje
@@ -570,6 +418,7 @@ func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechM
`) + // Vypsání informací o kilometrech htmlContent.WriteString(`
Stav tachometru
@@ -620,11 +469,3 @@ func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechM return d.DialAndSend(m) } - -func handleContacts(w http.ResponseWriter, r *http.Request) { - // Implement contact listing logic -} - -func handleReload(w http.ResponseWriter, r *http.Request) { - // Implement contact reload logic -}