+ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..db62b89 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.vscode +.gitignore +Makefile +README.md diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..528fc01 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,28 @@ +name: Go CI Pipeline +on: [push, pull_request] +jobs: + build_and_test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + - name: Install dependencies + run: go mod download + + - name: Install Docker + run: curl -fsSL https://get.docker.com | sh + - name: Login to private registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY_URL }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + - name: Build and push container image + run: | + docker build -t toutaloue:1.0 . + docker tag toutaloue:1.0 ${{ secrets.REGISTRY_URL }}/jkinyongo/toutaloue:1.0 + docker push ${{ secrets.REGISTRY_URL }}/jkinyongo/toutaloue:1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a23b4d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Editor/IDE +# .idea/ +# .vscode/ +*.DS_Store + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2ecda5d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Stage 1: Build the Go application +FROM golang:1.25-alpine AS builder +WORKDIR /app + +# Copy go.mod and go.sum files to download dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Copy the rest of the source code +COPY . . + +# Build the application +# CGO_ENABLED=0 is important for a small static binary +# -o /app/server creates the binary in a known location +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server + +# Stage 2: Create the final, small image +FROM alpine:latest +WORKDIR /app + +# Copy the static assets and templates +COPY --from=builder /app/public/assets ./public/assets +COPY --from=builder /app/public/templates ./public/templates + +# Copy the compiled binary from the builder stage +COPY --from=builder /app/server . + +# Expose the port the app runs on +EXPOSE 8080 + +# Command to run the executable +CMD ["./server"] diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..a83e0c1 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "html/template" + "log" + "net/http" + "time" +) + +// PageData holds data to be rendered in templates +type PageData struct { + Title string + Page string // Used by index.html to decide which template to include on full load + Time string +} + +// Global template variable +var tmpl *template.Template + +func main() { + // 1. Parse Templates + // standard lib 'Must' panics if parsing fails (good for startup) + tmpl = template.Must(template.ParseGlob("public/templates/*.html")) + tmpl = template.Must(tmpl.ParseGlob("public/templates/*/*.html")) + + // 2. Setup Router (Go 1.22+ Standard Library Mux) + mux := http.NewServeMux() + + // 3. Static Files + // "GET /static/" matches any path starting with /static/ + fileServer := http.FileServer(http.Dir("./public/assets")) + mux.Handle("GET /public/assets/", http.StripPrefix("/public/assets/", fileServer)) + + // 4. Routes + mux.HandleFunc("GET /", homeHandler) + mux.HandleFunc("GET /explore", exploreHandler) + mux.HandleFunc("GET /songon", songonHandler) + mux.HandleFunc("GET /deposer-annonce", deposerAnnonceHandler) + mux.HandleFunc("GET /inscription", inscriptionHandler) + mux.HandleFunc("GET /contact", contactHandler) + + // Example POST to show HTMX form handling + mux.HandleFunc("POST /contact", contactPostHandler) + + log.Println("Starting server on :8080") + if err := http.ListenAndServe(":8080", mux); err != nil { + log.Fatal(err) + } +} + +// Helper function to render pages with "If Else" logic for HTMX +func render(w http.ResponseWriter, r *http.Request, pageName string, data PageData) { + // Check if request is from HTMX + isHtmx := r.Header.Get("HX-Request") == "true" + + // Ensure the Page field is set for the layout to use + data.Page = pageName + + if isHtmx { + // IF HTMX: Render only the partial (e.g., "home", "songon") + // We set the content type slightly differently usually, but HTML is fine. + err := tmpl.ExecuteTemplate(w, pageName, data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } else { + // ELSE: Render the full layout ("index"), which includes the partial + err := tmpl.ExecuteTemplate(w, "index", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// --- Handlers --- + +func homeHandler(w http.ResponseWriter, r *http.Request) { + // Prevent catch-all "/" from matching non-existent paths + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + + data := PageData{ + Title: "Home", + Time: time.Now().Format(time.Kitchen), + } + render(w, r, "home", data) +} + +func exploreHandler(w http.ResponseWriter, r *http.Request) { + data := PageData{ + Title: "Explore", + Time: time.Now().Format(time.Kitchen), + } + render(w, r, "explore", data) +} + +func songonHandler(w http.ResponseWriter, r *http.Request) { + data := PageData{ + Title: "Songon", + Time: time.Now().Format(time.Kitchen), + } + render(w, r, "songon", data) +} + +func deposerAnnonceHandler(w http.ResponseWriter, r *http.Request) { + data := PageData{ + Title: "Déposer", + Time: time.Now().Format(time.Kitchen), + } + render(w, r, "deposer-annonce", data) +} + +func inscriptionHandler(w http.ResponseWriter, r *http.Request) { + data := PageData{ + Title: "Inscription", + Time: time.Now().Format(time.Kitchen), + } + render(w, r, "inscription", data) +} + +func contactHandler(w http.ResponseWriter, r *http.Request) { + data := PageData{ + Title: "Contact", + } + render(w, r, "contact", data) +} + +func contactPostHandler(w http.ResponseWriter, r *http.Request) { + // Emulate processing + time.Sleep(500 * time.Millisecond) + + // Return a snippet to replace the form + w.Write([]byte(`
Thank you for contacting us.
+ +1&&u.push(e.virtualSize-r)}if(0===u.length&&(u=[0]),0!==a.spaceBetween){const s=e.isHorizontal()&&n?"marginLeft":t("marginRight");c.filter(((e,t)=>!a.cssMode||t!==c.length-1)).css({[s]:`${x}px`})}if(a.centeredSlides&&a.centeredSlidesBounds){let e=0;m.forEach((t=>{e+=t+(a.spaceBetween?a.spaceBetween:0)})),e-=a.spaceBetween;const t=e-r;u=u.map((e=>e<0?-f:e>t?t+g:e))}if(a.centerInsufficientSlides){let e=0;if(m.forEach((t=>{e+=t+(a.spaceBetween?a.spaceBetween:0)})),e-=a.spaceBetween,e {r.wrapperEl.style.scrollSnapType="",r._swiperImmediateVirtual=!1}))}else{if(!r.support.smoothScroll)return w({swiper:r,targetPosition:s,side:e?"left":"top"}),!0;h.scrollTo({[e?"left":"top"]:s,behavior:"smooth"})}return!0}return r.setTransition(t),r.setTranslate(v),r.updateActiveIndex(n),r.updateSlidesClasses(),r.emit("beforeTransitionStart",t,a),r.transitionStart(s,b),0===t?r.transitionEnd(s,b):r.animating||(r.animating=!0,r.onSlideToWrapperTransitionEnd||(r.onSlideToWrapperTransitionEnd=function(e){r&&!r.destroyed&&e.target===this&&(r.$wrapperEl[0].removeEventListener("transitionend",r.onSlideToWrapperTransitionEnd),r.$wrapperEl[0].removeEventListener("webkitTransitionEnd",r.onSlideToWrapperTransitionEnd),r.onSlideToWrapperTransitionEnd=null,delete r.onSlideToWrapperTransitionEnd,r.transitionEnd(s,b))}),r.$wrapperEl[0].addEventListener("transitionend",r.onSlideToWrapperTransitionEnd),r.$wrapperEl[0].addEventListener("webkitTransitionEnd",r.onSlideToWrapperTransitionEnd)),!0},slideToLoop:function(e,t,s,a){if(void 0===e&&(e=0),void 0===t&&(t=this.params.speed),void 0===s&&(s=!0),"string"==typeof e){const t=parseInt(e,10);if(!isFinite(t))throw new Error(`The passed-in 'index' (string) couldn't be converted to 'number'. [${e}] given.`);e=t}const i=this;let r=e;return i.params.loop&&(r+=i.loopedSlides),i.slideTo(r,t,s,a)},slideNext:function(e,t,s){void 0===e&&(e=this.params.speed),void 0===t&&(t=!0);const a=this,{animating:i,enabled:r,params:n}=a;if(!r)return a;let l=n.slidesPerGroup;"auto"===n.slidesPerView&&1===n.slidesPerGroup&&n.slidesPerGroupAuto&&(l=Math.max(a.slidesPerViewDynamic("current",!0),1));const o=a.activeIndex
+ We believe in keeping things simple. No complex JavaScript frameworks, just
+ pure HTML and hypermedia controls.
+ This page was rendered at: {{.Time}} Plus de 1000 références disponibles immédiatement. Des prix imbattables, des réservations flexibles. Pour plus de 1000€ de locations 100% de paiements sécurisés Nous contacter au 01 84 21 13 96
+ Trouve les meilleurs produits de tes catégories préférées,
+ explore des centaines de produits avec toutes les catégories
+ possibles, profite d’une réduction de 15% et bien plus encore.
+
+ Profitez des jours de réduction que nous avons pour vous,
+ louez vos produits préférés, plus vous louez, plus nous avons
+ de réductions pour vous.
+
+ La meilleure plateforme pour louer des produits,
+ la location est très facile à faire et offre de grandes réductions.
+
+ La meilleure plateforme pour louer des produits,
+ la location est très facile à faire et offre de grandes réductions.
+
+ La meilleure plateforme pour louer des produits,
+ la location est très facile à faire et offre de grandes réductions.
+
+ La meilleure plateforme pour louer des produits,
+ la location est très facile à faire et offre de grandes réductions.
+ Idéal pour les sociétés, associations Ou auto-entreprenneurs Inscription simplifiée Louez vos outils sans forme juridique
+ Une commune et une sous-préfecture située dans le district
+ autonome d'Abidjan. Elle fait partie des communes de la région côtière
+ et longe la lagune Ebrié à l'ouest.
+ 0&&o(u(t));const a=e.children(`.${s.slidePrevClass}`);a.length>0&&o(u(a))}}function p(){const e=r();if(!t||t.destroyed)return;const s=t.params.lazy.scrollingElement?d(t.params.lazy.scrollingElement):d(e),a=s[0]===e,i=a?e.innerWidth:s[0].offsetWidth,l=a?e.innerHeight:s[0].offsetHeight,o=t.$el.offset(),{rtlTranslate:u}=t;let h=!1;u&&(o.left-=t.$el[0].scrollLeft);const m=[[o.left,o.top],[o.left+t.width,o.top],[o.left,o.top+t.height],[o.left+t.width,o.top+t.height]];for(let e=0;eAbout Us
+ Contact
+
+Location de matériel pour
+
Professionnels et PartculiersLes catégories populaires
+
+ Matériels de construction
+ Électroménagers
+ Véhicules
+ Salles événementielles
+ Bateaux
+ Les locations de la semaine
+ Tous les produits
+
+ Chaise événementielle
+
+ Bateau de plaisance
+
+ Vaisselle haut de gamme
+
+ Bâche événementielle
+
+ Terrain Attecoubé
+
+ Terrain Treichville
+
+ Terrain Marcory
+
+ Terrain Bracodi
+
+ Renault megane
+
+ Mazda
+
+ Audi
+
+ Renault megane
+ Location des produits et services
+ Livraison gratuite
+ Paiements sécurisés
+ Support 24/7
+
+ Nos catégories
+
+ de produits
+
+
+
+
+
+
+ Nos terres agricoles
+
+ Terrain Nord
+
+ Terrain Sud
+
+ Terrain Ouest
+
+ Terrain Est
+
+ Terrain Sud
+ Matériels de construction
+
+ Pelleteuse
+
+ Marteau piqueur
+
+ Benne à béton
+
+ Goulotte à matériaux
+ Jusqu'à 50% de réduction
+
+
+ Véhicules
+
+ Renault megane
+
+ Mazda
+
+ Audi
+
+ Renault megane
+
+ Mazda
+
+ Audi
+
+ Renault megane
+
+ Mazda
+
+ Audi
+
+ Renault megane
+
+ Mazda
+
+ Audi
+
+ Renault megane
+
+ Mazda
+
+ Audi
+
+ Renault megane
+
+ Mazda
+
+ Audi
+
+ Renault megane
+
+ Mazda
+
+
+ Abonnez-vous pour recevoir
+
+
+ les dernières mises à jour
+ Opinions des clients
+ Alain Doumbia
+ Quentin Loz
+ Karim Sissoko
+ Abou Traoré
+ Quel compte souhaitez-vous créer ?
+
+ PROFESSIONNEL
+
+
+ PARTICULIER
+
+ Songon
+
+ Salles évenementielles
+
+ Salle A
+
+ Salle B
+
+ Salle C
+
+ Salle D
+ Bâches structure événement
+
+ Bâche structure A
+
+ Bâche structure B
+
+ Bâche structure C
+
+ Bâche structure D
+