diff --git a/api/controllers/users.go b/api/controllers/users.go index d3d3c73..b74b9da 100644 --- a/api/controllers/users.go +++ b/api/controllers/users.go @@ -153,3 +153,37 @@ func DeleteUser(c *gin.Context) error { }) return nil } + +// user godoc +// @Router /api/users/onboard [post] +// @in header +// @ID onboardUser +// @Tags users +// @Summary Onboard a user using a fingerprint and a video +// @Description Onboard a user using a fingerprint and a videossss +// @Accept json +// @Produce json +// @Param user body models.User true "User data" +// @Success 201 {object} models.User +func OnboardUser(c *gin.Context) error { + var user models.User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(400, gin.H{ + "error": "Invalid user data", + }) + return err + } + + if err := database.OnboardUser(user); err != nil { + c.JSON(500, gin.H{ + "error": "Failed to onboard user", + }) + return err + } + + c.JSON(201, gin.H{ + "message": "User onboarded successfully", + "user": user, + }) + return nil +} diff --git a/api/data/main.go b/api/data/main.go index baa44f8..aa581d9 100644 --- a/api/data/main.go +++ b/api/data/main.go @@ -7,19 +7,21 @@ import ( "github.com/uug-ai/facial-access-control/api/utils" ) + var Users = []models.User{ - {Id: 0, FirstName: "admin", LastName: "admin", Email: "admin@example.com", Password: "admin", Role: "admin", Language: "en"}, - {Id: 1, FirstName: "user", LastName: "user", Email: "user@example.com", Password: "user", Role: "user", Language: "en"}, - {Id: 2, FirstName: "Kilian", LastName: "Smith", Email: "kilian@example.com", Password: "Kilian", Role: "admin", Language: "en"}, - {Id: 3, FirstName: "Cedric", LastName: "Johnson", Email: "cedric@example.com", Password: "Cedric", Role: "admin", Language: "en"}, - {Id: 4, FirstName: "Johann", LastName: "Brown", Email: "johann@example.com", Password: "Johann", Role: "admin", Language: "en"}, - {Id: 5, FirstName: "Romain", LastName: "Davis", Email: "romain@example.com", Password: "Romain", Role: "admin", Language: "en"}, - {Id: 6, FirstName: "Alex", LastName: "Wilson", Email: "alex@example.com", Password: "Alex", Role: "admin", Language: "en"}, - {Id: 7, FirstName: "Mickael", LastName: "Taylor", Email: "mickael@example.com", Password: "Mickael", Role: "admin", Language: "en"}, - {Id: 8, FirstName: "Mickael", LastName: "Thomas", Email: "mickael1@example.com", Password: "Mickael", Role: "admin", Language: "en"}, - {Id: 9, FirstName: "Mickael", LastName: "Robinson", Email: "mickael2@example.com", Password: "Mickael", Role: "admin", Language: "en"}, - {Id: 10, FirstName: "Mickael", LastName: "Clark", Email: "mickael3@example.com", Password: "Mickael", Role: "admin", Language: "en"}, -} + {Id: 0, FirstName: "admin", LastName: "admin", Email: "admin@example.com", Password: "admin", Role: "admin", Language: "en", Status: "pending", VideoPath: ""}, + {Id: 1, FirstName: "user", LastName: "user", Email: "user@example.com", Password: "user", Role: "user", Language: "en", Status: "invited", VideoPath: "/"}, + {Id: 2, FirstName: "Kilian", LastName: "Smith", Email: "kilian@example.com", Password: "Kilian", Role: "admin", Language: "en", Status: "onboarded", VideoPath: "/path/to/kilian-video.mp4"}, + {Id: 3, FirstName: "Cedric", LastName: "Johnson", Email: "cedric@example.com", Password: "Cedric", Role: "admin", Language: "en", Status: "pending", VideoPath: ""}, + {Id: 4, FirstName: "Johann", LastName: "Brown", Email: "johann@example.com", Password: "Johann", Role: "admin", Language: "en", Status: "invited", VideoPath: ""}, + {Id: 5, FirstName: "Romain", LastName: "Davis", Email: "romain@example.com", Password: "Romain", Role: "admin", Language: "en", Status: "onboarded", VideoPath: "/path/to/romain-video.mp4"}, + {Id: 6, FirstName: "Alex", LastName: "Wilson", Email: "alex@example.com", Password: "Alex", Role: "admin", Language: "en", Status: "pending", VideoPath: ""}, + {Id: 7, FirstName: "Mickael", LastName: "Taylor", Email: "mickael@example.com", Password: "Mickael", Role: "admin", Language: "en", Status: "invited", VideoPath: ""}, + {Id: 8, FirstName: "Mickael", LastName: "Thomas", Email: "mickael1@example.com", Password: "Mickael", Role: "admin", Language: "en", Status: "onboarded", VideoPath: "/path/to/mickael1-video.mp4"}, + {Id: 9, FirstName: "Mickael", LastName: "Robinson", Email: "mickael2@example.com", Password: "Mickael", Role: "admin", Language: "en", Status: "pending", VideoPath: ""}, + {Id: 10, FirstName: "Mickael", LastName: "Clark", Email: "mickael3@example.com", Password: "Mickael", Role: "admin", Language: "en", Status: "invited", VideoPath: ""}, + + } var Locations = []models.Location{ {Id: 1, Name: "Location 1", Address: "Address 1", Lat: 1.0, Lng: 1.0}, diff --git a/api/database/file.go b/api/database/file.go index c0b852a..72fe865 100644 --- a/api/database/file.go +++ b/api/database/file.go @@ -71,6 +71,17 @@ func DeleteUserFromFile(id int) error { return errors.New("user not found") } +func OnboardUserToFile(user models.User) error { + if(user.Status == "onboarded") { + return errors.New("user already onboarded") + } + if(user.Status != "invited") { + return errors.New("user must be invited before onboarding") + } + data.Users[user.Id] = user + data.Users[user.Id].Status = "onboarded" + return nil; +} func GetLocationsFromFile() []models.Location { locations := data.Locations diff --git a/api/database/main.go b/api/database/main.go index 3d0e3f2..b3bbd53 100644 --- a/api/database/main.go +++ b/api/database/main.go @@ -19,6 +19,9 @@ func AddUser(user models.User) error { func DeleteUser(id int) error { return DeleteUserFromFile(id) } +func OnboardUser(user models.User) error { + return OnboardUserToFile(user) +} func GetLocations() []models.Location { return GetLocationsFromFile() } @@ -31,3 +34,4 @@ func AddLocation(location models.Location) error { func DeleteLocation(id int) error { return DeleteLocationFromFile(id) } + diff --git a/api/docs/docs.go b/api/docs/docs.go index 7f2d9c4..9acf675 100644 --- a/api/docs/docs.go +++ b/api/docs/docs.go @@ -9,16 +9,7 @@ const docTemplate = `{ "info": { "description": "{{escape .Description}}", "title": "{{.Title}}", - "termsOfService": "https://kerberos.io", - "contact": { - "name": "API Support", - "url": "https://www.kerberos.io", - "email": "support@kerberos.io" - }, - "license": { - "name": "Apache 2.0 - Commons Clause", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, + "contact": {}, "version": "{{.Version}}" }, "host": "{{.Host}}", @@ -240,6 +231,41 @@ const docTemplate = `{ } } }, + "/api/users/onboard": { + "post": { + "description": "Onboard a user using a fingerprint and a videossss", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Onboard a user using a fingerprint and a video", + "operationId": "onboardUser", + "parameters": [ + { + "description": "User data", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.User" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + } + }, "/api/users/{email}": { "get": { "security": [ @@ -407,27 +433,26 @@ const docTemplate = `{ }, "role": { "type": "string" + }, + "status": { + "type": "string" + }, + "videopath": { + "type": "string" } } } - }, - "securityDefinitions": { - "Bearer": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - } } }` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ - Version: "1.0", + Version: "", Host: "", - BasePath: "/", + BasePath: "", Schemes: []string{}, - Title: "Swagger Kerberos Agent API", - Description: "This is the API for using and configuring Kerberos Agent.", + Title: "", + Description: "", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", diff --git a/api/docs/swagger.json b/api/docs/swagger.json index e64a2aa..7f90634 100644 --- a/api/docs/swagger.json +++ b/api/docs/swagger.json @@ -1,21 +1,8 @@ { "swagger": "2.0", "info": { - "description": "This is the API for using and configuring Kerberos Agent.", - "title": "Swagger Kerberos Agent API", - "termsOfService": "https://kerberos.io", - "contact": { - "name": "API Support", - "url": "https://www.kerberos.io", - "email": "support@kerberos.io" - }, - "license": { - "name": "Apache 2.0 - Commons Clause", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0" + "contact": {} }, - "basePath": "/", "paths": { "/api/locations": { "get": { @@ -233,6 +220,41 @@ } } }, + "/api/users/onboard": { + "post": { + "description": "Onboard a user using a fingerprint and a videossss", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Onboard a user using a fingerprint and a video", + "operationId": "onboardUser", + "parameters": [ + { + "description": "User data", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.User" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + } + }, "/api/users/{email}": { "get": { "security": [ @@ -400,15 +422,14 @@ }, "role": { "type": "string" + }, + "status": { + "type": "string" + }, + "videopath": { + "type": "string" } } } - }, - "securityDefinitions": { - "Bearer": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - } } } \ No newline at end of file diff --git a/api/docs/swagger.yaml b/api/docs/swagger.yaml index a698b1a..453835b 100644 --- a/api/docs/swagger.yaml +++ b/api/docs/swagger.yaml @@ -1,4 +1,3 @@ -basePath: / definitions: models.Authentication: properties: @@ -49,19 +48,13 @@ definitions: type: string role: type: string + status: + type: string + videopath: + type: string type: object info: - contact: - email: support@kerberos.io - name: API Support - url: https://www.kerberos.io - description: This is the API for using and configuring Kerberos Agent. - license: - name: Apache 2.0 - Commons Clause - url: http://www.apache.org/licenses/LICENSE-2.0.html - termsOfService: https://kerberos.io - title: Swagger Kerberos Agent API - version: "1.0" + contact: {} paths: /api/locations: get: @@ -256,9 +249,27 @@ paths: summary: Get user tags: - users -securityDefinitions: - Bearer: - in: header - name: Authorization - type: apiKey + /api/users/onboard: + post: + consumes: + - application/json + description: Onboard a user using a fingerprint and a videossss + operationId: onboardUser + parameters: + - description: User data + in: body + name: user + required: true + schema: + $ref: '#/definitions/models.User' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.User' + summary: Onboard a user using a fingerprint and a video + tags: + - users swagger: "2.0" diff --git a/api/go.mod b/api/go.mod index b8ebf3a..a818dc8 100644 --- a/api/go.mod +++ b/api/go.mod @@ -43,7 +43,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.23.0 // indirect + golang.org/x/crypto v0.23.0 // direct golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/api/models/user.go b/api/models/user.go index 8ab94e3..864c52f 100644 --- a/api/models/user.go +++ b/api/models/user.go @@ -2,13 +2,14 @@ package models type User struct { Id int `json:"id" bson:"id"` - FirstName string `json:"firstname" bson:"firstname"` + FirstName string `json:"firstname" bson:"firstname"` LastName string `json:"lastname" bson:"lastname"` Email string `json:"email" bson:"email"` Password string `json:"password" bson:"password"` Role string `json:"role" bson:"role"` Language string `json:"language" bson:"language"` - + Status string `json:"status" bson:"status"` + VideoPath string `json:"videopath" bson:"videopath"` } type Authentication struct { diff --git a/api/routers/http/routes.go b/api/routers/http/routes.go index 4768928..2f3178a 100644 --- a/api/routers/http/routes.go +++ b/api/routers/http/routes.go @@ -12,6 +12,10 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterG { api.POST("/login", authMiddleware.LoginHandler) + api.POST("/users/onboard", func(c *gin.Context) { + controllers.OnboardUser(c) + }) + // Secured endpoints.. api.Use(authMiddleware.MiddlewareFunc()) { @@ -37,6 +41,8 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterG api.DELETE("/users/:id", func(c *gin.Context) { controllers.DeleteUser(c) }) + + // End users // Locations diff --git a/package.json b/package.json index 64fa0f8..5c388ba 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "@uug-ai/ui": "^1.0.34" + "@uug-ai/ui": "^1.0.35" } } diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json index 47be497..48bf56e 100644 --- a/ui/.eslintrc.json +++ b/ui/.eslintrc.json @@ -1,7 +1,3 @@ { - "extends": [ - "next/babel", - "next/core-web-vitals", - "plugin:storybook/recommended" - ] + "extends": ["next", "next/core-web-vitals", "plugin:storybook/recommended"] } diff --git a/ui/.gitignore b/ui/.gitignore index c9a5abc..c12a8cd 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -39,5 +39,4 @@ next-env.d.ts /test-results/ /playwright-report/ /blob-report/ -/playwright/.cache/ -.env*.local \ No newline at end of file +/playwright/.cache/ \ No newline at end of file diff --git a/ui/next.config.mjs b/ui/next.config.mjs index 7f9f6f0..0669a04 100644 --- a/ui/next.config.mjs +++ b/ui/next.config.mjs @@ -1,8 +1,6 @@ - - /** @type {import('next').NextConfig} */ const nextConfig = { - // basePath: "/facial-access-control", <==== required for github pages only. + // basePath: "/facial-access-control", // <==== required for github pages only. // output: "export", // <=== enables static exports reactStrictMode: true, }; diff --git a/ui/src/app/onboarding/page.tsx b/ui/src/app/onboarding/page.tsx index 222886a..400d3c9 100644 --- a/ui/src/app/onboarding/page.tsx +++ b/ui/src/app/onboarding/page.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import { Box, Row, Stack, Gradient, Text, Button, Socials, Icon, VideoCapture } from "../../components/ui"; import FormComponent from "./components/FormComponent"; +import { useAddUserMutation } from "@/lib/services/users/userApi"; import { SubmitHandler } from "react-hook-form"; import * as z from "zod"; @@ -23,6 +24,9 @@ const Onboarding: React.FC = () => { const [isRecording, setIsRecording] = useState(false); const [videoFile, setVideoFile] = useState(null); const [isSubmitted, setIsSubmitted] = useState(false); + const [newUser, setNewUser] = useState(null); + + const [BoardUser, { data, error, isLoading }] = useAddUserMutation(); const handleRecordingComplete = (recordedChunks: Blob[]) => { const videoBlob = new Blob(recordedChunks, { type: "video/webm" }); @@ -30,6 +34,33 @@ const Onboarding: React.FC = () => { console.log("Final video blob:", videoBlob); }; + const handleBoardUser = async (data: FormData) => { + try { + console.log("Data received in handleBoardUser:", data); + + const newUserData = { + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + phoneNumber: data.phoneNumber, + dateOfBirth: data.dateOfBirth, + password: "password", + role: "admin", + installed: true, + language: "en", + }; + + console.log("New User Data:", newUserData); + + const response = await BoardUser(newUserData).unwrap(); + setNewUser(response); + setIsSubmitted(true); + } catch (err) { + console.error(err); + } + }; + + const onSubmit: SubmitHandler = (data) => { console.log("onSubmit called with data:", data); @@ -37,9 +68,9 @@ const Onboarding: React.FC = () => { console.log("Video file exists in onSubmit:", videoFile); data.video = videoFile; - console.log("Form data with video:", data); + console.log("Form data with video:", data.firstName); - setIsSubmitted(true); + handleBoardUser(data); } else { console.error("Video is required in onSubmit"); } @@ -51,8 +82,15 @@ const Onboarding: React.FC = () => { - {isSubmitted ? ( - User registered! + {isSubmitted && newUser ? ( + + User registered! + First Name: {newUser.firstName} + Last Name: {newUser.lastName} + Email: {newUser.email} + Phone Number: {newUser.phoneNumber} + Date of Birth: {newUser.dateOfBirth} + ) : ( )} diff --git a/ui/src/components/SignOutButton.tsx b/ui/src/components/SignOutButton.tsx index 3c81e47..9ab3444 100644 --- a/ui/src/components/SignOutButton.tsx +++ b/ui/src/components/SignOutButton.tsx @@ -2,4 +2,6 @@ import { signOut } from "next-auth/react"; -export default () => ; +const SignOutButton = () => ; + +export default SignOutButton; diff --git a/ui/src/lib/services/users/userApi.ts b/ui/src/lib/services/users/userApi.ts index 12b2d99..18aab33 100644 --- a/ui/src/lib/services/users/userApi.ts +++ b/ui/src/lib/services/users/userApi.ts @@ -63,6 +63,15 @@ export const userApi = createApi({ }), invalidatesTags: ["User"], }), + boardUser: build.mutation({ + query: (user) => ({ + url: "users/onboarding", + method: "POST", + headers: { "Content-Type": "application/json" }, + body: user, + }), + invalidatesTags: ["User"], + }), }), }); diff --git a/yarn.lock b/yarn.lock index ccdd644..4d70e7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1184,10 +1184,10 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@uug-ai/ui@^1.0.34": - version "1.0.34" - resolved "https://registry.yarnpkg.com/@uug-ai/ui/-/ui-1.0.34.tgz#473b67825631993154f667fa00ac9842c26dae1a" - integrity sha512-TojkW5o3QlrctQBnC1B+C16kexfoCbypW4jHFjHW6IrooS/qH/poWnkAAGzj/U2hqGC1LL/Y4nvz2kyagjn5oQ== +"@uug-ai/ui@^1.0.35": + version "1.0.35" + resolved "https://registry.yarnpkg.com/@uug-ai/ui/-/ui-1.0.35.tgz#3daab5491ac3d3a2d33d9b47af1f3d07b9aec2e8" + integrity sha512-/ZNHGNt1NWsYk1XTKmUlLSsSqelWXuo3UiIOCxwE2DNeTTfvboB8/OpXOCgKYpTKtoay5Zs85g+WyE6rrLFkbg== dependencies: "@storybook/addon-essentials" "^8.0.9" "@storybook/addon-interactions" "^8.0.9"