Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
FROM golang:1.23 AS builder

WORKDIR /app
COPY . .
RUN cd software_engineering && go build -o server main.go

FROM debian:stable-slim
WORKDIR /root/
COPY --from=builder /app/software_engineering/server .
COPY go.mod go.sum ./
RUN go mod download

COPY software_engineering/ ./software_engineering/

WORKDIR /app/software_engineering
RUN CGO_ENABLED=0 GOOS=linux go build -o /server main.go

FROM scratch

WORKDIR /root
COPY --from=builder /server .

EXPOSE 8080
CMD ["./server"]
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module interactive-whiteboard-docs

go 1.23.9

require github.com/gorilla/mux v1.8.1
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
107 changes: 100 additions & 7 deletions software_engineering/main.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,110 @@
package main

import (
"fmt"
"encoding/json"
"log"
"net/http"
"sync"

"github.com/gorilla/mux"
)

func main() {
http.HandleFunc("/ping", PingHandler)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
type Board struct {
UID string `json:"uid"`
Title string `json:"title"`
}

var (
boards = make(map[string]Board)
mu sync.Mutex
apiKey = "secret123"
)

func auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-API-Key") != apiKey {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}

// POST /board — создать доску
func createBoard(w http.ResponseWriter, r *http.Request) {
var b Board
if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}

if b.UID == "" || b.Title == "" {
http.Error(w, "bad request", http.StatusBadRequest)
return
}

mu.Lock()
boards[b.UID] = b
mu.Unlock()

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(b)
}

// GET /board/{uid} — получить инфу о доске
func getBoard(w http.ResponseWriter, r *http.Request) {
uid := mux.Vars(r)["uid"]

if uid == "" {
http.Error(w, "bad request", http.StatusBadRequest)
return
}

mu.Lock()
b, ok := boards[uid]
mu.Unlock()

if !ok {
http.Error(w, "not found", http.StatusNotFound)
return
}

w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(b)
}

// DELETE /board/{uid} — удалить доску
func deleteBoard(w http.ResponseWriter, r *http.Request) {
uid := mux.Vars(r)["uid"]

if uid == "" {
http.Error(w, "bad request", http.StatusBadRequest)
return
}

mu.Lock()
_, ok := boards[uid]
if ok {
delete(boards, uid)
}
mu.Unlock()

if !ok {
http.Error(w, "not found", http.StatusNotFound)
return
}

w.WriteHeader(http.StatusNoContent)
}

func PingHandler(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprint(w, "pong\n")
func main() {
r := mux.NewRouter()

r.Handle("/board", auth(http.HandlerFunc(createBoard))).Methods(http.MethodPost)
r.Handle("/board/{uid}", auth(http.HandlerFunc(getBoard))).Methods(http.MethodGet)
r.Handle("/board/{uid}", auth(http.HandlerFunc(deleteBoard))).Methods(http.MethodDelete)

log.Println("server running at :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
58 changes: 50 additions & 8 deletions software_engineering/main_test.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,64 @@
package main

import (
"io/ioutil"
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/gorilla/mux"
)

func TestPingHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/ping", nil)
func setupRouter() *mux.Router {
r := mux.NewRouter()
r.Handle("/board", auth(http.HandlerFunc(createBoard))).Methods(http.MethodPost)
r.Handle("/board/{uid}", auth(http.HandlerFunc(getBoard))).Methods(http.MethodGet)
r.Handle("/board/{uid}", auth(http.HandlerFunc(deleteBoard))).Methods(http.MethodDelete)
return r
}

func newReq(method, url string, body []byte) *http.Request {
req := httptest.NewRequest(method, url, bytes.NewReader(body))
req.Header.Set("X-API-Key", apiKey)
return req
}

func TestBoardAPI(t *testing.T) {
boards = make(map[string]Board)
router := setupRouter()

// Создать доску
body := []byte(`{"uid":"b1","title":"Первая доска"}`)
w := httptest.NewRecorder()
router.ServeHTTP(w, newReq(http.MethodPost, "/board", body))
if w.Code != http.StatusCreated {
t.Fatalf("ожидали 201, получили %d", w.Code)
}

PingHandler(w, req)
// Получить доску
w = httptest.NewRecorder()
router.ServeHTTP(w, newReq(http.MethodGet, "/board/b1", nil))
if w.Code != http.StatusOK {
t.Fatalf("ожидали 200, получили %d", w.Code)
}
var b Board
_ = json.NewDecoder(w.Body).Decode(&b)
if b.Title != "Первая доска" {
t.Errorf("ожидали title=Первая доска, получили %s", b.Title)
}

resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
// Удалить доску
w = httptest.NewRecorder()
router.ServeHTTP(w, newReq(http.MethodDelete, "/board/b1", nil))
if w.Code != http.StatusNoContent {
t.Fatalf("ожидали 204, получили %d", w.Code)
}

if string(body) != "pong\n" {
t.Errorf("ожидали pong, получили %s", string(body))
// Повторное удаление -> 404
w = httptest.NewRecorder()
router.ServeHTTP(w, newReq(http.MethodDelete, "/board/b1", nil))
if w.Code != http.StatusNotFound {
t.Fatalf("ожидали 404, получили %d", w.Code)
}
}