diff --git a/.env b/.env new file mode 100644 index 0000000..74a26c0 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +DB_HOST=localhost +DB_USER=postgres +DB_NAME=urlshortener +DB_PORT=5432 +DB_PASSWORD=postgres +PORT=8080 diff --git a/main.go b/main.go new file mode 100644 index 0000000..269674f --- /dev/null +++ b/main.go @@ -0,0 +1,90 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "net/http" + "os" + + "github.com/gorilla/mux" + _ "github.com/lib/pq" +) + +type URLShortener struct { + DB *sql.DB +} + +type URL struct { + ID int + OriginalURL string + ShortCode string + CreatedAt string + Visits int +} + +func main() { + dbHost := getEnv("DB_HOST", "localhost") + dbPort := getEnv("DB_PORT", "5432") + dbUser := getEnv("DB_USER", "postgres") + dbPassword := getEnv("DB_PASSWORD", "postgres") + dbName := getEnv("DB_NAME", "urlshortener") + + connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName) + db, err := sql.Open("postgres", connStr) + + if err != nil { + log.Fatalf("Failed to connect to the database: %v", err) + } + + defer db.Close() + + err = db.Ping() + if err != nil { + log.Fatalf("Failed to ping the database: %v", err) + } + + err = initializeDB(db) + if err != nil { + log.Fatalf("Failed to initialize the DB: %v", err) + } + + shortener := &URLShortener{DB: db} + + router := mux.NewRouter() + + router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) + + router.HandleFunc("/", shortener.homeHandler).Methods("GET") + + port := getEnv("PORT", "8080") + log.Printf("Server starting on port http://localhost:%s", port) + if err := http.ListenAndServe(":"+port, router); err != nil { + log.Fatalf("Server failed to start: %v", err) + } +} + +func getEnv(key, fallback string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return fallback +} + +func initializeDB(db *sql.DB) error { + _, err := db.Exec(` + CREATE TABLE IF NOT EXISTS urls( + id SERIAL PRIMARY KEY, + original_url TEXT NOT NULL, + short_code VARCHAR(10) UNIQUE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + visits INTEGER DEFAULT 0 + ) +`) + return err +} + +func (s *URLShortener) homeHandler(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "./templates/index.html") +} diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..a236886 --- /dev/null +++ b/static/style.css @@ -0,0 +1,148 @@ +:root{ + --primary-color: #1095c1; + --secondary-color: #98c1d9; + --accent-color: #3d5a80; + --background-color: #f8f9fa; + --text-color:#293242; +} + +body{ + display: flex; + flex-direction: column; +min-height: 100vh; +background-color: var(--background-color); +color: var(--text-color); +} + +main{ + flex: 1; + padding: 2rem 0; +} + +h1{ + text-align: center; + margin-bottom: 2rem; + color: var(--accent-color); +} + +nav{ + margin-bottom: 2rem; +} + +nav ul{ + display: flex; + justify-content: center; + padding: 0; + list-style: none; +} + +nav ul li{ + margin: 0 1rem; +} + +nav ul li a { + text-decoration: none; + padding: 0.5rem 1rem; + border-radius: 4px; + transition: background-color 0.3s; +} + +nav ul li a.active{ + background-color: var(--primary-color); + color: white; +} + +form{ + margin-bottom: 2rem; +} + +.shorten-btn{ + background-color: var(--primary-color); + border-color: var(--primary-color); +} + +.shorten-btn:hover{ + background-color: var(--accent-color); + border-color: var(--accent-color); +} + +.result-container{ + background-color: white; + padding: 1.5rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-top: 2rem; + text-align: center; +} + +.result-container a{ + color: var(--primary-color); + word-break: break-all; + display: block; + margin: 1rem 0; + font-size: 1.2rem; +} + +.copy-btn{ + background-color: var(--secondary-color); + border-color: var(--secondary-color); + color: var(--text-color) +} + +.copy-btn:hover{ + background-color: var(--accent-color); + border-color: var(--accent-color); + color: white; +} + +.stats-container{ + background-color: white; + padding: 1.5rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +table{ + width: 100%; + border-collapse: collapse; +} + +tab th, table td{ + padding: 0.75rem; + text-align: left; + border-bottom: 1px solid #e9ecef; +} + +table th{ + background-color: var(--secondary-color); + color: var(--text-color); +} + +table tr:hover{ + background-color: rgba(152, 193, 217, 0.1); +} + +.loading{ + text-align: center; + padding: 2rem; + color: #6c757d; +} + +footer{ + text-align: center; + padding: 1rem 0; + margin-top: 2rem; + background-color: white; + box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.05); +} + +@media (max-width: 768px) { + .grid{ + grid-template-columns: 1fr; + } + + nav ul li { + margin: 0 0.5rem; + } + +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index be86b73..a39a208 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,55 +1,67 @@ -
- - -