Skip to content

Commit 595050b

Browse files
Merge pull request #4721 from linuxfoundation/unicron-port-user-api-to-golang
Port '/v2/user/<uuid>' py API to '/v3/user-compat/<uuid>' go API
2 parents 7200a0d + abac3f1 commit 595050b

File tree

14 files changed

+639
-18
lines changed

14 files changed

+639
-18
lines changed

cla-backend-go/swagger/cla.v1.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,34 @@ paths:
141141
tags:
142142
- users
143143

144+
/user-compat/{userID}:
145+
parameters:
146+
- $ref: "#/parameters/x-request-id"
147+
- $ref: "#/parameters/userPathUuid"
148+
get:
149+
summary: Get user by ID (returns data in the same format as Py V2 API)
150+
security: [ ]
151+
operationId: getUserCompat
152+
responses:
153+
'200':
154+
description: 'Success'
155+
headers:
156+
x-request-id:
157+
type: string
158+
description: The unique request ID value - assigned/set by the API Gateway based on the session
159+
schema:
160+
$ref: '#/definitions/user-compat'
161+
'400':
162+
$ref: '#/responses/invalid-request'
163+
'401':
164+
$ref: '#/responses/unauthorized'
165+
'403':
166+
$ref: '#/responses/forbidden'
167+
'404':
168+
$ref: '#/responses/not-found'
169+
tags:
170+
- users
171+
144172
/users/{userID}:
145173
get:
146174
summary: Get User by ID
@@ -2429,6 +2457,13 @@ paths:
24292457

24302458
# Common parameters
24312459
parameters:
2460+
userPathUuid:
2461+
name: userID
2462+
in: path
2463+
required: true
2464+
description: The user ID (UUID string)
2465+
type: string
2466+
pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}$' # this is any UUID, not only v4
24322467
x-request-id:
24332468
name: X-REQUEST-ID
24342469
description: The unique request ID value - assigned/set by the API Gateway based on the login session
@@ -2677,6 +2712,9 @@ definitions:
26772712
user:
26782713
$ref: './common/user.yaml'
26792714

2715+
user-compat:
2716+
$ref: './common/user-compat.yaml'
2717+
26802718
user-update:
26812719
type: object
26822720
title: User
@@ -2690,6 +2728,8 @@ definitions:
26902728
type: string
26912729
lfEmail:
26922730
type: string
2731+
lfSub:
2732+
type: string
26932733
lfUsername:
26942734
type: string
26952735
companyID:
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright The Linux Foundation and each contributor to CommunityBridge.
2+
# SPDX-License-Identifier: MIT
3+
4+
type: object
5+
x-nullable: false
6+
title: User Model in Py V2 format
7+
description: User Model - in Py V2 - minimal fields needed by FE
8+
properties:
9+
user_id:
10+
description: User's UUID
11+
$ref: './common/properties/uuid.yaml'
12+
x-omitempty: false
13+
x-nullable: false
14+
user_external_id:
15+
description: External user ID
16+
$ref: './common/properties/external-id.yaml'
17+
x-omitempty: false
18+
x-nullable: true
19+
user_emails:
20+
description: Set of user emails (may be empty)
21+
type: array
22+
items:
23+
type: string
24+
format: email
25+
26+
example: ["[email protected]"]
27+
x-omitempty: false
28+
x-nullable: false
29+
user_name:
30+
description: User's name
31+
type: string
32+
example: "Jane Smith"
33+
x-omitempty: false
34+
x-nullable: true
35+
user_company_id:
36+
description: User's company ID
37+
$ref: './common/properties/uuid.yaml'
38+
x-omitempty: false
39+
x-nullable: true
40+
user_github_id:
41+
description: User's GitHub numeric ID
42+
type: string
43+
example: "123456"
44+
x-omitempty: false
45+
x-nullable: true
46+
user_github_username:
47+
description: User's GitHub username
48+
type: string
49+
example: "lukaszgryglicki"
50+
x-omitempty: false
51+
x-nullable: true
52+
user_gitlab_id:
53+
description: User's GitLab numeric ID
54+
type: string
55+
example: "78910"
56+
x-omitempty: false
57+
x-nullable: true
58+
user_gitlab_username:
59+
description: User's GitLab username
60+
type: string
61+
example: "gitlabUser"
62+
x-omitempty: false
63+
x-nullable: true
64+
user_ldap_id:
65+
description: User's LDAP ID
66+
type: string
67+
x-omitempty: false
68+
x-nullable: true
69+
note:
70+
description: Optional admin note
71+
type: string
72+
example: "Pending verification"
73+
x-omitempty: false
74+
x-nullable: true
75+
lf_email:
76+
description: LF email
77+
$ref: './common/properties/email.yaml'
78+
x-omitempty: false
79+
x-nullable: true
80+
lf_username:
81+
description: Linux Foundation username
82+
type: string
83+
example: "janesmith"
84+
x-omitempty: false
85+
x-nullable: true
86+
lf_sub:
87+
type: string
88+
x-omitempty: false
89+
x-nullable: true
90+
is_sanctioned:
91+
type: boolean
92+
description: "Is this user OFAC sanctioned? This field comes from users's company"
93+
example: true
94+
x-omitempty: false
95+
x-nullable: true
96+
version:
97+
type: string
98+
description: the version identifier for this record
99+
example: 'v1'
100+
x-omitempty: false
101+
x-nullable: false

cla-backend-go/swagger/common/user.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ properties:
2222
$ref: './common/properties/email.yaml'
2323
lfUsername:
2424
type: string
25+
lfSub:
26+
type: string
2527
companyID:
2628
$ref: './common/properties/internal-id.yaml'
2729
description: the user's optional company ID

cla-backend-go/users/handlers.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,28 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser
248248
return users.NewSearchUsersOK().WithPayload(userModel)
249249

250250
})
251+
252+
// Get User by ID compat handler
253+
api.UsersGetUserCompatHandler = users.GetUserCompatHandlerFunc(func(params users.GetUserCompatParams) middleware.Responder {
254+
f := logrus.Fields{"" +
255+
"functionName": "users.GetUserCompatHandlerFunc",
256+
"paramsUserID": params.UserID,
257+
}
258+
259+
userModel, err := service.GetUser(params.UserID)
260+
if err != nil {
261+
log.WithFields(f).Warnf("error retrieving user for user_id: %s, error: %+v", params.UserID, err)
262+
return users.NewGetUserCompatBadRequest().WithPayload(errorResponse(err))
263+
}
264+
265+
compatModel, err := service.ConvertUserModelToUserCompatModel(userModel)
266+
if err != nil {
267+
log.WithFields(f).Warnf("error converting user model to compat model for user_id: %s, error: %+v", params.UserID, err)
268+
return users.NewGetUserCompatBadRequest().WithPayload(errorResponse(err))
269+
}
270+
log.WithFields(f).Debugf("returning compat user model %+v from user model %+v for user_id: %s", compatModel, userModel, params.UserID)
271+
return users.NewGetUserCompatOK().WithPayload(compatModel)
272+
})
251273
}
252274

253275
type codedResponse interface {

cla-backend-go/users/models.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type DBUser struct {
2121
UserGitlabUsername string `json:"user_gitlab_username"`
2222
UserCompanyID string `json:"user_company_id"`
2323
Note string `json:"note"`
24+
LFSub string `json:"lf_sub"`
2425
}
2526

2627
type UserEmails struct {

cla-backend-go/users/repository.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ func (repo repository) CreateUser(user *models.User) (*models.User, error) {
129129
}
130130
}
131131

132+
if user.LfSub != "" {
133+
attributes["lf_sub"] = &dynamodb.AttributeValue{
134+
S: aws.String(user.LfSub),
135+
}
136+
}
137+
132138
if len(user.Emails) > 0 {
133139
attributes["user_emails"] = &dynamodb.AttributeValue{
134140
SS: utils.ArrayStringPointer(user.Emails),
@@ -363,6 +369,13 @@ func (repo repository) Save(user *models.UserUpdate) (*models.User, error) {
363369
updateExpression = updateExpression + " #E = :e, "
364370
}
365371

372+
if user.LfSub != "" && oldUserModel.LfSub != user.LfSub {
373+
log.WithFields(f).Debugf("building query - adding lf_sub: %s", user.LfSub)
374+
expressionAttributeNames["#SU"] = aws.String("lf_sub")
375+
expressionAttributeValues[":su"] = &dynamodb.AttributeValue{S: aws.String(user.LfSub)}
376+
updateExpression = updateExpression + " #SU = :su, "
377+
}
378+
366379
if user.UserExternalID != "" && oldUserModel.UserExternalID != user.UserExternalID {
367380
log.WithFields(f).Debugf("building query - adding user_external_id: %s", user.UserExternalID)
368381
expressionAttributeNames["#UE"] = aws.String("user_external_id")
@@ -1313,6 +1326,7 @@ func convertDBUserModel(user DBUser) *models.User {
13131326
UserExternalID: user.UserExternalID,
13141327
Admin: user.Admin,
13151328
LfEmail: strfmt.Email(user.LFEmail),
1329+
LfSub: user.LFSub,
13161330
LfUsername: user.LFUsername,
13171331
DateCreated: user.DateCreated,
13181332
DateModified: user.DateModified,
@@ -1336,6 +1350,7 @@ func buildUserProjection() expression.ProjectionBuilder {
13361350
expression.Name("user_company_id"),
13371351
expression.Name("admin"),
13381352
expression.Name("lf_email"),
1353+
expression.Name("lf_sub"),
13391354
expression.Name("lf_username"),
13401355
expression.Name("user_name"),
13411356
expression.Name("user_emails"),

cla-backend-go/users/service.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package users
66
import (
77
"errors"
88

9+
"github.com/go-openapi/strfmt"
910
"github.com/linuxfoundation/easycla/cla-backend-go/events"
1011
"github.com/linuxfoundation/easycla/cla-backend-go/gen/v1/models"
1112
"github.com/linuxfoundation/easycla/cla-backend-go/user"
@@ -27,6 +28,7 @@ type Service interface {
2728
GetUserByGitLabUsername(gitlabUsername string) (*models.User, error)
2829
SearchUsers(field string, searchTerm string, fullMatch bool) (*models.Users, error)
2930
UpdateUserCompanyID(userID, companyID, note string) error
31+
ConvertUserModelToUserCompatModel(*models.User) (*models.UserCompat, error)
3032
}
3133

3234
type service struct {
@@ -192,3 +194,46 @@ func (s service) SearchUsers(searchField string, searchTerm string, fullMatch bo
192194
func (s service) UpdateUserCompanyID(userID, companyID, note string) error {
193195
return s.repo.UpdateUserCompanyID(userID, companyID, note)
194196
}
197+
198+
func stringPtr(s string) *string {
199+
if s == "" {
200+
return nil
201+
}
202+
return &s
203+
}
204+
205+
func boolPtr(b bool) *bool {
206+
return &b
207+
}
208+
209+
// ConvertUserModelToUserCompatModel converts User to UserCompat
210+
func (s service) ConvertUserModelToUserCompatModel(user *models.User) (*models.UserCompat, error) {
211+
userEmails := make([]strfmt.Email, len(user.Emails))
212+
for i, e := range user.Emails {
213+
userEmails[i] = strfmt.Email(e)
214+
}
215+
216+
var lfEmail *strfmt.Email
217+
if user.LfEmail != "" {
218+
lfEmail = &user.LfEmail
219+
}
220+
221+
return &models.UserCompat{
222+
IsSanctioned: boolPtr(user.IsSanctioned),
223+
LfEmail: lfEmail,
224+
LfSub: stringPtr(user.LfSub),
225+
LfUsername: stringPtr(user.LfUsername),
226+
Note: stringPtr(user.Note),
227+
UserCompanyID: stringPtr(user.CompanyID),
228+
UserEmails: userEmails,
229+
UserExternalID: stringPtr(user.UserExternalID),
230+
UserGithubID: stringPtr(user.GithubID),
231+
UserGithubUsername: stringPtr(user.GithubUsername),
232+
UserGitlabID: stringPtr(user.GitlabID),
233+
UserGitlabUsername: stringPtr(user.GitlabUsername),
234+
UserID: user.UserID,
235+
UserLdapID: nil,
236+
UserName: stringPtr(user.Username),
237+
Version: "v1",
238+
}, nil
239+
}

cla-backend-go/v2/signatures/mock_users/mock_service.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cla-backend/cla/routes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def get_health(request):
100100
#
101101

102102

103+
# LG: This is ported to golang and no longer used in dev (still used in prod)
103104
@hug.get("/user/{user_id}", versions=2)
104105
def get_user(user_id: hug.types.uuid):
105106
"""

0 commit comments

Comments
 (0)