From 516633acc22cbc644d43531e0ffaaf3172d28d1c Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 5 Dec 2025 15:11:45 +0700 Subject: [PATCH] feat: init --- webp-converter/.gitignore | 157 ++++++-------------------------------- webp-converter/Dockerfile | 40 ++++++++++ webp-converter/README.md | 2 +- webp-converter/go.mod | 7 ++ webp-converter/go.sum | 2 + webp-converter/main.go | 78 +++++++++++++++++++ 6 files changed, 153 insertions(+), 133 deletions(-) create mode 100644 webp-converter/Dockerfile create mode 100644 webp-converter/go.mod create mode 100644 webp-converter/go.sum create mode 100644 webp-converter/main.go diff --git a/webp-converter/.gitignore b/webp-converter/.gitignore index 9a5aced..aaadf73 100644 --- a/webp-converter/.gitignore +++ b/webp-converter/.gitignore @@ -1,139 +1,32 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* +# 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 -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +# Test binary, built with `go test -c` +*.test -# Runtime data -pids -*.pid -*.seed -*.pid.lock +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov +# Dependency directories (remove the comment below to include it) +# vendor/ -# Coverage directory used by tools like istanbul -coverage -*.lcov +# Go workspace file +go.work +go.work.sum -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files +# env file .env -.env.* -!.env.example -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Sveltekit cache directory -.svelte-kit/ - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# Firebase cache directory -.firebase/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v3 -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -# Vite logs files -vite.config.js.timestamp-* -vite.config.ts.timestamp-* +# Editor/IDE +# .idea/ +# .vscode/ diff --git a/webp-converter/Dockerfile b/webp-converter/Dockerfile new file mode 100644 index 0000000..d57fa4e --- /dev/null +++ b/webp-converter/Dockerfile @@ -0,0 +1,40 @@ +ARG GO_VERSION=1.24.11 +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build +WORKDIR /src + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + --mount=type=bind,source=go.sum,target=go.sum \ + --mount=type=bind,source=go.mod,target=go.mod \ + go mod download -x + +ARG TARGETARCH + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + --mount=type=bind,target=. \ + CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin/server . + +FROM alpine:latest AS final + +RUN --mount=type=cache,target=/var/cache/apk \ + apk --update add \ + ca-certificates \ + tzdata \ + && \ + update-ca-certificates + +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +COPY --from=build /bin/server /bin/ + +EXPOSE 8080 + +ENTRYPOINT [ "/bin/server" ] diff --git a/webp-converter/README.md b/webp-converter/README.md index 0dc6e6f..302a1a3 100644 --- a/webp-converter/README.md +++ b/webp-converter/README.md @@ -1,2 +1,2 @@ # webp-worker -Cloudflare Worker to convert webp to other formats +Convert webp to other formats diff --git a/webp-converter/go.mod b/webp-converter/go.mod new file mode 100644 index 0000000..72cd888 --- /dev/null +++ b/webp-converter/go.mod @@ -0,0 +1,7 @@ +module github.com/tiennm99/webp-converter + +go 1.24.0 + +toolchain go1.24.11 + +require golang.org/x/image v0.33.0 diff --git a/webp-converter/go.sum b/webp-converter/go.sum new file mode 100644 index 0000000..ba0ff29 --- /dev/null +++ b/webp-converter/go.sum @@ -0,0 +1,2 @@ +golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= +golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= diff --git a/webp-converter/main.go b/webp-converter/main.go new file mode 100644 index 0000000..6162072 --- /dev/null +++ b/webp-converter/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "bytes" + "encoding/json" + "image/png" + "io" + "net/http" + + // _ "image/gif" + // _ "image/jpeg" + // _ "image/png" + + "golang.org/x/image/webp" +) + +type Request struct { + Img string `json:"img"` +} + +func main() { + http.HandleFunc("/process", processHandler) + http.ListenAndServe(":8080", nil) +} + +func processHandler(w http.ResponseWriter, r *http.Request) { + // Parse JSON + var req Request + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + + resp, err := http.Get(req.Img) + if err != nil { + http.Error(w, "Failed to fetch image", http.StatusBadRequest) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + http.Error(w, "Image URL returned non-200", http.StatusBadRequest) + return + } + + origBytes, err := io.ReadAll(resp.Body) + if err != nil { + http.Error(w, "Failed to read image", http.StatusInternalServerError) + return + } + + // Simple detection for WebP + isWebP := bytes.Contains(origBytes[:32], []byte("WEBP")) + + if !isWebP { + // Return the original image + w.Header().Set("Content-Type", resp.Header.Get("Content-Type")) + w.Write(origBytes) + return + } + + // Decode WebP + img, err := webp.Decode(bytes.NewReader(origBytes)) + if err != nil { + http.Error(w, "Failed to decode WebP", http.StatusInternalServerError) + return + } + + // Convert to PNG + var out bytes.Buffer + if err := png.Encode(&out, img); err != nil { + http.Error(w, "Failed to encode PNG", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "image/png") + w.Write(out.Bytes()) +}