feat: add session create

This commit is contained in:
Muyao CHEN 2024-10-13 21:10:33 +02:00
parent 798b9a7695
commit ca2985abb4
5 changed files with 255 additions and 9 deletions

View File

@ -1,7 +1,34 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package controller package controller
import ( import (
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log" "git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/token"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -9,12 +36,55 @@ type Session interface {
Create(*gin.Context) Create(*gin.Context)
} }
type SessionController struct{} type SessionController struct {
userUsecase usecase.User
func NewSessionController() Session {
return &SessionController{}
} }
func NewSessionController(u usecase.User) Session {
return &SessionController{u}
}
type Token struct {
Token string `json:"token"`
}
type createParams struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
}
// Create creates a session for a user and returns a token
//
// Since we use JWT method, this token is not stored anywhere. Thus it
// stops at the controller level.
func (sc *SessionController) Create(ctx *gin.Context) { func (sc *SessionController) Create(ctx *gin.Context) {
log.CtxLog(ctx).DebugLog("session create") var params createParams
if err := ctx.Bind(&params); err != nil {
log.ErrorLog("param error", "err", err)
core.WriteResponse(ctx, UserParamsErr, nil)
return
}
user := model.User{
Email: params.Email,
Password: params.Password,
}
err := sc.userUsecase.Exist(ctx, &user)
if err != nil {
core.WriteResponse(ctx, err, nil)
return
}
// user exists. Generate the token for the user
tokenString, err := token.Sign(user.Email)
if err != nil {
core.WriteResponse(ctx, errno.InternalServerErr, nil)
return
}
core.WriteResponse(ctx, nil, Token{
Token: tokenString,
})
} }

View File

@ -0,0 +1,95 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package controller
import (
"bytes"
"encoding/json"
"net/http"
"testing"
"time"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller/usecasemock"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/test"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/token"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestSessionCreate(t *testing.T) {
tests := []struct {
Name string
User createParams
Errno *errno.Errno
}{
{"registered user", createParams{
Email: "correct@correct.com",
Password: "strong password",
}, errno.OK},
{"unregistered user", createParams{
Email: "unregistered@error.com",
Password: "strong password",
}, usecase.UserNotExist},
}
token.Init("secret", 1*time.Second)
for _, tst := range tests {
t.Run(tst.Name, func(t *testing.T) {
testUserUsecase := usecasemock.NewtestUserUsecase()
sessionController := NewSessionController(testUserUsecase)
r := gin.New()
r.POST(
"/session/create",
func(ctx *gin.Context) { sessionController.Create(ctx) },
)
user, _ := json.Marshal(tst.User)
res := test.PerformRequest(t, r, "POST", "/session/create", bytes.NewReader(user),
test.Header{
Key: "content-type",
Value: "application/json",
})
assert.Equal(t, tst.Errno.HTTP, res.Result().StatusCode, res.Body)
if tst.Errno.HTTP != http.StatusOK {
var got errno.Errno
err := json.NewDecoder(res.Result().Body).Decode(&got)
// XXX: the http status is not in the json. So it must be reset back the the struct
got.HTTP = res.Result().StatusCode
assert.NoError(t, err)
assert.Equal(t, tst.Errno, &got)
return
}
var got Token
err := json.NewDecoder(res.Result().Body).Decode(&got)
assert.NoError(t, err)
tkResp, err := token.Parse(got.Token)
assert.NoError(t, err)
assert.Equal(t, tst.User.Email, tkResp.Identity)
})
}
}

View File

@ -0,0 +1,55 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package usecasemock
import (
"context"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase"
)
type testUserUsecase struct{}
func NewtestUserUsecase() usecase.User {
return &testUserUsecase{}
}
func (*testUserUsecase) Create(ctx context.Context, u *model.User) (*model.User, error) {
return nil, nil
}
func (*testUserUsecase) Exist(ctx context.Context, u *model.User) error {
switch u.Email {
case "a@b.c":
if u.Password == "strong password" {
return nil
} else {
return usecase.UserWrongPassword
}
case "unregistered@error.com":
return usecase.UserNotExist
}
// Should never reach here
return nil
}

View File

@ -1,9 +1,35 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package registry package registry
import "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller" import (
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/repo"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase"
)
// NewSessionController returns a session controller's implementation // NewSessionController returns a session controller's implementation
func (r *registry) NewSessionController() controller.Session { func (r *registry) NewSessionController() controller.Session {
// u := usecase.NewSessionUsecase(repo.NewSessionRepository(r.db), repo.NewDBRepository(r.db)) u := usecase.NewUserUsecase(repo.NewUserRepository(r.db), repo.NewDBRepository(r.db))
return controller.NewSessionController() return controller.NewSessionController(u)
} }

View File

@ -49,7 +49,7 @@ type TokenResp struct {
var ( var (
once sync.Once once sync.Once
config *Config config Config
) )
var ErrMissingHeader = errors.New("Authorization is needed in the header") var ErrMissingHeader = errors.New("Authorization is needed in the header")