forked from mirrors/homebox
Initial commit
This commit is contained in:
commit
29f583e936
135 changed files with 18463 additions and 0 deletions
38
backend/internal/repo/main_test.go
Normal file
38
backend/internal/repo/main_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/git-web-template/backend/ent"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var testEntClient *ent.Client
|
||||
var testRepos *AllRepos
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rand.Seed(int64(time.Now().Unix()))
|
||||
|
||||
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
|
||||
if err != nil {
|
||||
log.Fatalf("failed opening connection to sqlite: %v", err)
|
||||
}
|
||||
|
||||
if err := client.Schema.Create(context.Background()); err != nil {
|
||||
log.Fatalf("failed creating schema resources: %v", err)
|
||||
}
|
||||
|
||||
testEntClient = client
|
||||
testRepos = EntAllRepos(testEntClient)
|
||||
|
||||
defer client.Close()
|
||||
|
||||
m.Run()
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
16
backend/internal/repo/repos_all.go
Normal file
16
backend/internal/repo/repos_all.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package repo
|
||||
|
||||
import "github.com/hay-kot/git-web-template/backend/ent"
|
||||
|
||||
// AllRepos is a container for all the repository interfaces
|
||||
type AllRepos struct {
|
||||
Users UserRepository
|
||||
AuthTokens TokenRepository
|
||||
}
|
||||
|
||||
func EntAllRepos(db *ent.Client) *AllRepos {
|
||||
return &AllRepos{
|
||||
Users: &EntUserRepository{db},
|
||||
AuthTokens: &EntTokenRepository{db},
|
||||
}
|
||||
}
|
74
backend/internal/repo/token_ent.go
Normal file
74
backend/internal/repo/token_ent.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/git-web-template/backend/ent"
|
||||
"github.com/hay-kot/git-web-template/backend/ent/authtokens"
|
||||
"github.com/hay-kot/git-web-template/backend/internal/mapper"
|
||||
"github.com/hay-kot/git-web-template/backend/internal/types"
|
||||
)
|
||||
|
||||
type EntTokenRepository struct {
|
||||
db *ent.Client
|
||||
}
|
||||
|
||||
// GetUserFromToken get's a user from a token
|
||||
func (r *EntTokenRepository) GetUserFromToken(ctx context.Context, token []byte) (types.UserOut, error) {
|
||||
dbToken, err := r.db.AuthTokens.Query().
|
||||
Where(authtokens.Token(token)).
|
||||
Where(authtokens.ExpiresAtGTE(time.Now())).
|
||||
WithUser().
|
||||
Only(ctx)
|
||||
|
||||
if err != nil {
|
||||
return types.UserOut{}, err
|
||||
}
|
||||
|
||||
return mapper.UserOutFromModel(*dbToken.Edges.User), nil
|
||||
}
|
||||
|
||||
// Creates a token for a user
|
||||
func (r *EntTokenRepository) CreateToken(ctx context.Context, createToken types.UserAuthTokenCreate) (types.UserAuthToken, error) {
|
||||
tokenOut := types.UserAuthToken{}
|
||||
|
||||
dbToken, err := r.db.AuthTokens.Create().
|
||||
SetToken(createToken.TokenHash).
|
||||
SetUserID(createToken.UserID).
|
||||
SetExpiresAt(createToken.ExpiresAt).
|
||||
Save(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tokenOut, err
|
||||
}
|
||||
|
||||
tokenOut.TokenHash = dbToken.Token
|
||||
tokenOut.UserID = createToken.UserID
|
||||
tokenOut.CreatedAt = dbToken.CreatedAt
|
||||
tokenOut.ExpiresAt = dbToken.ExpiresAt
|
||||
|
||||
return tokenOut, nil
|
||||
}
|
||||
|
||||
// DeleteToken remove a single token from the database - equivalent to revoke or logout
|
||||
func (r *EntTokenRepository) DeleteToken(ctx context.Context, token []byte) error {
|
||||
_, err := r.db.AuthTokens.Delete().Where(authtokens.Token(token)).Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// PurgeExpiredTokens removes all expired tokens from the database
|
||||
func (r *EntTokenRepository) PurgeExpiredTokens(ctx context.Context) (int, error) {
|
||||
tokensDeleted, err := r.db.AuthTokens.Delete().Where(authtokens.ExpiresAtLTE(time.Now())).Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return tokensDeleted, nil
|
||||
}
|
||||
|
||||
func (r *EntTokenRepository) DeleteAll(ctx context.Context) (int, error) {
|
||||
amount, err := r.db.AuthTokens.Delete().Exec(ctx)
|
||||
return amount, err
|
||||
}
|
110
backend/internal/repo/token_ent_test.go
Normal file
110
backend/internal/repo/token_ent_test.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/git-web-template/backend/internal/types"
|
||||
"github.com/hay-kot/git-web-template/backend/pkgs/hasher"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_EntAuthTokenRepo_CreateToken(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
user := UserFactory()
|
||||
|
||||
userOut, _ := testRepos.Users.Create(ctx, user)
|
||||
|
||||
expiresAt := time.Now().Add(time.Hour)
|
||||
|
||||
generatedToken := hasher.GenerateToken()
|
||||
|
||||
token, err := testRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
TokenHash: generatedToken.Hash,
|
||||
ExpiresAt: expiresAt,
|
||||
UserID: userOut.ID,
|
||||
})
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(userOut.ID, token.UserID)
|
||||
assert.Equal(expiresAt, token.ExpiresAt)
|
||||
|
||||
// Cleanup
|
||||
err = testRepos.Users.Delete(ctx, userOut.ID)
|
||||
_, err = testRepos.AuthTokens.DeleteAll(ctx)
|
||||
}
|
||||
|
||||
func Test_EntAuthTokenRepo_GetUserByToken(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
user := UserFactory()
|
||||
userOut, _ := testRepos.Users.Create(ctx, user)
|
||||
|
||||
expiresAt := time.Now().Add(time.Hour)
|
||||
generatedToken := hasher.GenerateToken()
|
||||
|
||||
token, err := testRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
TokenHash: generatedToken.Hash,
|
||||
ExpiresAt: expiresAt,
|
||||
UserID: userOut.ID,
|
||||
})
|
||||
|
||||
// Get User from token
|
||||
foundUser, err := testRepos.AuthTokens.GetUserFromToken(ctx, token.TokenHash)
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(userOut.ID, foundUser.ID)
|
||||
assert.Equal(userOut.Name, foundUser.Name)
|
||||
assert.Equal(userOut.Email, foundUser.Email)
|
||||
|
||||
// Cleanup
|
||||
err = testRepos.Users.Delete(ctx, userOut.ID)
|
||||
_, err = testRepos.AuthTokens.DeleteAll(ctx)
|
||||
}
|
||||
|
||||
func Test_EntAuthTokenRepo_PurgeExpiredTokens(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
user := UserFactory()
|
||||
userOut, _ := testRepos.Users.Create(ctx, user)
|
||||
|
||||
createdTokens := []types.UserAuthToken{}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
expiresAt := time.Now()
|
||||
generatedToken := hasher.GenerateToken()
|
||||
|
||||
createdToken, err := testRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
TokenHash: generatedToken.Hash,
|
||||
ExpiresAt: expiresAt,
|
||||
UserID: userOut.ID,
|
||||
})
|
||||
|
||||
assert.NoError(err)
|
||||
assert.NotNil(createdToken)
|
||||
|
||||
createdTokens = append(createdTokens, createdToken)
|
||||
|
||||
}
|
||||
|
||||
// Purge expired tokens
|
||||
tokensDeleted, err := testRepos.AuthTokens.PurgeExpiredTokens(ctx)
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(5, tokensDeleted)
|
||||
|
||||
// Check if tokens are deleted
|
||||
for _, token := range createdTokens {
|
||||
_, err := testRepos.AuthTokens.GetUserFromToken(ctx, token.TokenHash)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
err = testRepos.Users.Delete(ctx, userOut.ID)
|
||||
_, err = testRepos.AuthTokens.DeleteAll(ctx)
|
||||
}
|
20
backend/internal/repo/token_interface.go
Normal file
20
backend/internal/repo/token_interface.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hay-kot/git-web-template/backend/internal/types"
|
||||
)
|
||||
|
||||
type TokenRepository interface {
|
||||
// GetUserFromToken get's a user from a token
|
||||
GetUserFromToken(ctx context.Context, token []byte) (types.UserOut, error)
|
||||
// Creates a token for a user
|
||||
CreateToken(ctx context.Context, createToken types.UserAuthTokenCreate) (types.UserAuthToken, error)
|
||||
// DeleteToken remove a single token from the database - equivalent to revoke or logout
|
||||
DeleteToken(ctx context.Context, token []byte) error
|
||||
// PurgeExpiredTokens removes all expired tokens from the database
|
||||
PurgeExpiredTokens(ctx context.Context) (int, error)
|
||||
// DeleteAll removes all tokens from the database
|
||||
DeleteAll(ctx context.Context) (int, error)
|
||||
}
|
141
backend/internal/repo/users_ent.go
Normal file
141
backend/internal/repo/users_ent.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/git-web-template/backend/ent"
|
||||
"github.com/hay-kot/git-web-template/backend/ent/user"
|
||||
"github.com/hay-kot/git-web-template/backend/internal/types"
|
||||
)
|
||||
|
||||
type EntUserRepository struct {
|
||||
db *ent.Client
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) toUserOut(usr *types.UserOut, entUsr *ent.User) {
|
||||
usr.ID = entUsr.ID
|
||||
usr.Password = entUsr.Password
|
||||
usr.Name = entUsr.Name
|
||||
usr.Email = entUsr.Email
|
||||
usr.IsSuperuser = entUsr.IsSuperuser
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) GetOneId(ctx context.Context, id uuid.UUID) (types.UserOut, error) {
|
||||
usr, err := e.db.User.Query().Where(user.ID(id)).Only(ctx)
|
||||
|
||||
usrOut := types.UserOut{}
|
||||
|
||||
if err != nil {
|
||||
return usrOut, err
|
||||
}
|
||||
|
||||
e.toUserOut(&usrOut, usr)
|
||||
|
||||
return usrOut, nil
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) GetOneEmail(ctx context.Context, email string) (types.UserOut, error) {
|
||||
usr, err := e.db.User.Query().Where(user.Email(email)).Only(ctx)
|
||||
|
||||
usrOut := types.UserOut{}
|
||||
|
||||
if err != nil {
|
||||
return usrOut, err
|
||||
}
|
||||
|
||||
e.toUserOut(&usrOut, usr)
|
||||
|
||||
return usrOut, nil
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) GetAll(ctx context.Context) ([]types.UserOut, error) {
|
||||
users, err := e.db.User.Query().All(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var usrs []types.UserOut
|
||||
|
||||
for _, usr := range users {
|
||||
usrOut := types.UserOut{}
|
||||
e.toUserOut(&usrOut, usr)
|
||||
usrs = append(usrs, usrOut)
|
||||
}
|
||||
|
||||
return usrs, nil
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) Create(ctx context.Context, usr types.UserCreate) (types.UserOut, error) {
|
||||
err := usr.Validate()
|
||||
usrOut := types.UserOut{}
|
||||
|
||||
if err != nil {
|
||||
return usrOut, err
|
||||
}
|
||||
|
||||
entUser, err := e.db.User.
|
||||
Create().
|
||||
SetName(usr.Name).
|
||||
SetEmail(usr.Email).
|
||||
SetPassword(usr.Password).
|
||||
SetIsSuperuser(usr.IsSuperuser).
|
||||
Save(ctx)
|
||||
|
||||
e.toUserOut(&usrOut, entUser)
|
||||
|
||||
return usrOut, err
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) Update(ctx context.Context, ID uuid.UUID, data types.UserUpdate) error {
|
||||
bldr := e.db.User.Update().Where(user.ID(ID))
|
||||
|
||||
if data.Name != nil {
|
||||
bldr = bldr.SetName(*data.Name)
|
||||
}
|
||||
|
||||
if data.Email != nil {
|
||||
bldr = bldr.SetEmail(*data.Email)
|
||||
}
|
||||
|
||||
// TODO: FUTURE
|
||||
// if data.Password != nil {
|
||||
// bldr = bldr.SetPassword(*data.Password)
|
||||
// }
|
||||
|
||||
// if data.IsSuperuser != nil {
|
||||
// bldr = bldr.SetIsSuperuser(*data.IsSuperuser)
|
||||
// }
|
||||
|
||||
_, err := bldr.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := e.db.User.Delete().Where(user.ID(id)).Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) DeleteAll(ctx context.Context) error {
|
||||
_, err := e.db.User.Delete().Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *EntUserRepository) GetSuperusers(ctx context.Context) ([]types.UserOut, error) {
|
||||
users, err := e.db.User.Query().Where(user.IsSuperuser(true)).All(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var usrs []types.UserOut
|
||||
|
||||
for _, usr := range users {
|
||||
usrOut := types.UserOut{}
|
||||
e.toUserOut(&usrOut, usr)
|
||||
usrs = append(usrs, usrOut)
|
||||
}
|
||||
|
||||
return usrs, nil
|
||||
}
|
148
backend/internal/repo/users_ent_test.go
Normal file
148
backend/internal/repo/users_ent_test.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hay-kot/git-web-template/backend/internal/types"
|
||||
"github.com/hay-kot/git-web-template/backend/pkgs/faker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func UserFactory() types.UserCreate {
|
||||
f := faker.NewFaker()
|
||||
return types.UserCreate{
|
||||
Name: f.RandomString(10),
|
||||
Email: f.RandomEmail(),
|
||||
Password: f.RandomString(10),
|
||||
IsSuperuser: f.RandomBool(),
|
||||
}
|
||||
}
|
||||
|
||||
func Test_EntUserRepo_GetOneEmail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
user := UserFactory()
|
||||
ctx := context.Background()
|
||||
|
||||
testRepos.Users.Create(ctx, user)
|
||||
|
||||
foundUser, err := testRepos.Users.GetOneEmail(ctx, user.Email)
|
||||
|
||||
assert.NotNil(foundUser)
|
||||
assert.Nil(err)
|
||||
assert.Equal(user.Email, foundUser.Email)
|
||||
assert.Equal(user.Name, foundUser.Name)
|
||||
|
||||
// Cleanup
|
||||
testRepos.Users.DeleteAll(ctx)
|
||||
}
|
||||
|
||||
func Test_EntUserRepo_GetOneId(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
user := UserFactory()
|
||||
ctx := context.Background()
|
||||
|
||||
userOut, _ := testRepos.Users.Create(ctx, user)
|
||||
foundUser, err := testRepos.Users.GetOneId(ctx, userOut.ID)
|
||||
|
||||
assert.NotNil(foundUser)
|
||||
assert.Nil(err)
|
||||
assert.Equal(user.Email, foundUser.Email)
|
||||
assert.Equal(user.Name, foundUser.Name)
|
||||
|
||||
// Cleanup
|
||||
testRepos.Users.DeleteAll(ctx)
|
||||
}
|
||||
|
||||
func Test_EntUserRepo_GetAll(t *testing.T) {
|
||||
// Setup
|
||||
toCreate := []types.UserCreate{
|
||||
UserFactory(),
|
||||
UserFactory(),
|
||||
UserFactory(),
|
||||
UserFactory(),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
created := []types.UserOut{}
|
||||
|
||||
for _, usr := range toCreate {
|
||||
usrOut, _ := testRepos.Users.Create(ctx, usr)
|
||||
created = append(created, usrOut)
|
||||
}
|
||||
|
||||
// Validate
|
||||
allUsers, err := testRepos.Users.GetAll(ctx)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(created), len(allUsers))
|
||||
|
||||
for _, usr := range created {
|
||||
fmt.Printf("%+v\n", usr)
|
||||
assert.Contains(t, allUsers, usr)
|
||||
}
|
||||
|
||||
for _, usr := range created {
|
||||
testRepos.Users.Delete(ctx, usr.ID)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
testRepos.Users.DeleteAll(ctx)
|
||||
}
|
||||
|
||||
func Test_EntUserRepo_Update(t *testing.T) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
func Test_EntUserRepo_Delete(t *testing.T) {
|
||||
// Create 10 Users
|
||||
for i := 0; i < 10; i++ {
|
||||
user := UserFactory()
|
||||
ctx := context.Background()
|
||||
_, _ = testRepos.Users.Create(ctx, user)
|
||||
}
|
||||
|
||||
// Delete all
|
||||
ctx := context.Background()
|
||||
allUsers, _ := testRepos.Users.GetAll(ctx)
|
||||
|
||||
assert.Greater(t, len(allUsers), 0)
|
||||
testRepos.Users.DeleteAll(ctx)
|
||||
|
||||
allUsers, _ = testRepos.Users.GetAll(ctx)
|
||||
assert.Equal(t, len(allUsers), 0)
|
||||
|
||||
}
|
||||
|
||||
func Test_EntUserRepo_GetSuperusers(t *testing.T) {
|
||||
// Create 10 Users
|
||||
superuser := 0
|
||||
users := 0
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
user := UserFactory()
|
||||
ctx := context.Background()
|
||||
_, _ = testRepos.Users.Create(ctx, user)
|
||||
|
||||
if user.IsSuperuser {
|
||||
superuser++
|
||||
} else {
|
||||
users++
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all
|
||||
ctx := context.Background()
|
||||
|
||||
superUsers, err := testRepos.Users.GetSuperusers(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, usr := range superUsers {
|
||||
assert.True(t, usr.IsSuperuser)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
testRepos.Users.DeleteAll(ctx)
|
||||
}
|
27
backend/internal/repo/users_interface.go
Normal file
27
backend/internal/repo/users_interface.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/git-web-template/backend/internal/types"
|
||||
)
|
||||
|
||||
type UserRepository interface {
|
||||
// GetOneId returns a user by id
|
||||
GetOneId(ctx context.Context, ID uuid.UUID) (types.UserOut, error)
|
||||
// GetOneEmail returns a user by email
|
||||
GetOneEmail(ctx context.Context, email string) (types.UserOut, error)
|
||||
// GetAll returns all users
|
||||
GetAll(ctx context.Context) ([]types.UserOut, error)
|
||||
// Get Super Users
|
||||
GetSuperusers(ctx context.Context) ([]types.UserOut, error)
|
||||
// Create creates a new user
|
||||
Create(ctx context.Context, user types.UserCreate) (types.UserOut, error)
|
||||
// Update updates a user
|
||||
Update(ctx context.Context, ID uuid.UUID, user types.UserUpdate) error
|
||||
// Delete deletes a user
|
||||
Delete(ctx context.Context, ID uuid.UUID) error
|
||||
|
||||
DeleteAll(ctx context.Context) error
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue