detached c872d87

Refactor user creation queries and add save more Github user informations

- Added a new struct GithubUserInfo to hold Github user information
- Modified CreateUser function to accept and process Github user information
- Renamed various methods for clearer semantics.
- Introduced new functions createDBUser, createDBAccount, createDBGitHubProvider, createDBSession to break down user creation steps
- Removed RefreshToken and Expires fields from Provider struct and added new fields (Login, AvatarURL, ProfileURL, Email)
- Added additional error checking to various database operations
- Removed checkAdmin function and its usage in RenderLoginPage
- Added functionality to get Github user details in OAuthCallback
commit
c872d875d6202da92d35a3a2d7f080fc2b08a7a6
author
bnema · · 2023-10-17 08:45 UTC
parent
b6a20a5
signature
unsigned
3 files · +156 -104
M internal/db/queries/userqueries.go +80 -58
@@ -1,6 +1,7 @@
11 package queries
22
33 import (
4+ "database/sql"
45 "fmt"
56 "time"
67
@@ -13,88 +14,95 @@ func generateUUID() string {
1314 return uuid.New().String()
1415 }
1516
16-// CreateUser creates a new user along with the associated account, provider, and session.
17-func CreateUser(a *app.App, accessToken string, browserInfo string) error {
17+// UserInfo holds the essential information for a Github user.
18+type GithubUserInfo struct {
19+ Login string `json:"login"`
20+ AvatarURL string `json:"avatar_url"`
21+ ProfileURL string `json:"html_url"`
22+ Emails []string
23+}
1824
19- // !! Additionnal security check !!
25+// CreateUser creates a new user along with the associated account, provider, and session.
26+func CreateUser(a *app.App, accessToken string, browserInfo string, userInfo *GithubUserInfo) error {
2027 // Check if a user already exists. If so, return an error.
21- userExists, err := UserExists(a)
22- if err != nil {
23- return err
28+ if exists, err := CheckDBUserExists(a); err != nil || exists {
29+ return fmt.Errorf("error checking user or user already exists: %v", err)
2430 }
25- if userExists {
26- return fmt.Errorf("user already exists")
27- }
28-
29- user := db.User{}
30- account := db.Account{}
3131
32- // Step 1: Create a user
33- newUUID := generateUUID()
34- user.ID = newUUID
35- user.Name = "Test User"
36- user.Email = "admin@gordon.cul"
37-
38- _, err = a.DB.Exec("INSERT INTO user (id ,name, email) VALUES (?, ?, ?)", user.ID, user.Name, user.Email)
32+ user, err := createDBUser(a, userInfo)
3933 if err != nil {
4034 return err
4135 }
42-
4336 a.DBTables.User.ID = user.ID
44- // Step 2: Create an account for the user with the user ID
45- account.ID, err = createAccount(a)
37+
38+ account, err := createDBAccount(a)
4639 if err != nil {
4740 return err
4841 }
49- // Step 3: Create a provider for the user
50- if err := createProvider(a, accessToken); err != nil {
51- return err
52- }
42+ a.DBTables.Account.ID = account.ID
5343
54- // Step 4: Create a session for the user
55- if err := createSession(a, browserInfo); err != nil {
44+ err = createDBGitHubProvider(a, accessToken, userInfo)
45+ if err != nil {
5646 return err
5747 }
5848
59- return nil
49+ return createDBSession(a, browserInfo)
6050 }
6151
62-func createAccount(a *app.App) (string, error) {
63- account := a.DBTables.Account
64- // Generate a new UUID for the account
65- account.ID = generateUUID()
52+func createDBUser(a *app.App, userInfo *GithubUserInfo) (*db.User, error) {
53+ user := &db.User{
54+ ID: generateUUID(),
55+ Name: userInfo.Login,
56+ Email: userInfo.Emails[0],
57+ }
6658
67- // Insert the account into the database
68- _, err := a.DB.Exec("INSERT INTO account (id, user_id) VALUES (?, ?)", account.ID, a.DBTables.User.ID)
69- if err != nil {
70- return "", err
59+ _, err := a.DB.Exec(
60+ "INSERT INTO user (id, name, email) VALUES (?, ?, ?)",
61+ user.ID, user.Name, user.Email,
62+ )
63+ return user, err
64+}
65+
66+func createDBAccount(a *app.App) (*db.Account, error) {
67+ account := &db.Account{
68+ ID: generateUUID(),
7169 }
72- a.DBTables.Account.ID = account.ID
73- return account.ID, nil
70+
71+ _, err := a.DB.Exec(
72+ "INSERT INTO account (id, user_id) VALUES (?, ?)",
73+ account.ID, a.DBTables.User.ID,
74+ )
75+ return account, err
7476 }
7577
76-func createProvider(a *app.App, accessToken string) error {
77- provider := a.DBTables.Provider
78- provider.ID = generateUUID()
78+func createDBGitHubProvider(a *app.App, accessToken string, userInfo *GithubUserInfo) error {
79+ provider := &db.Provider{
80+ ID: generateUUID(),
81+ AccessToken: accessToken,
82+ Email: userInfo.Emails[0],
83+ Login: userInfo.Login,
84+ AvatarURL: userInfo.AvatarURL,
85+ ProfileURL: userInfo.ProfileURL,
86+ }
7987
80- queryInsertProvider := "INSERT INTO provider (id, account_id, name, access_token, refresh_token, expires) VALUES (?, ?, ?, ?, ?, ?)"
81- _, err := a.DB.Exec(queryInsertProvider, provider.ID, a.DBTables.Account.ID, "GitHub", accessToken, "refreshToken", time.Now().Add(time.Hour*24).Format(time.RFC3339))
88+ query := "INSERT INTO provider (id, account_id, name, access_token, login, avatar_url, profile_url, email) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
89+ _, err := a.DB.Exec(query, provider.ID, a.DBTables.Account.ID, "GitHub", provider.AccessToken, provider.Login, provider.AvatarURL, provider.ProfileURL, provider.Email)
8290 return err
8391 }
8492
8593 // createSession creates a session for the user.
86-func createSession(a *app.App, browserInfo string) error {
87- sessions := a.DBTables.Sessions
88- sessions.ID = generateUUID()
89-
90- queryInsertSession := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)"
91- _, err := a.DB.Exec(queryInsertSession, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true)
94+func createDBSession(a *app.App, browserInfo string) error {
95+ sessions := &db.Sessions{
96+ ID: generateUUID(),
97+ }
9298
99+ query := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)"
100+ _, err := a.DB.Exec(query, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true)
93101 a.DBTables.Sessions.ID = sessions.ID
94102 return err
95103 }
96104
97-func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) error {
105+func CreateOrUpdateDBSession(a *app.App, accessToken string, browserInfo string) error {
98106 existingSessionID := a.DBTables.Sessions.ID
99107
100108 queryFindExistingSession := "SELECT id FROM sessions WHERE account_id = ? AND browser_info = ?"
@@ -124,19 +132,33 @@ func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) e
124132 return nil
125133 }
126134
127-func UserExists(a *app.App) (bool, error) {
128- var count int
129- err := a.DB.QueryRow("SELECT COUNT(*) FROM user").Scan(&count)
135+func CheckDBAccountAccessToken(a *app.App, accessToken string) (bool, error) {
136+ var existingAccessToken string
137+ err := a.DB.QueryRow("SELECT access_token FROM provider WHERE name = ?", "GitHub").Scan(&existingAccessToken)
130138 if err != nil {
131139 return false, err
132140 }
133- if count > 0 {
141+
142+ if existingAccessToken == accessToken {
134143 return true, nil
135144 }
145+
136146 return false, nil
137147 }
138148
139-func UpdateAccessToken(a *app.App, accessToken string) error {
140- _, err := a.DB.Exec("UPDATE provider SET access_token = ? WHERE name = ?", accessToken, "GitHub")
141- return err
149+func CheckDBUserExists(a *app.App) (bool, error) {
150+ var userID string
151+ err := a.DB.QueryRow("SELECT id FROM user").Scan(&userID)
152+ if err != nil {
153+ if err == sql.ErrNoRows {
154+ return false, nil
155+ }
156+ return false, err
157+ }
158+
159+ if userID != "" {
160+ return true, nil
161+ }
162+
163+ return false, nil
142164 }
@@ -1,6 +1,7 @@
1 package queries1 package queries
2 2
3 import (3 import (
4+ "database/sql"
4 "fmt"5 "fmt"
5 "time"6 "time"
6 7
@@ -13,88 +14,95 @@ func generateUUID() string {
13 return uuid.New().String()14 return uuid.New().String()
14 }15 }
15 16
16-// CreateUser creates a new user along with the associated account, provider, and session.17+// UserInfo holds the essential information for a Github user.
17-func CreateUser(a *app.App, accessToken string, browserInfo string) error {18+type GithubUserInfo struct {
19+ Login string `json:"login"`
20+ AvatarURL string `json:"avatar_url"`
21+ ProfileURL string `json:"html_url"`
22+ Emails []string
23+}
18 24
19- // !! Additionnal security check !!25+// CreateUser creates a new user along with the associated account, provider, and session.
26+func CreateUser(a *app.App, accessToken string, browserInfo string, userInfo *GithubUserInfo) error {
20 // Check if a user already exists. If so, return an error.27 // Check if a user already exists. If so, return an error.
21- userExists, err := UserExists(a)28+ if exists, err := CheckDBUserExists(a); err != nil || exists {
22- if err != nil {29+ return fmt.Errorf("error checking user or user already exists: %v", err)
23- return err
24 }30 }
25- if userExists {
26- return fmt.Errorf("user already exists")
27- }
28-
29- user := db.User{}
30- account := db.Account{}
31 31
32- // Step 1: Create a user32+ user, err := createDBUser(a, userInfo)
33- newUUID := generateUUID()
34- user.ID = newUUID
35- user.Name = "Test User"
36- user.Email = "admin@gordon.cul"
37-
38- _, err = a.DB.Exec("INSERT INTO user (id ,name, email) VALUES (?, ?, ?)", user.ID, user.Name, user.Email)
39 if err != nil {33 if err != nil {
40 return err34 return err
41 }35 }
42-
43 a.DBTables.User.ID = user.ID36 a.DBTables.User.ID = user.ID
44- // Step 2: Create an account for the user with the user ID37+
45- account.ID, err = createAccount(a)38+ account, err := createDBAccount(a)
46 if err != nil {39 if err != nil {
47 return err40 return err
48 }41 }
49- // Step 3: Create a provider for the user42+ a.DBTables.Account.ID = account.ID
50- if err := createProvider(a, accessToken); err != nil {
51- return err
52- }
53 43
54- // Step 4: Create a session for the user44+ err = createDBGitHubProvider(a, accessToken, userInfo)
55- if err := createSession(a, browserInfo); err != nil {45+ if err != nil {
56 return err46 return err
57 }47 }
58 48
59- return nil49+ return createDBSession(a, browserInfo)
60 }50 }
61 51
62-func createAccount(a *app.App) (string, error) {52+func createDBUser(a *app.App, userInfo *GithubUserInfo) (*db.User, error) {
63- account := a.DBTables.Account53+ user := &db.User{
64- // Generate a new UUID for the account54+ ID: generateUUID(),
65- account.ID = generateUUID()55+ Name: userInfo.Login,
56+ Email: userInfo.Emails[0],
57+ }
66 58
67- // Insert the account into the database59+ _, err := a.DB.Exec(
68- _, err := a.DB.Exec("INSERT INTO account (id, user_id) VALUES (?, ?)", account.ID, a.DBTables.User.ID)60+ "INSERT INTO user (id, name, email) VALUES (?, ?, ?)",
69- if err != nil {61+ user.ID, user.Name, user.Email,
70- return "", err62+ )
63+ return user, err
64+}
65+
66+func createDBAccount(a *app.App) (*db.Account, error) {
67+ account := &db.Account{
68+ ID: generateUUID(),
71 }69 }
72- a.DBTables.Account.ID = account.ID70+
73- return account.ID, nil71+ _, err := a.DB.Exec(
72+ "INSERT INTO account (id, user_id) VALUES (?, ?)",
73+ account.ID, a.DBTables.User.ID,
74+ )
75+ return account, err
74 }76 }
75 77
76-func createProvider(a *app.App, accessToken string) error {78+func createDBGitHubProvider(a *app.App, accessToken string, userInfo *GithubUserInfo) error {
77- provider := a.DBTables.Provider79+ provider := &db.Provider{
78- provider.ID = generateUUID()80+ ID: generateUUID(),
81+ AccessToken: accessToken,
82+ Email: userInfo.Emails[0],
83+ Login: userInfo.Login,
84+ AvatarURL: userInfo.AvatarURL,
85+ ProfileURL: userInfo.ProfileURL,
86+ }
79 87
80- queryInsertProvider := "INSERT INTO provider (id, account_id, name, access_token, refresh_token, expires) VALUES (?, ?, ?, ?, ?, ?)"88+ query := "INSERT INTO provider (id, account_id, name, access_token, login, avatar_url, profile_url, email) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
81- _, err := a.DB.Exec(queryInsertProvider, provider.ID, a.DBTables.Account.ID, "GitHub", accessToken, "refreshToken", time.Now().Add(time.Hour*24).Format(time.RFC3339))89+ _, err := a.DB.Exec(query, provider.ID, a.DBTables.Account.ID, "GitHub", provider.AccessToken, provider.Login, provider.AvatarURL, provider.ProfileURL, provider.Email)
82 return err90 return err
83 }91 }
84 92
85 // createSession creates a session for the user.93 // createSession creates a session for the user.
86-func createSession(a *app.App, browserInfo string) error {94+func createDBSession(a *app.App, browserInfo string) error {
87- sessions := a.DBTables.Sessions95+ sessions := &db.Sessions{
88- sessions.ID = generateUUID()96+ ID: generateUUID(),
89-97+ }
90- queryInsertSession := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)"
91- _, err := a.DB.Exec(queryInsertSession, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true)
92 98
99+ query := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)"
100+ _, err := a.DB.Exec(query, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true)
93 a.DBTables.Sessions.ID = sessions.ID101 a.DBTables.Sessions.ID = sessions.ID
94 return err102 return err
95 }103 }
96 104
97-func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) error {105+func CreateOrUpdateDBSession(a *app.App, accessToken string, browserInfo string) error {
98 existingSessionID := a.DBTables.Sessions.ID106 existingSessionID := a.DBTables.Sessions.ID
99 107
100 queryFindExistingSession := "SELECT id FROM sessions WHERE account_id = ? AND browser_info = ?"108 queryFindExistingSession := "SELECT id FROM sessions WHERE account_id = ? AND browser_info = ?"
@@ -124,19 +132,33 @@ func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) e
124 return nil132 return nil
125 }133 }
126 134
127-func UserExists(a *app.App) (bool, error) {135+func CheckDBAccountAccessToken(a *app.App, accessToken string) (bool, error) {
128- var count int136+ var existingAccessToken string
129- err := a.DB.QueryRow("SELECT COUNT(*) FROM user").Scan(&count)137+ err := a.DB.QueryRow("SELECT access_token FROM provider WHERE name = ?", "GitHub").Scan(&existingAccessToken)
130 if err != nil {138 if err != nil {
131 return false, err139 return false, err
132 }140 }
133- if count > 0 {141+
142+ if existingAccessToken == accessToken {
134 return true, nil143 return true, nil
135 }144 }
145+
136 return false, nil146 return false, nil
137 }147 }
138 148
139-func UpdateAccessToken(a *app.App, accessToken string) error {149+func CheckDBUserExists(a *app.App) (bool, error) {
140- _, err := a.DB.Exec("UPDATE provider SET access_token = ? WHERE name = ?", accessToken, "GitHub")150+ var userID string
141- return err151+ err := a.DB.QueryRow("SELECT id FROM user").Scan(&userID)
152+ if err != nil {
153+ if err == sql.ErrNoRows {
154+ return false, nil
155+ }
156+ return false, err
157+ }
158+
159+ if userID != "" {
160+ return true, nil
161+ }
162+
163+ return false, nil
142 }164 }
M internal/db/struct.go +8 -6
@@ -23,10 +23,12 @@ type Sessions struct {
2323 }
2424
2525 type Provider struct {
26- ID string `sql:"id, primary_key"`
27- AccountID string `sql:"account_id, foreign_key=account.id"`
28- Name string `sql:"name"`
29- AccessToken string `sql:"access_token"`
30- RefreshToken string `sql:"refresh_token"`
31- Expires string `sql:"expires"`
26+ ID string `sql:"id, primary_key"`
27+ AccountID string `sql:"account_id, foreign_key=account.id"`
28+ Name string `sql:"name"`
29+ AccessToken string `sql:"access_token"`
30+ Login string `sql:"login"`
31+ AvatarURL string `sql:"avatar_url"`
32+ ProfileURL string `sql:"profile_url"`
33+ Email string `sql:"email"`
3234 }
@@ -23,10 +23,12 @@ type Sessions struct {
23 }23 }
24 24
25 type Provider struct {25 type Provider struct {
26- ID string `sql:"id, primary_key"`26+ ID string `sql:"id, primary_key"`
27- AccountID string `sql:"account_id, foreign_key=account.id"`27+ AccountID string `sql:"account_id, foreign_key=account.id"`
28- Name string `sql:"name"`28+ Name string `sql:"name"`
29- AccessToken string `sql:"access_token"`29+ AccessToken string `sql:"access_token"`
30- RefreshToken string `sql:"refresh_token"`30+ Login string `sql:"login"`
31- Expires string `sql:"expires"`31+ AvatarURL string `sql:"avatar_url"`
32+ ProfileURL string `sql:"profile_url"`
33+ Email string `sql:"email"`
32 }34 }
M internal/httpserve/handler/oauth.go +68 -40
@@ -2,7 +2,9 @@ package handler
22
33 import (
44 "encoding/base64"
5+ "encoding/json"
56 "fmt"
7+ "io"
68 "net/http"
79 "os"
810
@@ -25,35 +27,12 @@ func compareGordonToken(c echo.Context, a *app.App) error {
2527 return nil
2628 }
2729
28-// Check if there is already a user in the db which means that the admin is already setup
29-func checkAdmin(a *app.App) (bool, error) {
30- var count int
31- err := a.DB.QueryRow("SELECT COUNT(*) FROM user WHERE id IS NOT NULL AND name != '' AND email != ''").Scan(&count)
32- if err != nil {
33- return false, err
34- }
35- if count > 0 {
36- return true, nil
37- }
38- return false, nil
39-}
40-
4130 // RenderLoginPage renders the login.html template
4231 func RenderLoginPage(c echo.Context, a *app.App) error {
4332
4433 // Compare the token
4534 compareGordonToken(c, a)
4635
47- // Check if there is already an admin in the db we cannot add another one so we redirect to the login page with a message
48- adminExists, err := checkAdmin(a)
49- if err != nil {
50- return err
51- }
52-
53- if adminExists {
54- return c.HTML(http.StatusOK, "<h1>User already exists</h1>")
55- }
56-
5736 data := map[string]interface{}{
5837 "Title": "Login",
5938 }
@@ -121,33 +100,82 @@ func OAuthCallback(c echo.Context, a *app.App) error {
121100 }
122101
123102 browserInfo := c.Request().UserAgent()
124-
125- sessStr := fmt.Sprintf("%v", sess)
126- fmt.Println(sessStr)
127-
128- // Check if the user already exists in the database
129- userExists, err := queries.UserExists(a)
103+ // print github user info
104+ userInfo, err := githubGetUserDetails(c, a)
130105 if err != nil {
131106 return err
132107 }
133108
109+ // Check if the user exists
110+ // if not, create it
111+ // if yes, update the access token and create or update the session
134112 // If the user does not exist, create it
113+ userExists, err := queries.CheckDBUserExists(a)
114+ if err != nil {
115+ return fmt.Errorf("could not check if user exists: %w", err)
116+ }
135117 if !userExists {
136- err := queries.CreateUser(a, accessToken, browserInfo)
118+ err := queries.CreateUser(a, accessToken, browserInfo, userInfo)
137119 if err != nil {
138- return err
120+ return fmt.Errorf("could not create user: %w", err)
139121 }
140122 } else {
141- // If the user exists, update acccessToken and then create or update the session
142- err := queries.UpdateAccessToken(a, accessToken)
143- if err != nil {
144- return err
145- }
146123
147- err = queries.CreateOrUpdateSession(a, accessToken, browserInfo)
148- if err != nil {
149- return err
150- }
151124 }
152125 return c.Redirect(http.StatusFound, "/admin")
153126 }
127+func fetchGithubAPI(url string, authHeader string, result interface{}) error {
128+ req, err := http.NewRequest("GET", url, nil)
129+ if err != nil {
130+ return err
131+ }
132+
133+ req.Header.Set("Authorization", authHeader)
134+
135+ resp, err := http.DefaultClient.Do(req)
136+ if err != nil {
137+ return err
138+ }
139+ defer resp.Body.Close()
140+
141+ if resp.StatusCode != http.StatusOK {
142+ return fmt.Errorf("got status code %d", resp.StatusCode)
143+ }
144+
145+ body, err := io.ReadAll(resp.Body)
146+ if err != nil {
147+ return err
148+ }
149+
150+ err = json.Unmarshal(body, &result)
151+ if err != nil {
152+ return err
153+ }
154+
155+ return nil
156+}
157+
158+func githubGetUserDetails(c echo.Context, a *app.App) (userInfo *queries.GithubUserInfo, err error) {
159+ accessToken := c.QueryParam("access_token")
160+
161+ // Fetch user info
162+ err = fetchGithubAPI("https://api.github.com/user", "Bearer "+accessToken, &userInfo)
163+ if err != nil {
164+ return nil, err
165+ }
166+ // Fetch user emails
167+ var emailObjects []struct {
168+ Email string `json:"email"`
169+ }
170+ err = fetchGithubAPI("https://api.github.com/user/emails", "Bearer "+accessToken, &emailObjects)
171+ if err != nil {
172+ return nil, err
173+ }
174+
175+ // Extract emails and populate the Emails field in userInfo
176+ for _, emailObj := range emailObjects {
177+ userInfo.Emails = append(userInfo.Emails, emailObj.Email)
178+ }
179+
180+ return userInfo, nil
181+}
@@ -2,7 +2,9 @@ package handler
2 2
3 import (3 import (
4 "encoding/base64"4 "encoding/base64"
5+ "encoding/json"
5 "fmt"6 "fmt"
7+ "io"
6 "net/http"8 "net/http"
7 "os"9 "os"
8 10
@@ -25,35 +27,12 @@ func compareGordonToken(c echo.Context, a *app.App) error {
25 return nil27 return nil
26 }28 }
27 29
28-// Check if there is already a user in the db which means that the admin is already setup
29-func checkAdmin(a *app.App) (bool, error) {
30- var count int
31- err := a.DB.QueryRow("SELECT COUNT(*) FROM user WHERE id IS NOT NULL AND name != '' AND email != ''").Scan(&count)
32- if err != nil {
33- return false, err
34- }
35- if count > 0 {
36- return true, nil
37- }
38- return false, nil
39-}
40-
41 // RenderLoginPage renders the login.html template30 // RenderLoginPage renders the login.html template
42 func RenderLoginPage(c echo.Context, a *app.App) error {31 func RenderLoginPage(c echo.Context, a *app.App) error {
43 32
44 // Compare the token33 // Compare the token
45 compareGordonToken(c, a)34 compareGordonToken(c, a)
46 35
47- // Check if there is already an admin in the db we cannot add another one so we redirect to the login page with a message
48- adminExists, err := checkAdmin(a)
49- if err != nil {
50- return err
51- }
52-
53- if adminExists {
54- return c.HTML(http.StatusOK, "<h1>User already exists</h1>")
55- }
56-
57 data := map[string]interface{}{36 data := map[string]interface{}{
58 "Title": "Login",37 "Title": "Login",
59 }38 }
@@ -121,33 +100,82 @@ func OAuthCallback(c echo.Context, a *app.App) error {
121 }100 }
122 101
123 browserInfo := c.Request().UserAgent()102 browserInfo := c.Request().UserAgent()
124-103+ // print github user info
125- sessStr := fmt.Sprintf("%v", sess)104+ userInfo, err := githubGetUserDetails(c, a)
126- fmt.Println(sessStr)
127-
128- // Check if the user already exists in the database
129- userExists, err := queries.UserExists(a)
130 if err != nil {105 if err != nil {
131 return err106 return err
132 }107 }
133 108
109+ // Check if the user exists
110+ // if not, create it
111+ // if yes, update the access token and create or update the session
134 // If the user does not exist, create it112 // If the user does not exist, create it
113+ userExists, err := queries.CheckDBUserExists(a)
114+ if err != nil {
115+ return fmt.Errorf("could not check if user exists: %w", err)
116+ }
135 if !userExists {117 if !userExists {
136- err := queries.CreateUser(a, accessToken, browserInfo)118+ err := queries.CreateUser(a, accessToken, browserInfo, userInfo)
137 if err != nil {119 if err != nil {
138- return err120+ return fmt.Errorf("could not create user: %w", err)
139 }121 }
140 } else {122 } else {
141- // If the user exists, update acccessToken and then create or update the session
142- err := queries.UpdateAccessToken(a, accessToken)
143- if err != nil {
144- return err
145- }
146 123
147- err = queries.CreateOrUpdateSession(a, accessToken, browserInfo)
148- if err != nil {
149- return err
150- }
151 }124 }
152 return c.Redirect(http.StatusFound, "/admin")125 return c.Redirect(http.StatusFound, "/admin")
153 }126 }
127+func fetchGithubAPI(url string, authHeader string, result interface{}) error {
128+ req, err := http.NewRequest("GET", url, nil)
129+ if err != nil {
130+ return err
131+ }
132+
133+ req.Header.Set("Authorization", authHeader)
134+
135+ resp, err := http.DefaultClient.Do(req)
136+ if err != nil {
137+ return err
138+ }
139+ defer resp.Body.Close()
140+
141+ if resp.StatusCode != http.StatusOK {
142+ return fmt.Errorf("got status code %d", resp.StatusCode)
143+ }
144+
145+ body, err := io.ReadAll(resp.Body)
146+ if err != nil {
147+ return err
148+ }
149+
150+ err = json.Unmarshal(body, &result)
151+ if err != nil {
152+ return err
153+ }
154+
155+ return nil
156+}
157+
158+func githubGetUserDetails(c echo.Context, a *app.App) (userInfo *queries.GithubUserInfo, err error) {
159+ accessToken := c.QueryParam("access_token")
160+
161+ // Fetch user info
162+ err = fetchGithubAPI("https://api.github.com/user", "Bearer "+accessToken, &userInfo)
163+ if err != nil {
164+ return nil, err
165+ }
166+ // Fetch user emails
167+ var emailObjects []struct {
168+ Email string `json:"email"`
169+ }
170+ err = fetchGithubAPI("https://api.github.com/user/emails", "Bearer "+accessToken, &emailObjects)
171+ if err != nil {
172+ return nil, err
173+ }
174+
175+ // Extract emails and populate the Emails field in userInfo
176+ for _, emailObj := range emailObjects {
177+ userInfo.Emails = append(userInfo.Emails, emailObj.Email)
178+ }
179+
180+ return userInfo, nil
181+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close