@@ -6,10 +6,8 @@ import (
66 "errors"
77 "time"
88
9- "github.com/ianlancetaylor/jsonschema"
109 "github.com/zitadel/nextgen/internal/crypto"
1110 "github.com/zitadel/nextgen/internal/domain"
12- "github.com/zitadel/nextgen/internal/maputil"
1311 "github.com/zitadel/nextgen/internal/storage/database"
1412)
1513
@@ -21,6 +19,11 @@ type CreateUserInput struct {
2119 User map [string ]any
2220}
2321
22+ type UserAction interface {
23+ Prepare (ctx context.Context , db database.QueryExecutor ) error
24+ Apply (ctx context.Context , db database.QueryExecutor ) error
25+ }
26+
2427type SetPasswordInput struct {
2528 ProjectID string
2629 UserID string
@@ -67,63 +70,53 @@ func NewUserService(
6770 }
6871}
6972
70- func (s * UserService ) CreateUser (ctx context.Context , input CreateUserInput ) (_ map [string ]any , err error ) {
71- // FETCH SCHEMA
72-
73- schemaURL , ok := maputil .Get [string ](input .User , "$schema" )
74- if ! ok {
75- return nil , domain .ErrUserInvalid ().
76- WithDetails ("No $schema provided for the user. A schema must be provided when creating a new user. Against this schema, the user will be validated" )
77- }
78-
79- schemaEntity , err := s .schemaRepo .GetByID (ctx , s .pool , input .ProjectID , schemaURL )
80- if err != nil {
81- if _ , ok := errors.AsType [* database.NoRowFoundError ](err ); ok {
82- return nil , domain .ErrUserInvalid ().WithDetails ("$schema is not known to the system. First create a schema, then create users." )
73+ func (s * UserService ) ApplyActions (ctx context.Context , actions ... UserAction ) (err error ) {
74+ for _ , action := range actions {
75+ err = action .Prepare (ctx , s .pool )
76+ if err != nil {
77+ return err
8378 }
84- return nil , domain .ErrInternal (err ).WithMessage ("failed to get schema from database" )
8579 }
8680
87- // VALIDATE USER
88-
89- var schema jsonschema.Schema
90- err = json .Unmarshal (schemaEntity .Schema , & schema )
81+ tx , err := s .pool .Begin (ctx , nil )
9182 if err != nil {
92- return nil , domain .ErrInternal (err ).WithMessage ("failed to unmarshal json schema " )
83+ return domain .ErrInternal (err ).WithMessage ("failed to create transaction " )
9384 }
85+ defer func () {
86+ if err != nil {
87+ _ = tx .Rollback (ctx )
88+ }
89+ }()
9490
95- err = schema . Validate ( input . User )
96- if err != nil {
97- return nil , domain . ErrUserInvalid ().
98- WithParent ( err ).
99- WithMessage ( "user is not valid according to schema" )
91+ for _ , action := range actions {
92+ err = action . Apply ( ctx , tx )
93+ if err != nil {
94+ return err
95+ }
10096 }
10197
102- // PREPARE DOMAIN USER
103-
104- var schemaMap map [string ]any
105- err = json .Unmarshal (schemaEntity .Schema , & schemaMap )
98+ err = tx .Commit (ctx )
10699 if err != nil {
107- return nil , domain .ErrInternal (err ).WithMessage ("failed to unmarshal schema map " )
100+ return domain .ErrInternal (err ).WithMessage ("failed to commit transaction " )
108101 }
102+ return nil
103+ }
104+
105+ func (s * UserService ) CreateUser (ctx context.Context , input CreateUserInput ) (_ map [string ]any , err error ) {
106+ // CreateUser does not need a transaction, so we don't wrap it in an `ApplyActions` call
109107
110- createUser , err := domain .NewCreateUser (input .ProjectID , input .TeamID , schemaURL , input .User , schemaMap )
108+ action := NewCreateUserAction (input , s .userRepo , s .schemaRepo )
109+ err = action .Prepare (ctx , s .pool )
111110 if err != nil {
112111 return nil , err
113112 }
114113
115- // SAVE USER
116-
117- err = s .userRepo .Create (ctx , s .pool , createUser )
114+ err = action .Apply (ctx , s .pool )
118115 if err != nil {
119- if _ , ok := errors.AsType [* database.UniqueError ](err ); ok {
120- return nil , domain .ErrUserAlreadyExists ().WithParent (err )
121- }
122- return nil , domain .ErrInternal (err ).WithMessage ("failed to create user in the database" )
116+ return nil , err
123117 }
124118
125- input .User ["id" ] = createUser .ID
126- return input .User , nil
119+ return action .User , nil
127120}
128121
129122func (s * UserService ) GetUserByID (ctx context.Context , input GetUserInput ) (map [string ]any , error ) {
@@ -145,45 +138,8 @@ func (s *UserService) GetUserByID(ctx context.Context, input GetUserInput) (map[
145138}
146139
147140func (s * UserService ) SetPassword (ctx context.Context , input SetPasswordInput ) (err error ) {
148- hash , err := domain .HashPassword (input .Password , s .hasher )
149- if err != nil {
150- return err
151- }
152-
153- tx , err := s .pool .Begin (ctx , nil )
154- if err != nil {
155- return domain .ErrInternal (err ).WithMessage ("failed to create transaction" )
156- }
157- defer func () {
158- if err != nil {
159- _ = tx .Rollback (ctx )
160- }
161- }()
162-
163- err = s .passwordRepo .DeleteByUserID (ctx , tx , input .ProjectID , input .UserID )
164- if err != nil {
165- return domain .ErrInternal (err ).WithMessage ("failed to remove old password from database" )
166- }
167-
168- err = s .passwordRepo .Create (ctx , tx , & domain.CreateUserPassword {
169- ProjectID : input .ProjectID ,
170- UserID : input .UserID ,
171- EncodedHash : hash ,
172- ChangeRequired : input .IsPasswordChangeRequired ,
173- VerificationID : nil , // TODO what should I do with this?
174- })
175- if err != nil {
176- if _ , ok := errors.AsType [* database.ForeignKeyError ](err ); ok {
177- return domain .ErrUserNotFound ()
178- }
179- return domain .ErrInternal (err ).WithMessage ("failed to set initial password" )
180- }
181-
182- err = tx .Commit (ctx )
183- if err != nil {
184- return domain .ErrInternal (err ).WithMessage ("failed to commit transaction while setting password" )
185- }
186- return nil
141+ action := NewSetUserPasswordAction (input , s .hasher , s .passwordRepo )
142+ return s .ApplyActions (ctx , action )
187143}
188144
189145func (s * UserService ) GetMyUser (ctx context.Context , input GetMyUserInput ) ([]byte , error ) {
@@ -213,3 +169,98 @@ func (s *UserService) GetMyUser(ctx context.Context, input GetMyUserInput) ([]by
213169
214170 return userbs , nil
215171}
172+
173+ // ---- CreateUser opts -------------------------------------------------------------
174+
175+ type CreateUserAction struct {
176+ CreateUserInput
177+
178+ userRepo domain.UserRepository
179+ schemaRepo domain.JSONSchemaRepository
180+
181+ createUser * domain.CreateUser
182+ }
183+
184+ func NewCreateUserAction (input CreateUserInput , userRepo domain.UserRepository , schemaRepo domain.JSONSchemaRepository ) * CreateUserAction {
185+ return & CreateUserAction {
186+ CreateUserInput : input ,
187+ userRepo : userRepo ,
188+ schemaRepo : schemaRepo ,
189+ }
190+ }
191+
192+ func (o * CreateUserAction ) Prepare (ctx context.Context , db database.QueryExecutor ) error {
193+ schemaURL , err := domain .SchemaFromUserMap (o .User )
194+ if err != nil {
195+ return err
196+ }
197+
198+ schemaEntity , err := o .schemaRepo .GetByID (ctx , db , o .ProjectID , schemaURL )
199+ if err != nil {
200+ if _ , ok := errors.AsType [* database.NoRowFoundError ](err ); ok {
201+ return domain .ErrUserInvalid ().WithDetails ("$schema is not known to the system. First create a schema, then create users." )
202+ }
203+ return domain .ErrInternal (err ).WithMessage ("failed to get schema from database" )
204+ }
205+
206+ o .createUser , err = domain .NewCreateUser (o .ProjectID , o .TeamID , schemaEntity .Schema , o .User )
207+ if err != nil {
208+ return err
209+ }
210+
211+ o .User ["id" ] = o .createUser .ID
212+ return nil
213+ }
214+ func (o * CreateUserAction ) Apply (ctx context.Context , db database.QueryExecutor ) error {
215+ err := o .userRepo .Create (ctx , db , o .createUser )
216+ if err != nil {
217+ if _ , ok := errors.AsType [* database.UniqueError ](err ); ok {
218+ return domain .ErrUserAlreadyExists ().WithParent (err )
219+ }
220+ return domain .ErrInternal (err ).WithMessage ("failed to create user in the database" )
221+ }
222+
223+ return nil
224+ }
225+
226+ type SetPasswordUserAction struct {
227+ SetPasswordInput
228+
229+ hasher crypto.Hasher
230+ passwordRepo domain.UserPasswordRepository
231+
232+ hash string
233+ }
234+
235+ func NewSetUserPasswordAction (input SetPasswordInput , hasher crypto.Hasher , passwordRepo domain.UserPasswordRepository ) * SetPasswordUserAction {
236+ return & SetPasswordUserAction {
237+ SetPasswordInput : input ,
238+ hasher : hasher ,
239+ passwordRepo : passwordRepo ,
240+ }
241+ }
242+ func (o * SetPasswordUserAction ) Prepare (_ context.Context , _ database.QueryExecutor ) (err error ) {
243+ o .hash , err = domain .HashPassword (o .Password , o .hasher )
244+ return err
245+ }
246+
247+ func (o * SetPasswordUserAction ) Apply (ctx context.Context , db database.QueryExecutor ) error {
248+ err := o .passwordRepo .DeleteByUserID (ctx , db , o .ProjectID , o .UserID )
249+ if err != nil {
250+ return domain .ErrInternal (err ).WithMessage ("failed to remove old password from database" )
251+ }
252+
253+ err = o .passwordRepo .Create (ctx , db , & domain.CreateUserPassword {
254+ ProjectID : o .ProjectID ,
255+ UserID : o .UserID ,
256+ EncodedHash : o .hash ,
257+ ChangeRequired : o .IsPasswordChangeRequired ,
258+ })
259+ if err != nil {
260+ if _ , ok := errors.AsType [* database.ForeignKeyError ](err ); ok {
261+ return domain .ErrUserNotFound ()
262+ }
263+ return domain .ErrInternal (err ).WithMessage ("failed to set initial password" )
264+ }
265+ return nil
266+ }
0 commit comments