feat(go): add schema for user invites table;

1. Schema Definition for User Invites table.
2. Use the newer table everywhere.
3. Migration code for User Invites table;
This commit is contained in:
VishalDalwadi
2026-04-07 13:27:10 +05:30
parent b6fbfe815b
commit 5bac8e1b07
7 changed files with 124 additions and 49 deletions
+3 -3
View File
@@ -1144,7 +1144,7 @@ func listUsers(w http.ResponseWriter, r *http.Request) {
// @Tags Users
// @Accept json
// @Produce json
// @Param body body models.User true "User details"
// @Param body body schema.User true "User details"
// @Success 200 {object} models.ReturnUser
// @Failure 400 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
@@ -1259,7 +1259,7 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) {
// @Accept json
// @Produce json
// @Param username path string true "Username of the user to create"
// @Param body body models.User true "User details"
// @Param body body schema.User true "User details"
// @Success 200 {object} models.ReturnUser
// @Failure 400 {object} models.ErrorResponse
// @Failure 403 {object} models.ErrorResponse
@@ -1355,7 +1355,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
// @Accept json
// @Produce json
// @Param username path string true "Username of the user to update"
// @Param body body models.User true "User details"
// @Param body body schema.User true "User details"
// @Success 200 {object} models.ReturnUser
// @Failure 400 {object} models.ErrorResponse
// @Failure 403 {object} models.ErrorResponse
+11 -31
View File
@@ -2,11 +2,9 @@ package logic
import (
"context"
"encoding/json"
"errors"
"sort"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/db"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/schema"
@@ -125,41 +123,23 @@ func GetUserMap() (map[string]schema.User, error) {
return userMap, nil
}
func InsertUserInvite(invite models.UserInvite) error {
data, err := json.Marshal(invite)
func GetUserInvite(email string) (*schema.UserInvite, error) {
userInvite := &schema.UserInvite{
Email: email,
}
err := userInvite.GetByEmail(db.WithContext(context.TODO()))
if err != nil {
return err
return nil, err
}
return database.Insert(invite.Email, string(data), database.USER_INVITES_TABLE_NAME)
}
func GetUserInvite(email string) (in models.UserInvite, err error) {
d, err := database.FetchRecord(database.USER_INVITES_TABLE_NAME, email)
if err != nil {
return
}
err = json.Unmarshal([]byte(d), &in)
return
}
func ListUserInvites() ([]models.UserInvite, error) {
invites := []models.UserInvite{}
records, err := database.FetchRecords(database.USER_INVITES_TABLE_NAME)
if err != nil && !database.IsEmptyRecord(err) {
return invites, err
}
for _, record := range records {
in := models.UserInvite{}
err = json.Unmarshal([]byte(record), &in)
if err == nil {
invites = append(invites, in)
}
}
return invites, nil
return userInvite, nil
}
func DeleteUserInvite(email string) error {
return database.DeleteRecord(database.USER_INVITES_TABLE_NAME, email)
userInvite := &schema.UserInvite{
Email: email,
}
return userInvite.DeleteByEmail(db.WithContext(context.TODO()))
}
func ValidateAndApproveUserInvite(email, code string) error {
+30
View File
@@ -9,6 +9,7 @@ import (
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/schema"
"gorm.io/datatypes"
)
func migrateV1_5_2(ctx context.Context) error {
@@ -51,5 +52,34 @@ func migratePendingUsers(ctx context.Context) error {
}
func migrateUserInvites(ctx context.Context) error {
records, err := database.FetchRecords(database.USER_INVITES_TABLE_NAME)
if err != nil && !database.IsEmptyRecord(err) {
return err
}
for _, record := range records {
var userInvite models.UserInvite
err = json.Unmarshal([]byte(record), &userInvite)
if err != nil {
return err
}
_userInvite := &schema.UserInvite{
InviteCode: userInvite.InviteCode,
InviteURL: userInvite.InviteURL,
Email: userInvite.Email,
PlatformRoleID: userInvite.PlatformRoleID,
UserGroups: datatypes.NewJSONType(userInvite.UserGroups),
}
logger.Log(4, fmt.Sprintf("migrating user invite %s", _userInvite.InviteCode))
err = _userInvite.Create(ctx)
if err != nil {
logger.Log(4, fmt.Sprintf("migrating user invite (%s/%s) failed: %v", _userInvite.InviteCode, _userInvite.Email, err))
return err
}
}
return nil
}
+11 -13
View File
@@ -11,7 +11,6 @@ import (
"strings"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/database"
dbtypes "github.com/gravitl/netmaker/db/types"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
@@ -90,7 +89,7 @@ func UserHandlers(r *mux.Router) {
// @Produce json
// @Param email query string true "Invitee email"
// @Param invite_code query string true "Invite code"
// @Param body body models.User true "User signup data"
// @Param body body schema.User true "User signup data"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} models.ErrorResponse
func userInviteSignUp(w http.ResponseWriter, r *http.Request) {
@@ -130,7 +129,7 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) {
return
}
user.UserGroups = datatypes.NewJSONType(in.UserGroups)
user.UserGroups = in.UserGroups
user.PlatformRoleID = schema.UserRoleID(in.PlatformRoleID)
if user.PlatformRoleID == "" {
user.PlatformRoleID = schema.ServiceUser
@@ -240,12 +239,11 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) {
// user exists already, so ignore
continue
}
invite := models.UserInvite{
invite := &schema.UserInvite{
InviteCode: logic.RandomString(8),
Email: inviteeEmail,
PlatformRoleID: inviteReq.PlatformRoleID,
UserGroups: inviteReq.UserGroups,
NetworkRoles: inviteReq.NetworkRoles,
InviteCode: logic.RandomString(8),
UserGroups: datatypes.NewJSONType(inviteReq.UserGroups),
}
frontendURL := strings.TrimSuffix(servercfg.GetFrontendURL(), "/")
if frontendURL == "" {
@@ -266,7 +264,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) {
}
}
invite.InviteURL = u.String()
err = logic.InsertUserInvite(invite)
err = invite.Create(r.Context())
if err != nil {
slog.Error("failed to insert invite for user", "email", invite.Email, "error", err)
}
@@ -287,7 +285,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) {
Origin: schema.Dashboard,
})
// notify user with magic link
go func(invite models.UserInvite) {
go func(invite *schema.UserInvite) {
// Set E-Mail body. You can set plain text or html with text/html
e := email.UserInvitedMail{
@@ -316,7 +314,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) {
// @Success 200 {array} models.UserInvite
// @Failure 500 {object} models.ErrorResponse
func listUserInvites(w http.ResponseWriter, r *http.Request) {
usersInvites, err := logic.ListUserInvites()
usersInvites, err := (&schema.UserInvite{}).ListAll(r.Context())
if err != nil {
logger.Log(0, "failed to fetch users: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -373,7 +371,7 @@ func deleteUserInvite(w http.ResponseWriter, r *http.Request) {
// @Success 200 {object} models.SuccessResponse
// @Failure 500 {object} models.ErrorResponse
func deleteAllUserInvites(w http.ResponseWriter, r *http.Request) {
err := database.DeleteAllRecords(database.USER_INVITES_TABLE_NAME)
err := (&schema.UserInvite{}).DeleteAll(r.Context())
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending user invites "+err.Error()), "internal"))
return
@@ -816,7 +814,7 @@ func listUnAssignedNetUsers(w http.ResponseWriter, r *http.Request) {
// @Produce json
// @Param username query string true "Username"
// @Param network_id query string true "Network ID"
// @Success 200 {object} models.User
// @Success 200 {object} schema.User
// @Failure 400 {object} models.ErrorResponse
func addUsertoNetwork(w http.ResponseWriter, r *http.Request) {
username := r.URL.Query().Get("username")
@@ -872,7 +870,7 @@ func addUsertoNetwork(w http.ResponseWriter, r *http.Request) {
// @Produce json
// @Param username query string true "Username"
// @Param network_id query string true "Network ID"
// @Success 200 {object} models.User
// @Success 200 {object} schema.User
// @Failure 400 {object} models.ErrorResponse
func removeUserfromNetwork(w http.ResponseWriter, r *http.Request) {
username := r.URL.Query().Get("username")
+2 -2
View File
@@ -833,7 +833,7 @@ func IsNetworkRolesValid(networkRoles map[schema.NetworkID]map[schema.UserRoleID
}
// PrepareOauthUserFromInvite - init oauth user before create
func PrepareOauthUserFromInvite(in models.UserInvite) (schema.User, error) {
func PrepareOauthUserFromInvite(in *schema.UserInvite) (schema.User, error) {
var newPass, fetchErr = logic.FetchPassValue("")
if fetchErr != nil {
return schema.User{}, fetchErr
@@ -842,7 +842,7 @@ func PrepareOauthUserFromInvite(in models.UserInvite) (schema.User, error) {
Username: in.Email,
Password: newPass,
}
user.UserGroups = datatypes.NewJSONType(in.UserGroups)
user.UserGroups = in.UserGroups
user.PlatformRoleID = schema.UserRoleID(in.PlatformRoleID)
if user.PlatformRoleID == "" {
user.PlatformRoleID = schema.ServiceUser
+1
View File
@@ -18,5 +18,6 @@ func ListModels() []interface{} {
&JITGrant{},
&Host{},
&PendingUser{},
&UserInvite{},
}
}
+66
View File
@@ -0,0 +1,66 @@
package schema
import (
"context"
"errors"
"github.com/google/uuid"
"github.com/gravitl/netmaker/db"
dbtypes "github.com/gravitl/netmaker/db/types"
"gorm.io/datatypes"
)
var (
ErrUserInviteIdentifiersNotProvided = errors.New("user invite identifiers not provided")
)
type UserInvite struct {
ID string `gorm:"primaryKey" json:"id"`
InviteCode string `gorm:"unique" json:"invite_code"`
InviteURL string `json:"invite_url"`
Email string `json:"email"`
PlatformRoleID string `json:"platform_role_id"`
UserGroups datatypes.JSONType[map[UserGroupID]struct{}] `json:"user_group_ids"`
}
func (u *UserInvite) TableName() string {
return "user_invites_v1"
}
func (u *UserInvite) Create(ctx context.Context) error {
if u.ID == "" {
u.ID = uuid.NewString()
}
return db.FromContext(ctx).Model(&UserInvite{}).Create(u).Error
}
func (u *UserInvite) GetByEmail(ctx context.Context) error {
return db.FromContext(ctx).Model(&UserInvite{}).
Where("email = ?", u.Email).
First(u).
Error
}
func (u *UserInvite) ListAll(ctx context.Context, options ...dbtypes.Option) ([]UserInvite, error) {
var userInvites []UserInvite
query := db.FromContext(ctx).Model(&UserInvite{})
for _, option := range options {
query = option(query)
}
err := query.Find(&userInvites).Error
return userInvites, err
}
func (u *UserInvite) DeleteByEmail(ctx context.Context) error {
return db.FromContext(ctx).Model(&UserInvite{}).
Where("email = ?", u.Email).
Delete(u).
Error
}
func (u *UserInvite) DeleteAll(ctx context.Context) error {
return db.FromContext(ctx).Exec("DELETE FROM user_invites_v1").Error
}