diff --git a/assets/css/style.css b/assets/css/style.css index 2cfa98f..dbf6822 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -28,7 +28,16 @@ body { font-family: "Helvetica Neue", "Helvetica", serif; margin: 0; align-self: center; +} + +.title-link { font-size: 1.75rem; + color: #000; + text-decoration: none; +} + +.title-link:hover { + color: #666; } .login-container { diff --git a/auth/sessions.go b/auth/sessions.go index 51c69f4..3505a5c 100644 --- a/auth/sessions.go +++ b/auth/sessions.go @@ -3,205 +3,33 @@ package auth import ( "encoding/json" "fmt" - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/gorilla/securecookie" - "github.com/markbates/goth" - "log" - "net/url" - "sponsorahacker/config" - "sponsorahacker/db" "strconv" - "time" ) -type SessionStore interface { - CreateSession(*gin.Context) error - GetSession(string) (Session, error) - DeleteSession(string) error -} - -type SessionManager struct { - DB *db.DBClient -} - -type Session struct { - sessionId string - sessionData string - createdOn string - modifiedOn string - expiresOn string -} - type User struct { NickName string `json:"NickName"` Provider string `json:"Provider"` Provider_userid string `json:"UserID"` + Sid string `json:"Sid"` } -var secureCookie *securecookie.SecureCookie - -func NewSessionManager(db *db.DBClient) SessionManager { - - return SessionManager{DB: db} -} - -func (s *SessionManager) CreateSession(user goth.User, c *gin.Context) error { - sessionData, err := json.Marshal(user) - if err != nil { - fmt.Printf("error marshalling user: %v", err) - } - - auth := Session{ - sessionData: string(sessionData), - createdOn: time.Now().Format("20060102150405"), - modifiedOn: time.Now().Format("20060102150405"), - expiresOn: user.ExpiresAt.Format("20060102150405"), - } - - result, err := s.DB.Exec(` - INSERT INTO sessions (sessionData, createdOn, modifiedOn, expiresOn) - VALUES (?, ?, ?, ?);`, auth.sessionData, auth.createdOn, auth.modifiedOn, auth.expiresOn) - - if result == nil { - fmt.Printf("error getting result from database while creating the session: %v", err) - return err - } - spuser, err := GetUserFromSession(auth) - - err = s.SaveUserIfNotExist(spuser) - - if err != nil { - log.Printf("error creating session: %v", err) - return err - } - - sessionId, err := result.LastInsertId() - - if err != nil { - fmt.Printf("error getting session id from database while creating the session: %v", err) - } - - hash := []byte(config.GetEnvVar("COOKIE_HASH")) - block := []byte(config.GetEnvVar("COOKIE_BLOCK")) - secureCookie = securecookie.New(hash, block) - cookieValue := map[string]string{ - "sessionId": strconv.Itoa(int(sessionId)), - } - - encoded, err := secureCookie.Encode("_session", cookieValue) - - if err != nil { - fmt.Printf("error encoding cookie value: %v", err) - return err - } - - c.SetCookie("_session", encoded, 3600, "/", "localhost", false, true) - - return nil -} - -func (s *SessionManager) GetSession(session sessions.Session) (Session, error) { - // query for one row - result, err := s.DB.Query(`SELECT sessionData FROM sessions WHERE sessionId=$1 LIMIT 1`, session.ID()) +func HydrateUser(userJson string) *User { + user := User{} - // if err, then return an empty struct - if err != nil { - return Session{}, err + if err := json.Unmarshal([]byte(userJson), &user); err != nil { + fmt.Println("serialization in hydrating user error = ", err) + return nil } - // else go through the results and create a Session - for result.Next() { - var s Session - - // unless there is an error, of course, then return an empty struct - if err := result.StructScan(&s); err != nil { - return Session{}, err - } - - return s, nil - } - - // if we get nothing, well, we go nothing - return Session{}, nil + return &user } -func GetUserFromSession(session Session) (User, error) { - var user User - - fmt.Println(session.sessionData) - err := json.Unmarshal([]byte(session.sessionData), &user) +func (u *User) GetSid() int { + strSid, err := strconv.Atoi(u.Sid) if err != nil { - fmt.Println("error unmarshalling session data for user", err) - - return User{}, err + fmt.Println("sid is invalid") } - return user, nil -} - -func (s *SessionManager) SaveUserIfNotExist(user User) error { - rows, err := s.DB.Query(`SELECT * FROM users WHERE provider_userid = ? AND provider = ? LIMIT 1`, user.Provider_userid, user.Provider) - - if err != nil { - fmt.Printf("error getting user from database: %v", err) - return err - } - - exists := rows.Next() - - if !exists { - _, err = s.DB.Exec(` - INSERT INTO users (provider_userid, provider, username) - VALUES (?, ?, ?);`, user.Provider_userid, user.Provider, user.NickName) - - if err != nil { - fmt.Printf("error inserting user: %v", err) - return err - } - } - - return nil -} - -func (s *SessionManager) DeleteSession(c *gin.Context) error { - if cookie, err := c.Request.Cookie("_session"); err == nil { - value := make(map[string]string) - - cookieValue, _ := url.QueryUnescape(cookie.Value) - - hash := []byte(config.GetEnvVar("COOKIE_HASH")) - block := []byte(config.GetEnvVar("COOKIE_BLOCK")) - secureCookie := securecookie.New(hash, block) - - err = secureCookie.Decode("_session", cookieValue, &value) - - if err != nil { - fmt.Printf("error decoding cookie value: %v", err) - return err - } - - sessionId := value["sessionId"] - sessionIdInt, err := strconv.Atoi(sessionId) - - if err != nil { - fmt.Printf("error converting sessionId to int: %v", err) - return err - } - - _, err = s.DB.Exec(`DELETE FROM sessions WHERE ID = ?`, sessionIdInt) - - if err != nil { - return err - } - - if err != nil { - return err - } - } - - c.SetCookie("_session", "", -1, "/", "localhost", false, true) - - return nil + return strSid } diff --git a/db/db.go b/db/db.go index 25acaec..e2fd2a8 100644 --- a/db/db.go +++ b/db/db.go @@ -34,6 +34,7 @@ func (db *DBClient) Query(query string, args ...interface{}) (*sqlx.Rows, error) } func (db *DBClient) Exec(query string, args ...interface{}) (sql.Result, error) { + fmt.Println("running query") return db.db.Exec(query, args...) } diff --git a/main.go b/main.go index 4534af5..84beb21 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ func main() { r.GET("/login", pages.Login) r.GET("/welcome", pages.Welcome) r.GET("/goals", pages.Goals) + r.GET("/goals/:goalId", pages.Goal) // post routes r.POST("/goals", pages.CreateGoal) err = r.Run(":8080") diff --git a/models/goal.go b/models/goal.go new file mode 100644 index 0000000..ced7114 --- /dev/null +++ b/models/goal.go @@ -0,0 +1,81 @@ +package models + +import ( + "fmt" + "log" + "time" + + "sponsorahacker/db" +) + +type Goal struct { + id int + Name string `form:"item-name" db:"name" binding:"required"` + FundingAmount float64 `form:"funding-amount" db:"funding_amount" binding:"required,numeric"` + Description string `form:"item-description" db:"description" binding:"required"` + LearnMoreURL string `form:"item-url" db:"learn_more_url"` // Optional field + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + CreatedBy int `db:"created_by"` +} + +func (g *Goal) CreateGoal() error { + dbClient, err := db.NewDbClient() + + if err != nil { + log.Fatalf("Could not connect to database: %v", err) + return err + } + _, err = dbClient.Exec(`INSERT INTO goals (name, description, learn_more_url, funding_amount, created_by, created_at, updated_at) +VALUES (?, ?, ?, ?, ?, ?, ?);`, g.Name, g.Description, g.LearnMoreURL, g.FundingAmount, g.CreatedBy, g.CreatedAt, g.UpdatedAt) + + if err != nil { + log.Fatalf("Could not create goal: %v", err) + } + return nil +} + +func GetGoals(user int) ([]Goal, error) { + var goals []Goal + + dbClient, err := db.NewDbClient() + + if err != nil { + log.Fatalf("Could not connect to database: %v", err) + } + + result, err := dbClient.Query(`SELECT name, description, learn_more_url, funding_amount, created_by, created_at, updated_at FROM goals where created_by = ?`, user) + + if err != nil { + log.Fatalf("Could not get goals: %v", err) + } + for result.Next() { + var goal Goal + err = result.StructScan(&goal) + + fmt.Println(goal.Name) + + if err != nil { + log.Fatalf("Could not read goal: %v", err) + return nil, err + } + goals = append(goals, goal) + } + + return goals, nil +} + +func GetGoal(id int) (*Goal, error) { + dbClient, err := db.NewDbClient() + result, err := dbClient.Query(`SELECT name, description, learn_more_url, funding_amount, created_by, created_at, updated_at FROM goals where id = ?`, id) + + if err != nil { + log.Fatalf("Could not read goal: %v", err) + return nil, err + } + + var goal Goal + err = result.StructScan(&goal) + + return &goal, nil +} diff --git a/pages/goals.go b/pages/goals.go index 9a75550..b75f08d 100644 --- a/pages/goals.go +++ b/pages/goals.go @@ -3,26 +3,86 @@ package pages import ( "fmt" "github.com/gin-gonic/gin" + "github.com/markbates/goth/gothic" + "log" "net/http" "net/url" + "sponsorahacker/auth" "sponsorahacker/models" - "sponsorahacker/utils" "strconv" "strings" "time" ) func Goals(c *gin.Context) { - isLoggedIn := utils.CheckIfLoggedIn(c) + user, err := gothic.GetFromSession("user", c.Request) + + if err != nil { + fmt.Println("error finding session: ", err) + c.Redirect(http.StatusTemporaryRedirect, "/login") + return + } + + userModel := auth.HydrateUser(user) + + goals, err := models.GetGoals(userModel.GetSid()) + + if err != nil { + fmt.Println("error getting goals: ", err) + } + + fmt.Println("sid: ", userModel.GetSid()) + fmt.Println("goals:", goals) c.HTML(http.StatusOK, "goals.html", gin.H{ "title": "Sponsor A Hacker", - "isLoggedIn": isLoggedIn, + "isLoggedIn": true, + "user": userModel.NickName, + "goals": goals, + }) +} + +func Goal(c *gin.Context) { + user, err := gothic.GetFromSession("user", c.Request) + + if err != nil { + fmt.Println("error finding session: ", err) + c.Redirect(http.StatusTemporaryRedirect, "/login") + return + } + + goalId, err := strconv.Atoi(c.Param("goalId")) + + if err != nil { + log.Println("error parsing goal id: ", err) + } + + userModel := auth.HydrateUser(user) + goal, err := models.GetGoal(goalId) + + if err != nil { + fmt.Println("error getting goals: ", err) + } + + c.HTML(http.StatusOK, "goal.html", gin.H{ + "title": "Sponsor A Hacker", + "isLoggedIn": true, + "user": userModel.NickName, + "goal": goal, }) } func CreateGoal(c *gin.Context) { - isLoggedIn := utils.CheckIfLoggedIn(c) + user, err := gothic.GetFromSession("user", c.Request) + + if err != nil { + fmt.Println("error finding session: ", err) + c.AbortWithStatus(http.StatusForbidden) + return + } + + userModel := auth.HydrateUser(user) + trim := strings.TrimSpace parseFloat := strconv.ParseFloat parseUrl := url.Parse @@ -36,13 +96,13 @@ func CreateGoal(c *gin.Context) { description := trim(c.PostForm("item-description")) learnMoreURL := trim(c.PostForm("item-url")) - _, err := parseUrl(learnMoreURL) + _, err = parseUrl(learnMoreURL) if isEmpty(name) && isEmpty(fundingAmountStr) && isEmpty(description) && isEmpty(learnMoreURL) && err != nil { fieldError := fmt.Errorf("missing a required field. Please check and make sure all fields are filled out") c.HTML(http.StatusNotAcceptable, "goals.html", gin.H{ "title": "Sponsor A Hacker", - "isLoggedIn": isLoggedIn, + "isLoggedIn": true, "error": fieldError, }) } @@ -53,7 +113,7 @@ func CreateGoal(c *gin.Context) { fieldError := fmt.Errorf("invalid funding amount") c.HTML(http.StatusInternalServerError, "goals.html", gin.H{ "title": "Sponsor A Hacker", - "isLoggedIn": isLoggedIn, + "isLoggedIn": true, "error": fieldError, }) } @@ -61,12 +121,18 @@ func CreateGoal(c *gin.Context) { createdDate := time.Now() updatedDate := time.Now() + if err != nil { + c.HTML(http.StatusInternalServerError, "goals.html", gin.H{}) + } + + fmt.Println("sid is : ", userModel.GetSid()) goal := models.Goal{ Name: name, FundingAmount: fundingAmount, Description: description, LearnMoreURL: learnMoreURL, CreatedAt: createdDate, + CreatedBy: userModel.GetSid(), UpdatedAt: updatedDate, } @@ -76,13 +142,13 @@ func CreateGoal(c *gin.Context) { fmt.Printf("Error inserting goal: %v", err) c.HTML(http.StatusInternalServerError, "goals.html", gin.H{ "title": "Sponsor A Hacker", - "isLoggedIn": isLoggedIn, + "isLoggedIn": true, "error": err, }) } c.HTML(http.StatusOK, "goals.html", gin.H{ "title": "Sponsor A Hacker", - "isLoggedIn": isLoggedIn, + "isLoggedIn": true, }) } diff --git a/pages/home.go b/pages/home.go index ec60a85..5e8057a 100644 --- a/pages/home.go +++ b/pages/home.go @@ -3,29 +3,29 @@ package pages import ( "fmt" "github.com/gin-gonic/gin" - "github.com/joho/godotenv" - "log" + "github.com/markbates/goth/gothic" "net/http" - "sponsorahacker/db" - "sponsorahacker/utils" + "sponsorahacker/auth" ) func Home(c *gin.Context) { + user, err := gothic.GetFromSession("user", c.Request) - envErr := godotenv.Load() - if envErr != nil { - log.Fatal("Error loading .env file") + if err != nil { + fmt.Println("error checking login", err) } - isLoggedIn := utils.CheckIfLoggedIn(c) - fmt.Println("isLoggedIn:", isLoggedIn) - _, err := db.NewDbClient() + auth.HydrateUser(user) + if err != nil { - log.Fatal(err) + fmt.Println("error checking login", err) + c.HTML(http.StatusOK, "index.html", gin.H{ + "title": "Sponsor a Hacker", + "isLoggedIn": false, + }) + + return } - c.HTML(http.StatusOK, "index.html", gin.H{ - "title": "Sponsor a Hacker", - "isLoggedIn": isLoggedIn, - }) + c.Redirect(http.StatusTemporaryRedirect, "/welcome") } diff --git a/pages/welcome.go b/pages/welcome.go index f60241c..2cb276b 100644 --- a/pages/welcome.go +++ b/pages/welcome.go @@ -3,15 +3,27 @@ package pages import ( "fmt" "github.com/gin-gonic/gin" + "github.com/markbates/goth/gothic" "net/http" - "sponsorahacker/utils" + "sponsorahacker/auth" ) func Welcome(c *gin.Context) { - isLoggedIn := utils.CheckIfLoggedIn(c) - fmt.Println("isLoggedIn:", isLoggedIn) + user, err := gothic.GetFromSession("user", c.Request) + fmt.Println(user) + + if err != nil { + fmt.Println("error finding session: ", err) + c.Redirect(http.StatusTemporaryRedirect, "/login") + return + } + + userModel := auth.HydrateUser(user) + fmt.Println(userModel.NickName) + c.HTML(http.StatusOK, "welcome.html", gin.H{ "title": "Sponsor A Hacker", - "isLoggedIn": isLoggedIn, + "isLoggedIn": true, + "user": userModel.NickName, }) } diff --git a/templates/goal.html b/templates/goal.html new file mode 100644 index 0000000..373235e --- /dev/null +++ b/templates/goal.html @@ -0,0 +1,148 @@ + + + + + + + Goal Page + + + +{{template "pageheader" . }} + +
+ +
+

Goal: Learn Full-Stack Development

+

Description: A comprehensive course to master full-stack web development. This goal includes learning front-end and back-end technologies to build robust applications.

+

Funding Needed: $500

+

Funding Received: $350

+
+ + +
+

Funding Progress

+
+
+
+

70% funded

+
+ + +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/templates/goals.html b/templates/goals.html index 14e4949..2ef473e 100644 --- a/templates/goals.html +++ b/templates/goals.html @@ -13,7 +13,7 @@
-

Manage Your Goals

+

Ready to set some goals, {{ .user }}?

Add new goals or track progress on your current items.

@@ -43,20 +43,15 @@

Your Current Goals

-
-
-

Full-Stack Development Certification

-

Progress: 75% funded

+ {{ range .goals }} +
+
+

{{ .Name }}

+

{{ .Description }}

+

Progress: 75% funded

+
- -
-
-
-

Data Science Course

-

Progress: 40% funded

-
- -
+ {{ end }}
diff --git a/templates/header.html b/templates/header.html index 0f941e1..6ec4ef5 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,14 +1,17 @@ {{define "pageheader"}}
- -

- Sponsor A Hacker -

+ {{if eq .isLoggedIn false}} +

+ Sponsor A Hacker +

Login {{ else }} - Logout +

+ Sponsor A Hacker +

+ Logout {{ end }}
diff --git a/templates/upload-goal.html b/templates/upload-goal.html new file mode 100644 index 0000000..17c7d13 --- /dev/null +++ b/templates/upload-goal.html @@ -0,0 +1,116 @@ + + + + + + Submit Hackathon Project + + + +
+

Submit Your Hack

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + diff --git a/templates/welcome.html b/templates/welcome.html index 90e0303..5af984c 100644 --- a/templates/welcome.html +++ b/templates/welcome.html @@ -12,7 +12,7 @@
-

Welcome, Codegirl007!

+

Welcome, {{ .user }}!

Find your path, fund futures, or inspire a community today.