commit c034dbdae085d42970d072267a5ea0ef736fdd22 Author: Sergey Melnikov Date: Wed Jan 17 11:41:47 2024 +0300 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5657f6e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..16241bb --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,20 @@ +# https://golangci-lint.run/usage/configuration/#config-file + +run: + allow-parallel-runners: true + +linters: + enable: + - megacheck + - govet + - gocritic + - gocyclo + - lll + - exportloopref + disable-all: false + disable: + - scopelint + presets: + - bugs + - unused + fast: false diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2c4d81a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: "3" +services: + cassandra: + container_name: cassandra + environment: + - CASSANDRA_KEYSPACE=userprofileservice + - TABLE_NAME=users + build: + context: . + dockerfile: ./docker/db/Dockerfile + command: /cassandra-init.sh + ports: + - 9042:9042 + service: + container_name: service + environment: + - CASSANDRA_HOST=cassandra + - CASSANDRA_PORT=9042 + - CASSANDRA_KEYSPACE=userprofileservice + - CASSANDRA_CONSISTANCY=LOCAL_QUORUM + - TABLE_NAME=users + - BIDN_SERVICE=:8080 + - AWS_REGION=fake-region + - AWS_ACCESS_KEY_ID=fake-key + - AWS_SECRET_ACCESS_KEY=fake-secret + - BUCKET_NAME=fake-bucket + - AWS_ACL=public-read + build: + context: . + dockerfile: ./docker/api/Dockerfile + ports: + - "8080:8080" + networks: + - default + depends_on: + - cassandra \ No newline at end of file diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile new file mode 100644 index 0000000..a3be06b --- /dev/null +++ b/docker/api/Dockerfile @@ -0,0 +1,12 @@ +FROM golang as builder +RUN mkdir -p /build/src +WORKDIR /build +COPY ./src/. /build/src/ +COPY ./go.mod /build/ +RUN go mod vendor && go test ./src/ && CGO_ENABLED=0 GOOS=linux go build -o apiservice ./src/main.go + +FROM alpine +ENV TZ=Europe/Helsinki +RUN apk add --no-cache tzdata && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +COPY --from=builder /build/apiservice . +ENTRYPOINT [ "./apiservice" ] \ No newline at end of file diff --git a/docker/db/Dockerfile b/docker/db/Dockerfile new file mode 100644 index 0000000..b92b151 --- /dev/null +++ b/docker/db/Dockerfile @@ -0,0 +1,3 @@ +FROM cassandra +COPY docker/db/cassandra-init.sh /cassandra-init.sh +RUN chmod +x /cassandra-init.sh \ No newline at end of file diff --git a/docker/db/cassandra-init.sh b/docker/db/cassandra-init.sh new file mode 100644 index 0000000..779edf0 --- /dev/null +++ b/docker/db/cassandra-init.sh @@ -0,0 +1,10 @@ +#!/bin/bash +cat >/import.cql < e.g. /upload/userpicture +// uid - User UUID +// file - file data +// file_name - e.g. 1234.jpg +func (a *API) uploadFiles() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + file_field := vars["field"] + // get uuid from request + uid, err := gocql.ParseUUID(r.FormValue("id")) + if err != nil { + a.respond(w, r, http.StatusBadRequest, nil, err, "") + return + } + // find user by uuid + user, err := a.services.GetUser(uid) + if err != nil { + a.respond(w, r, http.StatusNotFound, nil, err, "") + return + } + // check available field in struct + if !utils.AvailableAndAddValue(user, file_field, "") { + a.respond(w, r, http.StatusBadRequest, nil, errors.New("wrong url parameter"), "") + return + } + + file_name := r.FormValue("file_name") + fileUrl, err := a.fileHandler(r, file_name) + if err != nil { + a.respond(w, r, http.StatusInternalServerError, nil, err, file_name) + return + } + // add aws url to struct user + utils.AvailableAndAddValue(user, file_field, fileUrl) + err = a.services.CreateUser(user) + if err != nil { + a.respond(w, r, http.StatusInternalServerError, nil, err, "") + return + } + a.respond(w, r, http.StatusOK, user, nil, "") + } +} + +// fileHandler get data from MultipartForm and send to AWS S3 +func (a *API) fileHandler(r *http.Request, filename string) (string, error) { + // total of maxMemory bytes + err := r.ParseMultipartForm(10 << 20) + if err != nil { + return "", err + } + // file upload as form-data + m := r.MultipartForm + fileUrl := "" + + for _, v := range m.File { + for _, f := range v { + file, err := f.Open() + if err != nil { + return fileUrl, err + } + defer file.Close() + // upload file to aws + fileUrl, err = a.aws.UploadFile(file, filename) + if err != nil { + return fileUrl, err + } + } + } + + return fileUrl, nil +} + +// handleRoot paga page +func (a *API) handleRoot() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + a.respond(w, r, http.StatusOK, nil, nil, "") + } +} + +// notFound page not found +func (a *API) notFound(w http.ResponseWriter, r *http.Request) { + a.respond(w, r, http.StatusNotFound, nil, errors.New("page not found"), "") +} + +// notAllowed return custom method method not allowed +func (a *API) notAllowed(w http.ResponseWriter, r *http.Request) { + a.respond(w, r, http.StatusMethodNotAllowed, nil, errors.New("method not allowed"), "") +} + +// setAnswer set api's answer +func setAnswer(data interface{}, e error, add string) *structs.ANSWERAPI { + err := "" + if e != nil { + err = e.Error() + } + a := &structs.ANSWERAPI{ + DATA: data, + ERROR: err, + ADDITIONAL: add, + } + a.Prepare() + return a +} + +// respond generate http response for api +func (a *API) respond(w http.ResponseWriter, r *http.Request, code int, data interface{}, e error, add string) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") + w.WriteHeader(code) + if r.Method == "OPTIONS" { + return + } + _ = json.NewEncoder(w).Encode(setAnswer(data, e, add)) +}