diff --git a/.gitignore b/.gitignore index aaadf73..ebe43b9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ coverage.* *.coverprofile profile.cov +images/ # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/css/index.css b/css/index.css new file mode 100644 index 0000000..a7444e5 --- /dev/null +++ b/css/index.css @@ -0,0 +1,26 @@ +.htmx-indicator { + opacity:0; + transition: opacity 500ms ease-in; +} +.htmx-request .htmx-indicator{ + opacity:1 +} +.htmx-request.htmx-indicator{ + opacity:1 +} + +.contact.htmx-swapping { + opacity:0; + transition: opacity 500ms ease-in; +} + +.nav-link { + padding-bottom: 2px; + border-bottom: 2px solid transparent; + transition: all 0.2s; +} +.nav-link.active { + color: #2563eb; /* Tailwind blue-600 */ + border-bottom-color: #2563eb; + font-weight: 600; +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6598b8f --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module duhweb + +go 1.24.5 + +require github.com/go-chi/chi/v5 v5.2.3 + +require github.com/mattn/go-sqlite3 v1.14.32 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dbec922 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/internal/api/project_handler.go b/internal/api/project_handler.go new file mode 100644 index 0000000..767cd2a --- /dev/null +++ b/internal/api/project_handler.go @@ -0,0 +1,44 @@ +package api + +import ( + "database/sql" + "duhweb/internal/store" + "html/template" + "net/http" +) + +type ProjectHandler struct { + Templates *template.Template + ProjectStore *store.SQLiteProjectStore +} + +func (h *ProjectHandler) Render(w http.ResponseWriter, tmpl string, data interface{}) { + if err := h.Templates.ExecuteTemplate(w, tmpl, data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func NewProjectHandler(db *sql.DB) *ProjectHandler { + return &ProjectHandler{ + Templates: template.Must(template.ParseGlob("views/*.html")), + ProjectStore: store.NewSQLiteProjectStore(db), + } +} + +func (h *ProjectHandler) InitPage(w http.ResponseWriter, r *http.Request) { + h.Render(w, "index", nil) +} + +func (h *ProjectHandler) AboutPage(w http.ResponseWriter, r *http.Request) { + h.Render(w, "about", nil) +} + +func (h *ProjectHandler) ProjectsPage(w http.ResponseWriter, r *http.Request) { + projects, err := h.ProjectStore.GetAllProjects() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + h.Render(w, "projects", projects) +} \ No newline at end of file diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..1ace923 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,30 @@ +package app + +import ( + "database/sql" + "duhweb/internal/api" + "duhweb/internal/store" + "log" + "os" +) + +type Application struct { + Logger *log.Logger + ProjectHandler *api.ProjectHandler + DB *sql.DB +} + +func NewApplication() (*Application, error) { + logger := log.New(os.Stdout, "", log.Ldate|log.Ltime) + sqlDB, err := store.Open() + if err != nil { + return nil, err + } + ProjectHandler := api.NewProjectHandler(sqlDB) + app := &Application{ + Logger: logger, + ProjectHandler: ProjectHandler, + DB: sqlDB, + } + return app, nil +} \ No newline at end of file diff --git a/internal/routes/routes.go b/internal/routes/routes.go new file mode 100644 index 0000000..4f7542b --- /dev/null +++ b/internal/routes/routes.go @@ -0,0 +1,16 @@ +package routes + +import ( + "duhweb/internal/app" + + "github.com/go-chi/chi/v5" +) + +func SetupRoutes(app *app.Application) *chi.Mux { + r := chi.NewRouter() + + r.Get("/", app.ProjectHandler.InitPage) + r.Get("/about", app.ProjectHandler.AboutPage) + r.Get("/projects", app.ProjectHandler.ProjectsPage) + return r +} \ No newline at end of file diff --git a/internal/store/database.go b/internal/store/database.go new file mode 100644 index 0000000..f463a40 --- /dev/null +++ b/internal/store/database.go @@ -0,0 +1,22 @@ +package store + +import ( + "database/sql" + "fmt" + + _ "github.com/mattn/go-sqlite3" +) + +func Open() (*sql.DB, error) { + db, err := sql.Open("sqlite3", "C:/Users/dilan/Documents/Go/my_website_db.db") + if err != nil { + return nil, fmt.Errorf("db open: %w", err) + } + + if err := db.Ping(); err != nil { + return nil, fmt.Errorf("db ping: %w", err) + } + + fmt.Println("Connected to Database...") + return db, nil +} \ No newline at end of file diff --git a/internal/store/project_store.go b/internal/store/project_store.go new file mode 100644 index 0000000..9a05968 --- /dev/null +++ b/internal/store/project_store.go @@ -0,0 +1,37 @@ +package store + +import "database/sql" + +type Project struct { + ID int `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Link string `json:"link"` + TechStack string `json:"tech_stack"` +} + +type SQLiteProjectStore struct { + db *sql.DB +} + +func NewSQLiteProjectStore(db *sql.DB) *SQLiteProjectStore { + return &SQLiteProjectStore{db: db} +} + +func (s *SQLiteProjectStore) GetAllProjects() ([]Project, error) { + rows, err := s.db.Query("SELECT id, title, description, link, tech_stack FROM projects") + if err != nil { + return nil, err + } + defer rows.Close() + + var projects []Project + for rows.Next() { + var p Project + if err := rows.Scan(&p.ID, &p.Title, &p.Description, &p.Link, &p.TechStack); err != nil { + return nil, err + } + projects = append(projects, p) + } + return projects, nil +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..7eab5cf --- /dev/null +++ b/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "duhweb/internal/app" + "duhweb/internal/routes" + "net/http" + "time" +) + +type Count struct { + Count int +} + +func main() { + app, err := app.NewApplication() + if err != nil { + panic(err) + } + + app.Logger.Println("app has started") + + r := routes.SetupRoutes(app) + r.Handle("/images/*", http.StripPrefix("/images/", http.FileServer(http.Dir("images")))) + r.Handle("/css/*", http.StripPrefix("/css/", http.FileServer(http.Dir("css")))) + + server := &http.Server{ + Addr: ":8080", + Handler: r, + IdleTimeout: time.Minute, + ReadTimeout: 10 * time.Second, + WriteTimeout: 30 * time.Second, + } + + if err := server.ListenAndServe(); err != nil { + app.Logger.Fatal(err) + } +} diff --git a/views/index.html b/views/index.html new file mode 100644 index 0000000..5baff0d --- /dev/null +++ b/views/index.html @@ -0,0 +1,120 @@ +{{ block "index" . }} + + + + + + My CV Website + + + + + + + +
+ +
+ + + + +
+ {{ template "about" . }} +
+ + + + + + + + + +{{ end }} + +{{ block "about" . }} + +
+

Hi, I'm Your Name

+

+ A [Your Role] who loves building [something about what you do]. +

+
+ View Projects + Contact Me +
+
+ + +
+

About Me

+

+ I’m a [Your Profession] with experience in [key skills]. I enjoy solving problems, learning new technologies, and building applications that make an impact. + Currently exploring Go, htmx, and modern web tools. +

+
+ {{ end }} + +{{ block "projects" . }} +

Projects

+ {{ range . }} + {{ template "project" . }} + {{ end}} +{{ end }} + +{{ block "project" . }} +
+ {{ .Title }} - {{ .Description }} +
+{{ end }} + + + \ No newline at end of file diff --git a/views/test.html b/views/test.html new file mode 100644 index 0000000..924e5bf --- /dev/null +++ b/views/test.html @@ -0,0 +1,75 @@ +{{ block "index" . }} + + + + + + + + + + + {{ template "form" .Form }} +
+ {{ template "display" .Data}} + + + +{{ end }} + +{{ block "form" . }} +
+ name: + email: + + {{ if .Errors.email }} +
{{ .Errors.email }}
+ {{ end }} + +
+{{ end }} + +{{ block "display" .}} +
+ {{ range .Contacts }} + {{ template "contact" . }} + {{ end }} +
+{{ end }} + +{{ block "contact" .}} +
+
+ + + + +
+ Name: {{ .Name }} + Email: {{ .Email }} + +
+ loading +
+
+{{ end }} + +{{ block "oob-contact" .}} +
+ {{ template "contact" . }} +
+{{ end }} \ No newline at end of file