user.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. package handlers
  2. import (
  3. "time"
  4. "git.linuxforward.com/byom/byom-core/common"
  5. "git.linuxforward.com/byom/byom-core/hook"
  6. "git.linuxforward.com/byom/byom-core/jwtutils"
  7. "git.linuxforward.com/byom/byom-core/logger"
  8. "git.linuxforward.com/byom/byom-core/smtp"
  9. "git.linuxforward.com/byom/byom-core/store"
  10. "github.com/gin-gonic/gin"
  11. "github.com/golang-jwt/jwt/v5"
  12. "github.com/google/uuid"
  13. "github.com/sirupsen/logrus"
  14. "golang.org/x/crypto/bcrypt"
  15. )
  16. type UserHandler struct {
  17. store *store.DataStore
  18. emailService smtp.EmailService
  19. jwtService *jwtutils.Service
  20. logger *logrus.Entry
  21. hook *hook.HookClient
  22. }
  23. func NewUserHandler(db *store.DataStore, emailSvc smtp.EmailService, jwtSvc *jwtutils.Service, hook *hook.HookClient) *UserHandler {
  24. return &UserHandler{
  25. store: db,
  26. emailService: emailSvc,
  27. jwtService: jwtSvc,
  28. logger: logger.NewLogger("user-handler"),
  29. hook: hook,
  30. }
  31. }
  32. // Auth
  33. func (h *UserHandler) Login(c *gin.Context) {
  34. var req common.AuthLoginRequest
  35. var resp common.AuthLoginResponse
  36. if err := c.ShouldBindJSON(&req); err != nil {
  37. c.JSON(400, gin.H{"error": "Invalid request"})
  38. return
  39. }
  40. user, err := h.store.GetUserByEmail(c, req.Email)
  41. if err != nil {
  42. c.JSON(400, gin.H{"error": "Invalid email or password"})
  43. return
  44. }
  45. err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
  46. if err != nil {
  47. c.JSON(400, gin.H{"error": "Invalid email or password"})
  48. return
  49. }
  50. token, err := h.jwtService.GenerateToken(
  51. user.Email,
  52. user.Role,
  53. )
  54. if err != nil {
  55. c.JSON(500, gin.H{"error": "Failed to generate token"})
  56. return
  57. }
  58. resp.Token = token
  59. c.JSON(200, resp)
  60. }
  61. // Workspace Owner Management
  62. func (h *UserHandler) InitOwner(c *gin.Context) {
  63. var req common.WorkspaceOwnerInitRequest
  64. if err := c.ShouldBindJSON(&req); err != nil {
  65. c.JSON(400, gin.H{"error": "Invalid request"})
  66. return
  67. }
  68. user := &common.User{
  69. ID: uuid.New(),
  70. Email: req.Email,
  71. Name: req.Name,
  72. Role: "owner",
  73. Status: "pending",
  74. CreatedAt: time.Now(),
  75. UpdatedAt: time.Now(),
  76. }
  77. if err := h.store.CreateUser(c, user); err != nil {
  78. c.JSON(500, gin.H{"error": "Failed to create user"})
  79. return
  80. }
  81. c.JSON(201, user)
  82. }
  83. func (h *UserHandler) CreateOwner(c *gin.Context) {
  84. var req common.WorkspaceOwnerCreateRequest
  85. if err := c.ShouldBindJSON(&req); err != nil {
  86. c.JSON(400, gin.H{"error": "Invalid request"})
  87. return
  88. }
  89. //check if the received email is already the one from the owner
  90. tmpOwner, err := h.store.GetUserByEmail(c, req.Email)
  91. if err != nil {
  92. c.JSON(500, gin.H{"error": "Failed to get user"})
  93. return
  94. }
  95. if tmpOwner.Email != req.Email {
  96. c.JSON(400, gin.H{"error": "Email does not match the owner"})
  97. return
  98. }
  99. hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
  100. user := &common.User{
  101. ID: tmpOwner.ID,
  102. Email: tmpOwner.Email,
  103. Name: tmpOwner.Name,
  104. PhoneNumber: tmpOwner.PhoneNumber,
  105. Password: string(hashedPassword),
  106. PasswordCreated: true,
  107. Role: tmpOwner.Role,
  108. Status: "active",
  109. CreatedAt: tmpOwner.CreatedAt,
  110. UpdatedAt: time.Now(),
  111. }
  112. if err := h.store.UpdateUser(c, user); err != nil {
  113. c.JSON(500, gin.H{"error": "Failed to create user"})
  114. return
  115. }
  116. user.Password = ""
  117. c.JSON(201, user)
  118. }
  119. // Invitation Management
  120. func (h *UserHandler) AcceptInvitation(c *gin.Context) {
  121. var req common.InvitationAcceptRequest
  122. if err := c.ShouldBindJSON(&req); err != nil {
  123. c.JSON(400, gin.H{"error": "Invalid request"})
  124. return
  125. }
  126. invite, err := h.store.GetInvite(c, req.Token)
  127. if err != nil {
  128. c.JSON(400, gin.H{"error": "Invalid invitation"})
  129. return
  130. }
  131. tx := h.store.BeginTx()
  132. hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
  133. user := &common.User{
  134. ID: uuid.New(),
  135. Email: req.Email,
  136. Name: req.Name,
  137. PhoneNumber: req.PhoneNumber,
  138. Password: string(hashedPassword),
  139. PasswordCreated: true,
  140. Role: invite.Role,
  141. Status: "active",
  142. CreatedAt: time.Now(),
  143. UpdatedAt: time.Now(),
  144. }
  145. if err := h.store.CreateUserTx(c, tx, user); err != nil {
  146. tx.Rollback()
  147. c.JSON(500, gin.H{"error": "Failed to create user"})
  148. return
  149. }
  150. //add user to workspace
  151. if err := h.store.AddUserToWorkspaceTx(c, tx, invite.Role, user.ID, invite.WorkspaceID); err != nil {
  152. tx.Rollback()
  153. c.JSON(500, gin.H{"error": "Failed to add user to workspace"})
  154. return
  155. }
  156. //update invite status
  157. invite.Status = "accepted"
  158. if err := h.store.UpdateInviteStatusTx(c, tx, invite); err != nil {
  159. tx.Rollback()
  160. c.JSON(500, gin.H{"error": "Failed to update invitation"})
  161. return
  162. }
  163. if err := tx.Commit().Error; err != nil {
  164. c.JSON(500, gin.H{"error": "Failed to complete registration"})
  165. return
  166. }
  167. user.Password = ""
  168. resp := common.InvitationAcceptResponse{
  169. User: *user,
  170. WorkspaceID: invite.WorkspaceID,
  171. }
  172. c.JSON(201, resp)
  173. }
  174. func (h *UserHandler) ValidateInvitation(c *gin.Context) {
  175. //request token from query
  176. token := c.Query("token")
  177. h.logger.WithField("token", token).Info("Validating invitation")
  178. inv, err := h.store.GetInvite(c, token)
  179. if err != nil {
  180. c.JSON(400, gin.H{"error": "Invalid invitation"})
  181. return
  182. }
  183. //validate token
  184. _, err = h.jwtService.ValidateToken(token)
  185. if err != nil {
  186. if err == jwt.ErrTokenExpired {
  187. //update invite status
  188. inv.Status = "expired"
  189. if err := h.store.UpdateInvite(c, inv); err != nil {
  190. c.JSON(500, gin.H{"error": "Failed to update invitation"})
  191. return
  192. }
  193. c.JSON(400, gin.H{"error": "Invalid token"})
  194. return
  195. }
  196. }
  197. response := common.InvitationValidateResponse{
  198. Valid: true,
  199. WorkspaceID: inv.WorkspaceID,
  200. Email: inv.Email,
  201. }
  202. c.JSON(200, response)
  203. }
  204. func (h *UserHandler) CreateInvitation(c *gin.Context) {
  205. var req common.InvitationCreateRequest
  206. if err := c.ShouldBindJSON(&req); err != nil {
  207. c.JSON(400, gin.H{"error": "Invalid request"})
  208. return
  209. }
  210. //check if user exists
  211. _, err := h.store.GetUserByEmail(c, req.Email)
  212. if err == nil {
  213. c.JSON(400, gin.H{"error": "User already exists"})
  214. return
  215. }
  216. //generate jwt token
  217. token, err := h.jwtService.GenerateToken(
  218. req.Email,
  219. req.Role,
  220. )
  221. if err != nil {
  222. c.JSON(500, gin.H{"error": "Failed to generate token"})
  223. return
  224. }
  225. //store token in db
  226. invite := &common.Invite{
  227. ID: uuid.New(),
  228. Email: req.Email,
  229. Role: req.Role,
  230. WorkspaceID: req.WorkspaceID,
  231. Token: token,
  232. Status: "pending",
  233. ExpiresAt: time.Now().Add(24 * time.Hour),
  234. CreatedAt: time.Now(),
  235. UpdatedAt: time.Now(),
  236. }
  237. //return invite
  238. if err := h.store.CreateInvite(c, invite); err != nil {
  239. c.JSON(500, gin.H{"error": "Failed to create invitation"})
  240. return
  241. }
  242. //send email with token
  243. if err := h.emailService.SendInviteEmail(req.Email, token, req.WorkspaceID.String()); err != nil {
  244. c.JSON(500, gin.H{"error": "Failed to send email"})
  245. return
  246. }
  247. c.JSON(201, invite)
  248. }
  249. // User Management
  250. func (h *UserHandler) GetCurrent(c *gin.Context) {
  251. // Get claims from context that were set by middleware
  252. claimsValue, exists := c.Get("claims")
  253. if !exists {
  254. c.JSON(500, gin.H{"error": "Failed to get user claims"})
  255. return
  256. }
  257. claims := claimsValue.(jwt.MapClaims)
  258. if claims == nil {
  259. c.JSON(500, gin.H{"error": "Failed to get user claims"})
  260. return
  261. }
  262. email := h.jwtService.ExtractEmailFromClaims(claims)
  263. if email == "" {
  264. c.JSON(500, gin.H{"error": "Failed to get user email"})
  265. return
  266. }
  267. user, err := h.store.GetUserByEmail(c, email)
  268. if err != nil {
  269. c.JSON(500, gin.H{"error": "Failed to get user"})
  270. return
  271. }
  272. workspaces, err := h.store.GetWorkspacesByUserID(c, user.ID)
  273. if err != nil {
  274. c.JSON(500, gin.H{"error": "Failed to get user workspace role"})
  275. return
  276. }
  277. resp := common.UserResponse{
  278. User: *user,
  279. Workspaces: workspaces,
  280. }
  281. c.JSON(200, resp)
  282. }
  283. func (h *UserHandler) UpdateCurrent(c *gin.Context) {
  284. var req common.UserUpdateRequest
  285. if err := c.ShouldBindJSON(&req); err != nil {
  286. c.JSON(400, gin.H{"error": "Invalid request"})
  287. return
  288. }
  289. // Implementation
  290. }
  291. // Workspace Member Management
  292. func (h *UserHandler) AddMember(c *gin.Context) {
  293. var req common.WorkspaceMemberAddRequest
  294. if err := c.ShouldBindJSON(&req); err != nil {
  295. c.JSON(400, gin.H{"error": "Invalid request"})
  296. return
  297. }
  298. //retrieve user from token
  299. claimsValue, exists := c.Get("claims")
  300. if !exists {
  301. c.JSON(500, gin.H{"error": "Failed to get user claims"})
  302. return
  303. }
  304. claims := claimsValue.(jwt.MapClaims)
  305. if claims == nil {
  306. c.JSON(500, gin.H{"error": "Failed to get user claims"})
  307. return
  308. }
  309. email := h.jwtService.ExtractEmailFromClaims(claims)
  310. if email == "" {
  311. c.JSON(500, gin.H{"error": "Failed to get user email"})
  312. return
  313. }
  314. user, err := h.store.GetUserByEmail(c, email)
  315. if err != nil {
  316. c.JSON(500, gin.H{"error": "Failed to get user"})
  317. return
  318. }
  319. //add user to workspace
  320. if err := h.store.AddUserToWorkspace(c, user.ID, req.WorkspaceID, req.Role); err != nil {
  321. c.JSON(500, gin.H{"error": "Failed to add user to workspace"})
  322. return
  323. }
  324. c.JSON(200, gin.H{"message": "User added to workspace"})
  325. }
  326. func (h *UserHandler) CancelInvitation(c *gin.Context) {
  327. // Implementation
  328. }