detached 546f18d

Introduce UUIDs for DB ID's and enhance error handling and comments

- Replace int64 IDs with string UUIDs in internal db structures
- Refactor error messages in token initialization and log handling
- Add UUID generation in user and account creation
- Modify SQL queries to accommodate UUID changes
- Add a dependency on github.com/google/uuid for UUID generation
commit
546f18da0226191425f1578ee41c78c519dbf9cb
author
bnema · · 2023-10-16 17:59 UTC
parent
e228d6a
signature
unsigned
9 files · +51 -43
M cmd/srv/main.go +1 -1
@@ -30,7 +30,7 @@ func main() {
3030
3131 _, err = app.HandleNewTokenInitialization(a)
3232 if err != nil {
33- log.Fatal("Failed to initialize new token:", err)
33+ log.Print(err)
3434 }
3535
3636 dockerClient := &docker.DockerClient{}
@@ -30,7 +30,7 @@ func main() {
30 30
31 _, err = app.HandleNewTokenInitialization(a)31 _, err = app.HandleNewTokenInitialization(a)
32 if err != nil {32 if err != nil {
33- log.Fatal("Failed to initialize new token:", err)33+ log.Print(err)
34 }34 }
35 35
36 dockerClient := &docker.DockerClient{}36 dockerClient := &docker.DockerClient{}
M go.mod +1 -0
@@ -23,6 +23,7 @@ require (
2323 github.com/docker/go-units v0.5.0 // indirect
2424 github.com/gogo/protobuf v1.3.2 // indirect
2525 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
26+ github.com/google/uuid v1.3.1 // indirect
2627 github.com/gorilla/context v1.1.1 // indirect
2728 github.com/gorilla/css v1.0.0 // indirect
2829 github.com/gorilla/securecookie v1.1.1 // indirect
@@ -23,6 +23,7 @@ require (
23 github.com/docker/go-units v0.5.0 // indirect23 github.com/docker/go-units v0.5.0 // indirect
24 github.com/gogo/protobuf v1.3.2 // indirect24 github.com/gogo/protobuf v1.3.2 // indirect
25 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect25 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
26+ github.com/google/uuid v1.3.1 // indirect
26 github.com/gorilla/context v1.1.1 // indirect27 github.com/gorilla/context v1.1.1 // indirect
27 github.com/gorilla/css v1.0.0 // indirect28 github.com/gorilla/css v1.0.0 // indirect
28 github.com/gorilla/securecookie v1.1.1 // indirect29 github.com/gorilla/securecookie v1.1.1 // indirect
M go.sum +2 -0 generated file: collapsed
M internal/app/sqliteclose.go +1 -0
@@ -10,5 +10,6 @@ func CloseDB(a *App) error {
1010 }
1111
1212 func saveDB(a *App) error {
13+ // TODO: Backup the database file if there is any modification.
1314 return nil
1415 }
@@ -10,5 +10,6 @@ func CloseDB(a *App) error {
10 }10 }
11 11
12 func saveDB(a *App) error {12 func saveDB(a *App) error {
13+ // TODO: Backup the database file if there is any modification.
13 return nil14 return nil
14 }15 }
M internal/app/tokeninit.go +1 -1
@@ -18,7 +18,7 @@ func HandleNewTokenInitialization(a *App) (string, error) {
1818
1919 // If count is greater than 0, it means there is at least one user in the database
2020 if count > 0 {
21- return "", fmt.Errorf("admin user already exists")
21+ return "", fmt.Errorf("user already present, skipping token initialization")
2222 }
2323
2424 // If we reach here, it means admin does not exist, so we generate a token
@@ -18,7 +18,7 @@ func HandleNewTokenInitialization(a *App) (string, error) {
18 18
19 // If count is greater than 0, it means there is at least one user in the database19 // If count is greater than 0, it means there is at least one user in the database
20 if count > 0 {20 if count > 0 {
21- return "", fmt.Errorf("admin user already exists")21+ return "", fmt.Errorf("user already present, skipping token initialization")
22 }22 }
23 23
24 // If we reach here, it means admin does not exist, so we generate a token24 // If we reach here, it means admin does not exist, so we generate a token
M internal/db/queries/userqueries.go +33 -29
@@ -6,8 +6,13 @@ import (
66
77 "github.com/bnema/gordon/internal/app"
88 "github.com/bnema/gordon/internal/db"
9+ "github.com/google/uuid"
910 )
1011
12+func generateUUID() string {
13+ return uuid.New().String()
14+}
15+
1116 // CreateUser creates a new user along with the associated account, provider, and session.
1217 func CreateUser(a *app.App, accessToken string, browserInfo string) error {
1318
@@ -23,23 +28,19 @@ func CreateUser(a *app.App, accessToken string, browserInfo string) error {
2328
2429 user := db.User{}
2530 account := db.Account{}
26- // Step 1: Create an entry in the user table
27- // TODO : obtain the user name and email from the GitHub API
28- result, err := a.DB.Exec("INSERT INTO user (name, email) VALUES (?, ?)", "admin", "admin@gordon")
29- if err != nil {
30- return err
31- }
3231
33- // Get the last insert ID from the result of the INSERT operation
34- user.ID, err = result.LastInsertId()
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)
3539 if err != nil {
36- return fmt.Errorf("error while fetching LastInsertId: %w", err)
40+ return err
3741 }
3842
39- fmt.Printf("User ID: %d\n", user.ID)
40-
4143 a.DBTables.User.ID = user.ID
42-
4344 // Step 2: Create an account for the user with the user ID
4445 account.ID, err = createAccount(a)
4546 if err != nil {
@@ -58,35 +59,38 @@ func CreateUser(a *app.App, accessToken string, browserInfo string) error {
5859 return nil
5960 }
6061
61-func createAccount(a *app.App) (int64, error) {
62- fmt.Println("Whats the value of a.DBTables.User.ID?", a.DBTables.User.ID)
62+func createAccount(a *app.App) (string, error) {
6363 account := a.DBTables.Account
64- queryInsertAccount := "INSERT INTO account (user_id) VALUES (?) RETURNING id"
65- // Execute the INSERT operation
66- result, err := a.DB.Exec(queryInsertAccount, a.DBTables.User.ID)
67- if err != nil {
68- return 0, fmt.Errorf("error while inserting into account: %w", err)
69- }
64+ // Generate a new UUID for the account
65+ account.ID = generateUUID()
7066
71- // Get the last inserted ID
72- account.ID, err = result.LastInsertId()
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)
7369 if err != nil {
74- return 0, fmt.Errorf("error while fetching LastInsertId for account: %w", err)
70+ return "", err
7571 }
76-
72+ a.DBTables.Account.ID = account.ID
7773 return account.ID, nil
7874 }
7975
8076 func createProvider(a *app.App, accessToken string) error {
81- queryInsertProvider := "INSERT INTO provider (account_id, name, access_token, refresh_token, expires) VALUES (?, ?, ?, ?, ?)"
82- _, err := a.DB.Exec(queryInsertProvider, a.DBTables.Account.ID, "GitHub", accessToken, "refreshToken", time.Now().Add(time.Hour*24).Format(time.RFC3339))
77+ provider := a.DBTables.Provider
78+ provider.ID = generateUUID()
79+
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))
8382 return err
8483 }
8584
8685 // createSession creates a session for the user.
8786 func createSession(a *app.App, browserInfo string) error {
88- queryInsertSession := "INSERT INTO sessions (account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?)"
89- _, err := a.DB.Exec(queryInsertSession, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true)
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)
92+
93+ a.DBTables.Sessions.ID = sessions.ID
9094 return err
9195 }
9296
@@ -102,7 +106,7 @@ func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) e
102106 // Calculate the new expiry time
103107 newExpiryTime := time.Now().Add(time.Hour * 24).Format(time.RFC3339)
104108
105- if existingSessionID > 0 {
109+ if existingSessionID != "" {
106110 // Update the session if it exists
107111 _, err = a.DB.Exec("UPDATE sessions SET expires = ?, is_online = ? WHERE id = ?", newExpiryTime, true, existingSessionID)
108112 if err != nil {
@@ -6,8 +6,13 @@ import (
6 6
7 "github.com/bnema/gordon/internal/app"7 "github.com/bnema/gordon/internal/app"
8 "github.com/bnema/gordon/internal/db"8 "github.com/bnema/gordon/internal/db"
9+ "github.com/google/uuid"
9 )10 )
10 11
12+func generateUUID() string {
13+ return uuid.New().String()
14+}
15+
11 // CreateUser creates a new user along with the associated account, provider, and session.16 // CreateUser creates a new user along with the associated account, provider, and session.
12 func CreateUser(a *app.App, accessToken string, browserInfo string) error {17 func CreateUser(a *app.App, accessToken string, browserInfo string) error {
13 18
@@ -23,23 +28,19 @@ func CreateUser(a *app.App, accessToken string, browserInfo string) error {
23 28
24 user := db.User{}29 user := db.User{}
25 account := db.Account{}30 account := db.Account{}
26- // Step 1: Create an entry in the user table
27- // TODO : obtain the user name and email from the GitHub API
28- result, err := a.DB.Exec("INSERT INTO user (name, email) VALUES (?, ?)", "admin", "admin@gordon")
29- if err != nil {
30- return err
31- }
32 31
33- // Get the last insert ID from the result of the INSERT operation32+ // Step 1: Create a user
34- user.ID, err = result.LastInsertId()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)
35 if err != nil {39 if err != nil {
36- return fmt.Errorf("error while fetching LastInsertId: %w", err)40+ return err
37 }41 }
38 42
39- fmt.Printf("User ID: %d\n", user.ID)
40-
41 a.DBTables.User.ID = user.ID43 a.DBTables.User.ID = user.ID
42-
43 // Step 2: Create an account for the user with the user ID44 // Step 2: Create an account for the user with the user ID
44 account.ID, err = createAccount(a)45 account.ID, err = createAccount(a)
45 if err != nil {46 if err != nil {
@@ -58,35 +59,38 @@ func CreateUser(a *app.App, accessToken string, browserInfo string) error {
58 return nil59 return nil
59 }60 }
60 61
61-func createAccount(a *app.App) (int64, error) {62+func createAccount(a *app.App) (string, error) {
62- fmt.Println("Whats the value of a.DBTables.User.ID?", a.DBTables.User.ID)
63 account := a.DBTables.Account63 account := a.DBTables.Account
64- queryInsertAccount := "INSERT INTO account (user_id) VALUES (?) RETURNING id"64+ // Generate a new UUID for the account
65- // Execute the INSERT operation65+ account.ID = generateUUID()
66- result, err := a.DB.Exec(queryInsertAccount, a.DBTables.User.ID)
67- if err != nil {
68- return 0, fmt.Errorf("error while inserting into account: %w", err)
69- }
70 66
71- // Get the last inserted ID67+ // Insert the account into the database
72- account.ID, err = result.LastInsertId()68+ _, err := a.DB.Exec("INSERT INTO account (id, user_id) VALUES (?, ?)", account.ID, a.DBTables.User.ID)
73 if err != nil {69 if err != nil {
74- return 0, fmt.Errorf("error while fetching LastInsertId for account: %w", err)70+ return "", err
75 }71 }
76-72+ a.DBTables.Account.ID = account.ID
77 return account.ID, nil73 return account.ID, nil
78 }74 }
79 75
80 func createProvider(a *app.App, accessToken string) error {76 func createProvider(a *app.App, accessToken string) error {
81- queryInsertProvider := "INSERT INTO provider (account_id, name, access_token, refresh_token, expires) VALUES (?, ?, ?, ?, ?)"77+ provider := a.DBTables.Provider
82- _, err := a.DB.Exec(queryInsertProvider, a.DBTables.Account.ID, "GitHub", accessToken, "refreshToken", time.Now().Add(time.Hour*24).Format(time.RFC3339))78+ provider.ID = generateUUID()
79+
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))
83 return err82 return err
84 }83 }
85 84
86 // createSession creates a session for the user.85 // createSession creates a session for the user.
87 func createSession(a *app.App, browserInfo string) error {86 func createSession(a *app.App, browserInfo string) error {
88- queryInsertSession := "INSERT INTO sessions (account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?)"87+ sessions := a.DBTables.Sessions
89- _, err := a.DB.Exec(queryInsertSession, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true)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)
92+
93+ a.DBTables.Sessions.ID = sessions.ID
90 return err94 return err
91 }95 }
92 96
@@ -102,7 +106,7 @@ func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) e
102 // Calculate the new expiry time106 // Calculate the new expiry time
103 newExpiryTime := time.Now().Add(time.Hour * 24).Format(time.RFC3339)107 newExpiryTime := time.Now().Add(time.Hour * 24).Format(time.RFC3339)
104 108
105- if existingSessionID > 0 {109+ if existingSessionID != "" {
106 // Update the session if it exists110 // Update the session if it exists
107 _, err = a.DB.Exec("UPDATE sessions SET expires = ?, is_online = ? WHERE id = ?", newExpiryTime, true, existingSessionID)111 _, err = a.DB.Exec("UPDATE sessions SET expires = ?, is_online = ? WHERE id = ?", newExpiryTime, true, existingSessionID)
108 if err != nil {112 if err != nil {
M internal/db/struct.go +7 -7
@@ -1,30 +1,30 @@
11 package db
22
33 type User struct {
4- ID int64 `sql:"id, primary_key"`
4+ ID string `sql:"id, primary_key"`
55 Name string `sql:"name"`
66 Email string `sql:"email"`
77 Account Account
88 }
99
1010 type Account struct {
11- ID int64 `sql:"id, primary_key"`
12- UserID int64 `sql:"user_id, foreign_key=user.id"`
11+ ID string `sql:"id, primary_key"`
12+ UserID string `sql:"user_id, foreign_key=user.id"`
1313 Sessions []Sessions
1414 Providers []Provider
1515 }
1616
1717 type Sessions struct {
18- ID int64 `sql:"id, primary_key"`
19- AccountID int64 `sql:"account_id, foreign_key=account.id"`
18+ ID string `sql:"id, primary_key"`
19+ AccountID string `sql:"account_id, foreign_key=account.id"`
2020 BrowserInfo string `sql:"browser_info"`
2121 Expires string `sql:"expires"`
2222 IsOnline bool `sql:"is_online"`
2323 }
2424
2525 type Provider struct {
26- ID int64 `sql:"id, primary_key"`
27- AccountID int64 `sql:"account_id, foreign_key=account.id"`
26+ ID string `sql:"id, primary_key"`
27+ AccountID string `sql:"account_id, foreign_key=account.id"`
2828 Name string `sql:"name"`
2929 AccessToken string `sql:"access_token"`
3030 RefreshToken string `sql:"refresh_token"`
@@ -1,30 +1,30 @@
1 package db1 package db
2 2
3 type User struct {3 type User struct {
4- ID int64 `sql:"id, primary_key"`4+ ID string `sql:"id, primary_key"`
5 Name string `sql:"name"`5 Name string `sql:"name"`
6 Email string `sql:"email"`6 Email string `sql:"email"`
7 Account Account7 Account Account
8 }8 }
9 9
10 type Account struct {10 type Account struct {
11- ID int64 `sql:"id, primary_key"`11+ ID string `sql:"id, primary_key"`
12- UserID int64 `sql:"user_id, foreign_key=user.id"`12+ UserID string `sql:"user_id, foreign_key=user.id"`
13 Sessions []Sessions13 Sessions []Sessions
14 Providers []Provider14 Providers []Provider
15 }15 }
16 16
17 type Sessions struct {17 type Sessions struct {
18- ID int64 `sql:"id, primary_key"`18+ ID string `sql:"id, primary_key"`
19- AccountID int64 `sql:"account_id, foreign_key=account.id"`19+ AccountID string `sql:"account_id, foreign_key=account.id"`
20 BrowserInfo string `sql:"browser_info"`20 BrowserInfo string `sql:"browser_info"`
21 Expires string `sql:"expires"`21 Expires string `sql:"expires"`
22 IsOnline bool `sql:"is_online"`22 IsOnline bool `sql:"is_online"`
23 }23 }
24 24
25 type Provider struct {25 type Provider struct {
26- ID int64 `sql:"id, primary_key"`26+ ID string `sql:"id, primary_key"`
27- AccountID int64 `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 RefreshToken string `sql:"refresh_token"`
M internal/httpserve/handler/oauth.go +3 -0
@@ -122,6 +122,9 @@ func OAuthCallback(c echo.Context, a *app.App) error {
122122
123123 browserInfo := c.Request().UserAgent()
124124
125+ sessStr := fmt.Sprintf("%v", sess)
126+ fmt.Println(sessStr)
127+
125128 // Check if the user already exists in the database
126129 userExists, err := queries.UserExists(a)
127130 if err != nil {
@@ -122,6 +122,9 @@ func OAuthCallback(c echo.Context, a *app.App) error {
122 122
123 browserInfo := c.Request().UserAgent()123 browserInfo := c.Request().UserAgent()
124 124
125+ sessStr := fmt.Sprintf("%v", sess)
126+ fmt.Println(sessStr)
127+
125 // Check if the user already exists in the database128 // Check if the user already exists in the database
126 userExists, err := queries.UserExists(a)129 userExists, err := queries.UserExists(a)
127 if err != nil {130 if err != nil {
M pkg/sqlite/createdb.go +2 -5
@@ -26,9 +26,8 @@ func CreateTable(db *sql.DB, table interface{}, tableName string) error {
2626 constraint := ""
2727
2828 for _, part := range parts[1:] {
29- fmt.Println("Inspecting part:", part)
3029 if part == "primary_key" {
31- constraint = "INTEGER PRIMARY KEY AUTOINCREMENT"
30+ constraint = "PRIMARY KEY"
3231 } else if strings.HasPrefix(strings.TrimSpace(part), "foreign_key=") {
3332 fkParts := strings.Split(part, "=")
3433 refTableAndField := strings.Split(fkParts[1], ".")
@@ -37,7 +36,6 @@ func CreateTable(db *sql.DB, table interface{}, tableName string) error {
3736 } else if len(refTableAndField) == 2 {
3837 foreignKeys = append(foreignKeys, fmt.Sprintf("FOREIGN KEY (%s) REFERENCES %s(%s)", sqlName, refTableAndField[0], refTableAndField[1]))
3938 }
40- fmt.Println("Entered foreign_key block")
4139 }
4240 }
4341
@@ -52,7 +50,6 @@ func CreateTable(db *sql.DB, table interface{}, tableName string) error {
5250 createTableSQL := fmt.Sprintf("CREATE TABLE %s (%s", tableName, strings.Join(fields, ", "))
5351 if len(foreignKeys) > 0 {
5452 createTableSQL += ", " + strings.Join(foreignKeys, ", ")
55- fmt.Printf("Table %s linked with: %s\n", tableName, strings.Join(foreignKeys, ", "))
5653 }
5754 createTableSQL += ");"
5855
@@ -69,7 +66,7 @@ func convertGoTypeToSQLType(goType string) string {
6966 case "int64":
7067 return "INTEGER"
7168 case "string":
72- return "TEXT NOT NULL"
69+ return "TEXT"
7370 case "bool":
7471 return "BOOLEAN"
7572 default:
@@ -26,9 +26,8 @@ func CreateTable(db *sql.DB, table interface{}, tableName string) error {
26 constraint := ""26 constraint := ""
27 27
28 for _, part := range parts[1:] {28 for _, part := range parts[1:] {
29- fmt.Println("Inspecting part:", part)
30 if part == "primary_key" {29 if part == "primary_key" {
31- constraint = "INTEGER PRIMARY KEY AUTOINCREMENT"30+ constraint = "PRIMARY KEY"
32 } else if strings.HasPrefix(strings.TrimSpace(part), "foreign_key=") {31 } else if strings.HasPrefix(strings.TrimSpace(part), "foreign_key=") {
33 fkParts := strings.Split(part, "=")32 fkParts := strings.Split(part, "=")
34 refTableAndField := strings.Split(fkParts[1], ".")33 refTableAndField := strings.Split(fkParts[1], ".")
@@ -37,7 +36,6 @@ func CreateTable(db *sql.DB, table interface{}, tableName string) error {
37 } else if len(refTableAndField) == 2 {36 } else if len(refTableAndField) == 2 {
38 foreignKeys = append(foreignKeys, fmt.Sprintf("FOREIGN KEY (%s) REFERENCES %s(%s)", sqlName, refTableAndField[0], refTableAndField[1]))37 foreignKeys = append(foreignKeys, fmt.Sprintf("FOREIGN KEY (%s) REFERENCES %s(%s)", sqlName, refTableAndField[0], refTableAndField[1]))
39 }38 }
40- fmt.Println("Entered foreign_key block")
41 }39 }
42 }40 }
43 41
@@ -52,7 +50,6 @@ func CreateTable(db *sql.DB, table interface{}, tableName string) error {
52 createTableSQL := fmt.Sprintf("CREATE TABLE %s (%s", tableName, strings.Join(fields, ", "))50 createTableSQL := fmt.Sprintf("CREATE TABLE %s (%s", tableName, strings.Join(fields, ", "))
53 if len(foreignKeys) > 0 {51 if len(foreignKeys) > 0 {
54 createTableSQL += ", " + strings.Join(foreignKeys, ", ")52 createTableSQL += ", " + strings.Join(foreignKeys, ", ")
55- fmt.Printf("Table %s linked with: %s\n", tableName, strings.Join(foreignKeys, ", "))
56 }53 }
57 createTableSQL += ");"54 createTableSQL += ");"
58 55
@@ -69,7 +66,7 @@ func convertGoTypeToSQLType(goType string) string {
69 case "int64":66 case "int64":
70 return "INTEGER"67 return "INTEGER"
71 case "string":68 case "string":
72- return "TEXT NOT NULL"69+ return "TEXT"
73 case "bool":70 case "bool":
74 return "BOOLEAN"71 return "BOOLEAN"
75 default:72 default:

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