Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User struct + CRUD operations #7

Merged
merged 8 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions authentication/user_accounts.go → authentication/auth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ import (

"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"slugquest.com/backend/crud"
)

const FRONTEND_HOST string = "localhost:5185"

// TODO: make this more elegant with Gin sessions or something
var Curr_user_id string = "hi"

// Checks if user is authenticated before redirecting to next page
func IsAuthenticated(c *gin.Context) {
// Auth token: for direct calls to this endpoint
Expand Down Expand Up @@ -46,6 +44,8 @@ func LoginHandler(auth *Authenticator) gin.HandlerFunc {

// Save the state inside the session.
session := sessions.Default(c)
session.Clear()

session.Set("state", state)
if err := session.Save(); err != nil {
c.String(http.StatusInternalServerError, err.Error())
Expand Down Expand Up @@ -130,20 +130,79 @@ func CallbackHandler(auth *Authenticator) gin.HandlerFunc {
return
}

// Extract Auth0's provided user vid
if profile["sub"] == nil {
var userInfoStruct *crud.User = getUserInfo(c)
if userInfoStruct == nil {
c.String(http.StatusInternalServerError, "Couldn't retrieve user profile.")
return
}

session.Set("user_profile", userInfoStruct)
if err := session.Save(); err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
user_id := profile["sub"].(string)[len("auth0|"):]
Curr_user_id = user_id

// Redirect to logged in page.
c.Redirect(http.StatusTemporaryRedirect, "http://"+FRONTEND_HOST+"/loggedin")
}
}

// Displays user profile from the current session
func getUserInfo(c *gin.Context) *crud.User {
session := sessions.Default(c)

profile, ok := session.Get("profile").(map[string]interface{})
if !ok || profile == nil {
c.String(http.StatusInternalServerError, "Couldn't retrieve user profile.")
return nil
}

// No user id? No SlugQuest.
sesUID, ok := profile["sub"].(string)
if !ok {
c.String(http.StatusInternalServerError, "Couldn't resolve user id.")
return nil
}

sesUsername, ok := profile["name"].(string)
if !ok {
c.String(http.StatusInternalServerError, "Couldn't resolve username.")
return nil
}

sesPFP, ok := profile["picture"].(string)
if !ok {
c.String(http.StatusInternalServerError, "Couldn't resolve profile picture URL.")
return nil
}

// Check if user exists in our DB
user, found, err := crud.GetUser(sesUID)
if err != nil {
c.String(http.StatusInternalServerError, "Couldn't fetch user.")
return nil
}

if !found {
// Need to populate and add a new user
user = crud.User{
UserID: sesUID,
Username: sesUsername,
Picture: sesPFP,
Points: 0,
BossId: 0,
}

added, err := crud.AddUser(user)
if err != nil || !added {
c.String(http.StatusInternalServerError, "Couldn't register user into our records.")
return nil
}
}

return &user
}

// Sends user profile from the current session as JSON
// func UserProfileHandler(c *gin.Context) {
// session := sessions.Default(c)
// profile := session.Get("profile")
Expand Down
4 changes: 2 additions & 2 deletions authentication/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ type Authenticator struct {
oauth2.Config
}

// New instantiates the *Authenticator.
func New() (*Authenticator, error) {
// NewAuthenticator instantiates the *Authenticator.
func NewAuthenticator() (*Authenticator, error) {
provider, err := oidc.NewProvider(
context.Background(),
"https://"+os.Getenv("AUTH0_DOMAIN")+"/",
Expand Down
108 changes: 108 additions & 0 deletions crud/db_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ type TaskPreview struct {
IsAllDay bool
}

type User struct {
UserID string // Not known to user, do not expose
Username string // Set by user, can be exposed
Picture string // A0 stores their profile pics as URLs
Points int
BossId int
}

var DB *sqlx.DB

func LoadDumbData() error {
Expand Down Expand Up @@ -290,3 +298,103 @@ func GetTaskId(Tid int) (Task, bool, error) {
print(counter)
return taskit, counter == 1, err
}

// Find user by UserID
func GetUser(uid string) (User, bool, error) {
rows, err := DB.Query("SELECT * FROM UserTable WHERE UserID=?;", uid)
var user User
if err != nil {
fmt.Println(err)
return user, false, err
}

counter := 0
for rows.Next() {
counter += 1
rows.Scan(&user.UserID, &user.Points, &user.BossId)
}
rows.Close()

return user, counter == 1, err
}

// Add user into DB
func AddUser(u User) (bool, error) {
tx, err := DB.Beginx()
if err != nil {
fmt.Printf("AddUser(): breaky 1: %v", err)
return false, err
}
defer tx.Rollback() // aborrt transaction if error

stmt, err := tx.Preparex("INSERT INTO UserTable (UserID, Points, Bossid) VALUES (?, ?, ?)")
if err != nil {
fmt.Printf("AddUser(): breaky 2: %v", err)
return false, err
}

defer stmt.Close() //defer the closing of SQL statement to ensure it Closes once the function completes
_, err = stmt.Exec(u.UserID, u.Points, u.BossId)
if err != nil {
fmt.Printf("AddUser(): breaky 3: %v", err)
return false, err
}

tx.Commit() //commit transaction to database

return true, nil
}

// Edit a user by supplying new values
func EditUser(u User, uid string) (bool, error) {
tx, err := DB.Beginx()
if err != nil {
return false, err
}
defer tx.Rollback() // aborrt transaction if error

stmt, err := tx.Preparex(`
UPDATE UserTable
SET UserID = ?, Points = ?, Bossid = ?
WHERE UserID = ?
`)
if err != nil {
return false, err
}

defer stmt.Close()

_, err = stmt.Exec(u.UserID, u.Points, u.BossId, uid)
if err != nil {
return false, err
}

tx.Commit()

return true, nil
}

func DeleteUser(uid string) (bool, error) {
tx, err := DB.Beginx()
if err != nil {
return false, err
}
defer tx.Rollback() // aborrt transaction if error

stmt, err := tx.Preparex("DELETE FROM UserTable WHERE UserID = ?")
if err != nil {
fmt.Println("DeleteUser: breaky 1")
return false, err
}
defer stmt.Close()

_, err = stmt.Exec(uid)
if err != nil {
fmt.Println("DeleteUser: breaky 2")
return false, err
}

tx.Commit()

return true, nil
}
8 changes: 5 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"fmt"
"log"

envfuncs "github.com/joho/godotenv"
Expand All @@ -16,12 +15,14 @@ func main() {
// Load .env
if env_err := envfuncs.Load(); env_err != nil {
log.Fatalf("Error loading the .env file: %v", env_err)
return
}

// Create new authenticator to pass to the router
auth, auth_err := authentication.New()
auth, auth_err := authentication.NewAuthenticator()
if auth_err != nil {
log.Fatalf("Failed to initialize the authenticator: %v", auth_err)
return
}
router := CreateRouter(auth)

Expand All @@ -34,10 +35,11 @@ func main() {
dummy_err := crud.LoadDumbData()
if dummy_err != nil {
log.Fatalf("error loaduing dumb data: %v", dummy_err)
return
}
utest := testing.RunAllTests()
if !utest {
fmt.Println("unit test failure")
log.Fatal("unit test failure")
return
}

Expand Down
19 changes: 9 additions & 10 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func CreateRouter(auth *authentication.Authenticator) *gin.Engine {
// To store custom types in our cookies,
// we must first register them using gob.Register
gob.Register(map[string]interface{}{})
gob.Register(crud.User{})

// Set up cookie store for the user session
store := cookie.NewStore([]byte("secret"))
Expand Down Expand Up @@ -134,16 +135,14 @@ func deleteTask(c *gin.Context) {

// Returns a list of all tasks of the current user
func getAllUserTasks(c *gin.Context) {
// TODO: ill be fixing this
// user_id stored as a variable within the session
// uid := c.GetString("user_id")
// log.Printf("found userid = %v", uid)
// if uid == "" {
// log.Println("getAllUserTasks(): couldn't get user_id")
// c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retreive user id"})
// return
// }
uid := authentication.Curr_user_id
// Retrieve the user_id through the struct stored in the session
session := sessions.Default(c)
userProfile, ok := session.Get("user_profile").(crud.User)
if !ok {
c.String(http.StatusInternalServerError, "Couldn't retreive user's id to display tasks.")
return
}
uid := userProfile.UserID

arr, err := crud.GetUserTask(uid)
if err != nil {
Expand Down
7 changes: 5 additions & 2 deletions schema.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
CREATE TABLE UserTable (
UserID VARCHAR(255) PRIMARY KEY NOT NULL-- Assuming Auth0 provides a string-based user ID
CREATE TABLE IF NOT EXISTS UserTable (
UserID VARCHAR(255) PRIMARY KEY NOT NULL,
Points INTEGER NOT NULL,
BossId INTEGER NOT NULL,
FOREIGN KEY (BossId) REFERENCES BossTable(BossID)
);

CREATE TABLE TaskTable (
Expand Down
Loading