0x4a52466c696e74 6 kuukautta sitten
vanhempi
commit
15d866fd5c
4 muutettua tiedostoa jossa 404 lisäystä ja 0 poistoa
  1. 77 0
      example/application.go
  2. 6 0
      example/group.go
  3. 148 0
      example/main_test.go
  4. 173 0
      example/user.go

+ 77 - 0
example/application.go

@@ -0,0 +1,77 @@
+package example_test
+
+import (
+	"log"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+// ExapmleApp реализует интерфейс IApplication, который отдает серверу параметры подключения,
+// секретный ключ шифрования токена авторизации,
+// а так же обрабатывает поступающие запросы
+type ExampleApp struct {
+	addr      string
+	secret    []byte
+	users     *sync.Map
+	groups    *sync.Map
+	idCounter int64
+}
+
+func (s *ExampleApp) GenerateID() int64 {
+	id := atomic.AddInt64(&s.idCounter, 1)
+	return id
+}
+
+func (s *ExampleApp) Addr() string {
+	return s.addr
+}
+
+func (s *ExampleApp) Secret() []byte {
+	return s.secret
+}
+
+func (s *ExampleApp) Executer(r *rest.Request) (rest.IExecuter, bool) {
+	switch r.RPath() {
+	case "/user/register":
+		return &ExampleRequestRegister{}, true
+	case "/user/info":
+		return &ExampleRequestUserInfo{}, true
+	case "/user/set_avatar":
+		return &ExampleRequestSetAvatar{}, true
+	default:
+		return nil, false
+	}
+}
+
+var App *ExampleApp
+
+// ExampleNew показывает пример создания и запуска сервера с RestAPI
+func ExampleNew() {
+	// создаем новый сервер с использованием приложения ExampleApp
+	App = &ExampleApp{
+		addr:      "localhost:8080",
+		secret:    []byte("top-secret"),
+		users:     new(sync.Map),
+		groups:    new(sync.Map),
+		idCounter: 0,
+	}
+	// Добавляем публичную группу
+	groupID := App.GenerateID()
+	App.groups.Store(
+		groupID,
+		&ExampleGroup{
+			ID:   groupID,
+			Name: "public",
+		},
+	)
+	// создаем сервер
+	restServ := rest.New(App)
+	// пробуем запустить его. Если через секунду запуск не удался, будет возвращена ошибка
+	if err := restServ.Listen(time.Second); err != nil {
+		// ошибка запуска
+		log.Fatal(err)
+	}
+}

+ 6 - 0
example/group.go

@@ -0,0 +1,6 @@
+package example_test
+
+type ExampleGroup struct {
+	ID   int64  `rest:"default"`
+	Name string `rest:"default"`
+}

+ 148 - 0
example/main_test.go

@@ -0,0 +1,148 @@
+package example_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"log"
+	"mime"
+	"mime/multipart"
+	"net/http"
+	"strings"
+	"testing"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+)
+
+func SendRequest(uri string, data json.Map, files map[string]io.Reader, auth string) (rData json.Map, rFiles map[string]io.ReadCloser) {
+	var (
+		buf         io.Reader
+		contentType string
+	)
+	if files == nil {
+		// json
+		contentType = "application/json"
+		buf = bytes.NewBuffer(data.JSON())
+	} else {
+		// multipart form
+		rBuf := bytes.NewBuffer(nil)
+		form := multipart.NewWriter(rBuf)
+		// setup content type
+		contentType = form.FormDataContentType()
+		// json (data)
+		field, _ := form.CreateFormField("data")
+		field.Write(data.JSON())
+		// files
+		for name, file := range files {
+			part, _ := form.CreateFormFile(name, name)
+			io.Copy(part, file)
+		}
+		form.Close()
+		buf = rBuf
+	}
+	req, _ := http.NewRequest(
+		"POST",
+		fmt.Sprintf("http://localhost:8080%s", uri),
+		buf,
+	)
+	req.Header.Set("Content-Type", contentType)
+	if len(auth) > 0 {
+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", auth))
+	}
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		log.Fatal(err)
+	}
+	if resp.StatusCode != http.StatusOK {
+		bData, _ := io.ReadAll(resp.Body)
+		log.Println(string(bData))
+		json.Unmarshal(bData, &rData)
+		log.Fatal("bad status code: ", resp.StatusCode, rData.JSONPrettyString())
+	}
+	rContentType := resp.Header.Get("Content-Type")
+	log.Println("---------------------", rContentType)
+	if rContentType == "application/json" {
+		bData, _ := io.ReadAll(resp.Body)
+		log.Println(string(bData))
+		json.Unmarshal(bData, &rData)
+	} else if strings.Contains(rContentType, "multipart/form-data") {
+		rFiles = make(map[string]io.ReadCloser)
+		_, params, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		mReader := multipart.NewReader(resp.Body, params["boundary"])
+		for {
+			part, err := mReader.NextPart()
+			if err == io.EOF {
+				break // Закончить на конце потока
+			}
+			if err != nil {
+				log.Fatal(err)
+			}
+			if part.FormName() == "data" {
+				bData, _ := io.ReadAll(part)
+				json.Unmarshal(bData, &rData)
+			} else {
+				if fName := part.FileName(); len(fName) > 0 {
+					rFiles[fName] = part
+				}
+			}
+		}
+	}
+	resp.Body.Close()
+	return
+}
+
+func TestExample(t *testing.T) {
+	var data json.Map
+	var files map[string]io.ReadCloser
+	ExampleNew()
+
+	t.Log("--- register user ---")
+	data, _ = SendRequest(
+		"/user/register",
+		json.Map{
+			"name":     "First User",
+			"password": "my-strong-pass",
+			"group_id": 1,
+		},
+		nil,
+		"",
+	)
+	t.Log(data)
+	t.Log("------------------------------------------")
+	token := data.String("token", "")
+	t.Log("token", token)
+
+	t.Log("--- set avatar ---")
+	sFiles := map[string]io.Reader{
+		"avatar.jpg": bytes.NewBuffer([]byte{1, 2, 3, 4, 5}),
+	}
+
+	data, files = SendRequest(
+		"/user/set_avatar",
+		json.Map{},
+		sFiles,
+		token,
+	)
+
+	data.LogPretty()
+
+	t.Log("------------------------------------------")
+
+	t.Log("--- user info ---")
+	data, files = SendRequest(
+		"/user/info",
+		json.Map{
+			"fields": []string{
+				"id", "name", "group", "avatar",
+			},
+		},
+		nil,
+		token,
+	)
+	data.LogPretty()
+	log.Println(files)
+	t.Log("------------------------------------------")
+	//ch := make(chan struct{})
+	//<-ch
+}

+ 173 - 0
example/user.go

@@ -0,0 +1,173 @@
+package example_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"log"
+	"strings"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+type ExampleUser struct {
+	ID       int64  `rest:"default"`
+	Name     string `rest:"default"`
+	password string
+	Group    *ExampleGroup
+	Avatar   []byte `rest:"ignore"`
+}
+
+func (s *ExampleUser) RestFields(result json.Map, files map[string]io.ReadCloser, names rest.FieldList) {
+	if _, check := names.Field("avatar"); check {
+		if len(s.Avatar) == 0 {
+			result["avatar"] = nil
+		} else {
+			fName := fmt.Sprintf("user%v.avatar.jpg", s.ID)
+			result["avatar"] = fName
+			files[fName] = io.NopCloser(bytes.NewReader(s.Avatar))
+		}
+	}
+}
+
+// Register ////////////////////////////////////////////
+
+type ExampleRequestRegister struct {
+	ID       int64
+	Name     string `rest:"required"`
+	Password string `rest:"required"`
+	GroupID  int64  `rest:"required"`
+	// Объект группы, который определяется в валидации.
+	// В боевых условиях это поле должно быть приватное.
+	group *ExampleGroup
+}
+
+func (s *ExampleRequestRegister) Validate(req *rest.Request) *rest.Response {
+	// пользователь не должен быть авторизован
+	if req.Auth() != nil {
+		return rest.ResponseErrorMessage("AlreadyAuthorized", "User is already authorized", 500)
+	}
+	// проверяем имя
+	if s.Name = strings.TrimSpace(s.Name); len(s.Name) == 0 {
+		return rest.ResponseErrField("name", "expected not empty string")
+	}
+	// проверяем пароль
+	if len(s.Password) < 3 {
+		return rest.ResponseErrField("password", "expected minimum 3 symbols")
+	}
+	// вилидируем группу
+	group, check := App.groups.Load(s.GroupID)
+	if !check {
+		return rest.ResponseErrorMessage("GroupNotFound", "Group is not found", 500)
+	}
+	// Устанавливаем группу для использования в методе выполняния,
+	// чтобы не выбирать её снова
+	s.group = group.(*ExampleGroup)
+	return nil
+}
+
+func (s *ExampleRequestRegister) Execute(req *rest.Request) *rest.Response {
+	// создаем нового юзера
+	userID := App.GenerateID()
+	user := &ExampleUser{
+		ID:       userID,
+		Name:     s.Name,
+		password: s.Password,
+		Group:    s.group,
+	}
+
+	// генерируем токен авторизацции
+	token, err := req.GenerateToken(
+		json.Map{
+			"id": userID,
+		},
+		0,
+	)
+	if err != nil {
+		return rest.ResponseErrorMessage("TokenGenerateError", err.Error(), 500)
+	}
+
+	files := make(map[string]io.ReadCloser)
+	// сохраняем пользлвателя в хранилище
+	App.users.Store(userID, user)
+	fields, err := rest.Fields(user, files, req.Data().Slice("fields", nil)...)
+	if err != nil {
+		return rest.ResponseErrorMessage("FieldsError", err.Error(), 500)
+	}
+
+	// возвращаем успешный ответ
+	return rest.ResponseSuccess(
+		json.Map{
+			"user":  fields,
+			"token": token,
+		},
+		nil,
+	)
+}
+
+// user info ////////////////////////////////////////////
+
+type ExampleRequestUserInfo struct {
+	user *ExampleUser
+}
+
+func (s *ExampleRequestUserInfo) Validate(req *rest.Request) *rest.Response {
+	if !req.IsAuth() {
+		return rest.ResponseErrorMessage("NotAuth", "Not authorized", 500)
+	}
+	auth := req.Auth()
+	user, check := App.users.Load(auth.Int("id", 0))
+	if !check {
+		return rest.ResponseErrorMessage("UserNotFound", "User not found", 500)
+	}
+	s.user = user.(*ExampleUser)
+	return nil
+}
+
+func (s *ExampleRequestUserInfo) Execute(req *rest.Request) *rest.Response {
+	files := make(map[string]io.ReadCloser)
+	log.Println(s.user.Group)
+	fields := req.Data().Slice("fields", nil)
+	rFields, err := rest.Fields(s.user, files, fields...)
+	log.Println(err)
+	if err != nil {
+		return rest.ResponseError(err)
+	}
+	return rest.ResponseSuccess(rFields, files)
+}
+
+// set avatar
+
+type ExampleRequestSetAvatar struct {
+	user   *ExampleUser
+	avatar io.Reader
+}
+
+func (s *ExampleRequestSetAvatar) Validate(req *rest.Request) *rest.Response {
+	log.Println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
+	// проверяем авторизацию
+	if !req.IsAuth() {
+		return rest.ResponseErrorMessage("NotAuth", "Not authorized", 500)
+	}
+	// проверяем файл аватара
+	var check bool
+	if s.avatar, check = req.File("avatar.jpg"); !check {
+		return rest.ResponseErrorMessage("FileNotFound", "avatar.jpg", 500)
+	}
+	// поиск юзера
+	auth := req.Auth()
+	user, check := App.users.Load(auth.Int("id", 0))
+	if !check {
+		return rest.ResponseErrorMessage("UserNotFound", "User not found", 500)
+	}
+	s.user = user.(*ExampleUser)
+	return nil
+}
+
+func (s *ExampleRequestSetAvatar) Execute(req *rest.Request) *rest.Response {
+	// сохраняем аватар
+	s.user.Avatar, _ = io.ReadAll(s.avatar)
+	// возвращаем пустой ответ
+	return rest.ResponseSuccess(json.Map{}, nil)
+}