Skip to content

Commit 1899189

Browse files
authored
Merge branch 'main' into go-gitea#17676-init-content-history
2 parents 547cee9 + 8244cfb commit 1899189

37 files changed

+1062
-113
lines changed

.air.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ bin = "gitea"
77
include_ext = ["go", "tmpl"]
88
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
99
include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]
10+
exclude_regex = ["_test.go$"]

.eslintrc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ plugins:
1313
- eslint-plugin-import
1414
- eslint-plugin-vue
1515
- eslint-plugin-html
16+
- eslint-plugin-github
1617

1718
extends:
1819
- plugin:vue/recommended
@@ -96,6 +97,23 @@ rules:
9697
function-paren-newline: [0]
9798
generator-star-spacing: [0]
9899
getter-return: [2]
100+
github/array-foreach: [2]
101+
github/async-currenttarget: [2]
102+
github/async-preventdefault: [2]
103+
github/authenticity-token: [0]
104+
github/get-attribute: [2]
105+
github/js-class-name: [0]
106+
github/no-blur: [0]
107+
github/no-d-none: [0]
108+
github/no-dataset: [2]
109+
github/no-implicit-buggy-globals: [0]
110+
github/no-inner-html: [0]
111+
github/no-innerText: [2]
112+
github/no-then: [2]
113+
github/no-useless-passive: [2]
114+
github/prefer-observers: [0]
115+
github/require-passive-events: [2]
116+
github/unescaped-html-literal: [0]
99117
grouped-accessor-pairs: [2]
100118
guard-for-in: [0]
101119
id-blacklist: [0]

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ lint: lint-frontend lint-backend
328328

329329
.PHONY: lint-frontend
330330
lint-frontend: node_modules
331-
npx eslint --color --max-warnings=0 web_src/js build templates *.config.js
331+
npx eslint --color --max-warnings=0 web_src/js build templates *.config.js docs/assets/js
332332
npx stylelint --color --max-warnings=0 web_src/less
333333
npx editorconfig-checker templates
334334

build/generate-images.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,5 @@ async function main() {
8080
]);
8181
}
8282

83-
main().then(exit).catch(exit);
83+
main().then(exit).catch(exit); // eslint-disable-line github/no-then
8484

build/generate-svg.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ async function main() {
5454
]);
5555
}
5656

57-
main().then(exit).catch(exit);
57+
main().then(exit).catch(exit); // eslint-disable-line github/no-then
5858

cmd/web_https.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"code.gitea.io/gitea/modules/graceful"
1414
"code.gitea.io/gitea/modules/log"
1515
"code.gitea.io/gitea/modules/setting"
16+
1617
"github.com/klauspost/cpuid/v2"
1718
)
1819

docs/assets/js/search.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const fuseOptions = {
1515
shouldSort: true,
1616
includeMatches: true,
1717
matchAllTokens: true,
18-
threshold: 0.0, // for parsing diacritics
18+
threshold: 0, // for parsing diacritics
1919
tokenize: true,
2020
location: 0,
2121
distance: 100,
@@ -52,25 +52,25 @@ function doSearch() {
5252
executeSearch(searchQuery);
5353
} else {
5454
const para = document.createElement('P');
55-
para.innerText = 'Please enter a word or phrase above';
55+
para.textContent = 'Please enter a word or phrase above';
5656
document.getElementById('search-results').appendChild(para);
5757
}
5858
}
5959

6060
function getJSON(url, fn) {
6161
const request = new XMLHttpRequest();
6262
request.open('GET', url, true);
63-
request.onload = function () {
63+
request.addEventListener('load', () => {
6464
if (request.status >= 200 && request.status < 400) {
6565
const data = JSON.parse(request.responseText);
6666
fn(data);
6767
} else {
6868
console.error(`Target reached on ${url} with error ${request.status}`);
6969
}
70-
};
71-
request.onerror = function () {
70+
});
71+
request.addEventListener('error', () => {
7272
console.error(`Connection error ${request.status}`);
73-
};
73+
});
7474
request.send();
7575
}
7676

@@ -84,20 +84,20 @@ function executeSearch(searchQuery) {
8484
populateResults(result);
8585
} else {
8686
const para = document.createElement('P');
87-
para.innerText = 'No matches found';
87+
para.textContent = 'No matches found';
8888
document.getElementById('search-results').appendChild(para);
8989
}
9090
});
9191
}
9292

9393
function populateResults(result) {
94-
result.forEach((value, key) => {
94+
for (const [key, value] of result.entries()) {
9595
const content = value.item.contents;
9696
let snippet = '';
9797
const snippetHighlights = [];
9898
if (fuseOptions.tokenize) {
9999
snippetHighlights.push(searchQuery);
100-
value.matches.forEach((mvalue) => {
100+
for (const mvalue of value.matches) {
101101
if (mvalue.key === 'tags' || mvalue.key === 'categories') {
102102
snippetHighlights.push(mvalue.value);
103103
} else if (mvalue.key === 'contents') {
@@ -111,7 +111,7 @@ function populateResults(result) {
111111
snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1));
112112
}
113113
}
114-
});
114+
}
115115
}
116116

117117
if (snippet.length < 1) {
@@ -130,10 +130,10 @@ function populateResults(result) {
130130
});
131131
document.getElementById('search-results').appendChild(htmlToElement(output));
132132

133-
snippetHighlights.forEach((snipvalue) => {
133+
for (const snipvalue of snippetHighlights) {
134134
new Mark(document.getElementById(`summary-${key}`)).mark(snipvalue);
135-
});
136-
});
135+
}
136+
}
137137
}
138138

139139
function render(templateString, data) {

integrations/api_repo_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ func TestAPIRepoMigrate(t *testing.T) {
334334
case "You can not import from disallowed hosts.":
335335
assert.EqualValues(t, "private-ip", testCase.repoName)
336336
default:
337-
assert.Fail(t, "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL)
337+
assert.Failf(t, "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL)
338338
}
339339
} else {
340340
assert.EqualValues(t, testCase.expectedStatus, resp.Code)

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ var migrations = []Migration{
357357
NewMigration("Add table app_state", addTableAppState),
358358
// v201 -> v202
359359
NewMigration("Drop table remote_version (if exists)", dropTableRemoteVersion),
360+
// v202 -> v203
361+
NewMigration("Create key/value table for user settings", createUserSettingsTable),
360362
}
361363

362364
// GetCurrentDBVersion returns the current db version

models/migrations/v202.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
10+
"xorm.io/xorm"
11+
)
12+
13+
func createUserSettingsTable(x *xorm.Engine) error {
14+
type UserSetting struct {
15+
ID int64 `xorm:"pk autoincr"`
16+
UserID int64 `xorm:"index unique(key_userid)"` // to load all of someone's settings
17+
SettingKey string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase
18+
SettingValue string `xorm:"text"`
19+
}
20+
if err := x.Sync2(new(UserSetting)); err != nil {
21+
return fmt.Errorf("sync2: %v", err)
22+
}
23+
return nil
24+
25+
}

models/user.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,7 @@ func DeleteUser(ctx context.Context, u *User) (err error) {
11921192
&TeamUser{UID: u.ID},
11931193
&Collaboration{UserID: u.ID},
11941194
&Stopwatch{UserID: u.ID},
1195+
&user_model.Setting{UserID: u.ID},
11951196
); err != nil {
11961197
return fmt.Errorf("deleteBeans: %v", err)
11971198
}

models/user/main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 The Gitea Authors. All rights reserved.
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
22
// Use of this source code is governed by a MIT-style
33
// license that can be found in the LICENSE file.
44

models/user/setting.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package user
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"strings"
11+
12+
"code.gitea.io/gitea/models/db"
13+
14+
"xorm.io/builder"
15+
)
16+
17+
// Setting is a key value store of user settings
18+
type Setting struct {
19+
ID int64 `xorm:"pk autoincr"`
20+
UserID int64 `xorm:"index unique(key_userid)"` // to load all of someone's settings
21+
SettingKey string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase
22+
SettingValue string `xorm:"text"`
23+
}
24+
25+
// TableName sets the table name for the settings struct
26+
func (s *Setting) TableName() string {
27+
return "user_setting"
28+
}
29+
30+
func init() {
31+
db.RegisterModel(new(Setting))
32+
}
33+
34+
// GetSettings returns specific settings from user
35+
func GetSettings(uid int64, keys []string) (map[string]*Setting, error) {
36+
settings := make([]*Setting, 0, len(keys))
37+
if err := db.GetEngine(db.DefaultContext).
38+
Where("user_id=?", uid).
39+
And(builder.In("setting_key", keys)).
40+
Find(&settings); err != nil {
41+
return nil, err
42+
}
43+
settingsMap := make(map[string]*Setting)
44+
for _, s := range settings {
45+
settingsMap[s.SettingKey] = s
46+
}
47+
return settingsMap, nil
48+
}
49+
50+
// GetUserAllSettings returns all settings from user
51+
func GetUserAllSettings(uid int64) (map[string]*Setting, error) {
52+
settings := make([]*Setting, 0, 5)
53+
if err := db.GetEngine(db.DefaultContext).
54+
Where("user_id=?", uid).
55+
Find(&settings); err != nil {
56+
return nil, err
57+
}
58+
settingsMap := make(map[string]*Setting)
59+
for _, s := range settings {
60+
settingsMap[s.SettingKey] = s
61+
}
62+
return settingsMap, nil
63+
}
64+
65+
// DeleteSetting deletes a specific setting for a user
66+
func DeleteSetting(setting *Setting) error {
67+
_, err := db.GetEngine(db.DefaultContext).Delete(setting)
68+
return err
69+
}
70+
71+
// SetSetting updates a users' setting for a specific key
72+
func SetSetting(setting *Setting) error {
73+
if strings.ToLower(setting.SettingKey) != setting.SettingKey {
74+
return fmt.Errorf("setting key should be lowercase")
75+
}
76+
return upsertSettingValue(setting.UserID, setting.SettingKey, setting.SettingValue)
77+
}
78+
79+
func upsertSettingValue(userID int64, key string, value string) error {
80+
return db.WithTx(func(ctx context.Context) error {
81+
e := db.GetEngine(ctx)
82+
83+
// here we use a general method to do a safe upsert for different databases (and most transaction levels)
84+
// 1. try to UPDATE the record and acquire the transaction write lock
85+
// if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly
86+
// if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist (b) value is not changed
87+
// 2. do a SELECT to check if the row exists or not (we already have the transaction lock)
88+
// 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe)
89+
//
90+
// to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1`
91+
// to make sure the UPDATE always returns a non-zero value for existing (unchanged) records.
92+
93+
res, err := e.Exec("UPDATE user_setting SET setting_value=? WHERE setting_key=? AND user_id=?", value, key, userID)
94+
if err != nil {
95+
return err
96+
}
97+
rows, _ := res.RowsAffected()
98+
if rows > 0 {
99+
// the existing row is updated, so we can return
100+
return nil
101+
}
102+
103+
// in case the value isn't changed, update would return 0 rows changed, so we need this check
104+
has, err := e.Exist(&Setting{UserID: userID, SettingKey: key})
105+
if err != nil {
106+
return err
107+
}
108+
if has {
109+
return nil
110+
}
111+
112+
// if no existing row, insert a new row
113+
_, err = e.Insert(&Setting{UserID: userID, SettingKey: key, SettingValue: value})
114+
return err
115+
})
116+
}

models/user/setting_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package user
6+
7+
import (
8+
"testing"
9+
10+
"code.gitea.io/gitea/models/unittest"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestSettings(t *testing.T) {
16+
keyName := "test_user_setting"
17+
assert.NoError(t, unittest.PrepareTestDatabase())
18+
19+
newSetting := &Setting{UserID: 99, SettingKey: keyName, SettingValue: "Gitea User Setting Test"}
20+
21+
// create setting
22+
err := SetSetting(newSetting)
23+
assert.NoError(t, err)
24+
// test about saving unchanged values
25+
err = SetSetting(newSetting)
26+
assert.NoError(t, err)
27+
28+
// get specific setting
29+
settings, err := GetSettings(99, []string{keyName})
30+
assert.NoError(t, err)
31+
assert.Len(t, settings, 1)
32+
assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue)
33+
34+
// updated setting
35+
updatedSetting := &Setting{UserID: 99, SettingKey: keyName, SettingValue: "Updated"}
36+
err = SetSetting(updatedSetting)
37+
assert.NoError(t, err)
38+
39+
// get all settings
40+
settings, err = GetUserAllSettings(99)
41+
assert.NoError(t, err)
42+
assert.Len(t, settings, 1)
43+
assert.EqualValues(t, updatedSetting.SettingValue, settings[updatedSetting.SettingKey].SettingValue)
44+
45+
// delete setting
46+
err = DeleteSetting(&Setting{UserID: 99, SettingKey: keyName})
47+
assert.NoError(t, err)
48+
settings, err = GetUserAllSettings(99)
49+
assert.NoError(t, err)
50+
assert.Len(t, settings, 0)
51+
}

modules/avatar/identicon/identicon_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// license that can be found in the LICENSE file.
44

55
//go:build test_avatar_identicon
6-
// +build test_avatar_identicon
6+
// +build test_avatar_identicon
77

88
package identicon
99

0 commit comments

Comments
 (0)