mirror of
https://github.com/go-gitea/gitea.git
synced 2024-07-01 02:05:30 +00:00
Compare commits
10 Commits
cae718015f
...
a08b085c2b
Author | SHA1 | Date | |
---|---|---|---|
|
a08b085c2b | ||
|
4b6eb46e69 | ||
|
0f09c22663 | ||
|
25f3ec5b65 | ||
|
597d1da96b | ||
|
f5dfd7d73c | ||
|
129206da45 | ||
|
eb00a7e733 | ||
|
1b3b35def8 | ||
|
fe2d0f8c94 |
|
@ -156,6 +156,14 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter users by their notification preference.
|
||||||
|
// At this point we exclude:
|
||||||
|
// user that don't have all notifications enabled or users only get notification on mention and this is one ...
|
||||||
|
if !(user.UINotificationsPreference == user_model.NotificationsEnabled ||
|
||||||
|
user.UINotificationsPreference == user_model.NotificationsOnMention) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if notificationExists(notifications, issue.ID, userID) {
|
if notificationExists(notifications, issue.ID, userID) {
|
||||||
if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil {
|
if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
email: user1@example.com
|
email: user1@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
email: user2@example.com
|
email: user2@example.com
|
||||||
keep_email_private: true
|
keep_email_private: true
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -82,6 +84,7 @@
|
||||||
email: org3@example.com
|
email: org3@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
|
ui_notifications_preference: onmention
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -119,6 +122,7 @@
|
||||||
email: user4@example.com
|
email: user4@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
|
ui_notifications_preference: onmention
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -156,6 +160,7 @@
|
||||||
email: user5@example.com
|
email: user5@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -193,6 +198,7 @@
|
||||||
email: org6@example.com
|
email: org6@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -230,6 +236,7 @@
|
||||||
email: org7@example.com
|
email: org7@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: disabled
|
email_notifications_preference: disabled
|
||||||
|
ui_notifications_preference: disabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -267,6 +274,7 @@
|
||||||
email: user8@example.com
|
email: user8@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -304,6 +312,7 @@
|
||||||
email: user9@example.com
|
email: user9@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
|
ui_notifications_preference: onmention
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -341,6 +350,7 @@
|
||||||
email: user10@example.com
|
email: user10@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -378,6 +388,7 @@
|
||||||
email: user11@example.com
|
email: user11@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -415,6 +426,7 @@
|
||||||
email: user12@example.com
|
email: user12@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -452,6 +464,7 @@
|
||||||
email: user13@example.com
|
email: user13@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -489,6 +502,7 @@
|
||||||
email: user14@example.com
|
email: user14@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -526,6 +540,7 @@
|
||||||
email: user15@example.com
|
email: user15@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -563,6 +578,7 @@
|
||||||
email: user16@example.com
|
email: user16@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -600,6 +616,7 @@
|
||||||
email: org17@example.com
|
email: org17@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -637,6 +654,7 @@
|
||||||
email: user18@example.com
|
email: user18@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -674,6 +692,7 @@
|
||||||
email: org19@example.com
|
email: org19@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -711,6 +730,7 @@
|
||||||
email: user20@example.com
|
email: user20@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -748,6 +768,7 @@
|
||||||
email: user21@example.com
|
email: user21@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -785,6 +806,7 @@
|
||||||
email: limited_org@example.com
|
email: limited_org@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -822,6 +844,7 @@
|
||||||
email: privated_org@example.com
|
email: privated_org@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -859,6 +882,7 @@
|
||||||
email: user24@example.com
|
email: user24@example.com
|
||||||
keep_email_private: true
|
keep_email_private: true
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -896,6 +920,7 @@
|
||||||
email: org25@example.com
|
email: org25@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -933,6 +958,7 @@
|
||||||
email: org26@example.com
|
email: org26@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
|
ui_notifications_preference: onmention
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -970,6 +996,7 @@
|
||||||
email: user27@example.com
|
email: user27@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1007,6 +1034,7 @@
|
||||||
email: user28@example.com
|
email: user28@example.com
|
||||||
keep_email_private: true
|
keep_email_private: true
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1044,6 +1072,7 @@
|
||||||
email: user29@example.com
|
email: user29@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1081,6 +1110,7 @@
|
||||||
email: user30@example.com
|
email: user30@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1118,6 +1148,7 @@
|
||||||
email: user31@example.com
|
email: user31@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1155,6 +1186,7 @@
|
||||||
email: user32@example.com
|
email: user32@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:notpassword
|
passwd: ZogKvWdyEx:notpassword
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1192,6 +1224,7 @@
|
||||||
email: user33@example.com
|
email: user33@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1230,6 +1263,7 @@
|
||||||
email: user34@example.com
|
email: user34@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1267,6 +1301,7 @@
|
||||||
email: private_org35@example.com
|
email: private_org35@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1304,6 +1339,7 @@
|
||||||
email: abcde@gitea.com
|
email: abcde@gitea.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1341,6 +1377,7 @@
|
||||||
email: user37@example.com
|
email: user37@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1378,6 +1415,7 @@
|
||||||
email: user38@example.com
|
email: user38@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1415,6 +1453,7 @@
|
||||||
email: user39@example.com
|
email: user39@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
|
ui_notifications_preference: enabled
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1452,6 +1491,7 @@
|
||||||
email: user40@example.com
|
email: user40@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
|
ui_notifications_preference: onmention
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
@ -1489,6 +1529,7 @@
|
||||||
email: org41@example.com
|
email: org41@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
|
ui_notifications_preference: onmention
|
||||||
passwd: ZogKvWdyEx:password
|
passwd: ZogKvWdyEx:password
|
||||||
passwd_hash_algo: dummy
|
passwd_hash_algo: dummy
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
|
|
|
@ -61,15 +61,16 @@ const (
|
||||||
UserTypeRemoteUser
|
UserTypeRemoteUser
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Constants used as user-setting for both Email and UI notifications.
|
||||||
const (
|
const (
|
||||||
// EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own
|
// NotificationsEnabled indicates that the user would like to receive all notifications except your own
|
||||||
EmailNotificationsEnabled = "enabled"
|
NotificationsEnabled = "enabled"
|
||||||
// EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned.
|
// NotificationsOnMention indicates that the user would like to be notified when mentioned.
|
||||||
EmailNotificationsOnMention = "onmention"
|
NotificationsOnMention = "onmention"
|
||||||
// EmailNotificationsDisabled indicates that the user would not like to be notified via email.
|
// NotificationsDisabled indicates that the user would not like to be notified.
|
||||||
EmailNotificationsDisabled = "disabled"
|
NotificationsDisabled = "disabled"
|
||||||
// EmailNotificationsAndYourOwn indicates that the user would like to receive all email notifications and your own
|
// NotificationsAndYourOwn indicates that the user would like to receive all notifications and their own
|
||||||
EmailNotificationsAndYourOwn = "andyourown"
|
NotificationsAndYourOwn = "andyourown"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User represents the object of individual and member of organization.
|
// User represents the object of individual and member of organization.
|
||||||
|
@ -82,6 +83,7 @@ type User struct {
|
||||||
Email string `xorm:"NOT NULL"`
|
Email string `xorm:"NOT NULL"`
|
||||||
KeepEmailPrivate bool
|
KeepEmailPrivate bool
|
||||||
EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
|
EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
|
||||||
|
UINotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
|
||||||
Passwd string `xorm:"NOT NULL"`
|
Passwd string `xorm:"NOT NULL"`
|
||||||
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
|
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
|
||||||
|
|
||||||
|
@ -578,6 +580,7 @@ type CreateUserOverwriteOptions struct {
|
||||||
Visibility *structs.VisibleType
|
Visibility *structs.VisibleType
|
||||||
AllowCreateOrganization optional.Option[bool]
|
AllowCreateOrganization optional.Option[bool]
|
||||||
EmailNotificationsPreference *string
|
EmailNotificationsPreference *string
|
||||||
|
UINotificationsPreference *string
|
||||||
MaxRepoCreation *int
|
MaxRepoCreation *int
|
||||||
Theme *string
|
Theme *string
|
||||||
IsRestricted optional.Option[bool]
|
IsRestricted optional.Option[bool]
|
||||||
|
@ -605,6 +608,8 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
|
||||||
u.Visibility = setting.Service.DefaultUserVisibilityMode
|
u.Visibility = setting.Service.DefaultUserVisibilityMode
|
||||||
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
|
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
|
||||||
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
|
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
|
||||||
|
u.UINotificationsPreference = setting.Admin.DefaultUINotification
|
||||||
|
|
||||||
u.MaxRepoCreation = -1
|
u.MaxRepoCreation = -1
|
||||||
u.Theme = setting.UI.DefaultTheme
|
u.Theme = setting.UI.DefaultTheme
|
||||||
u.IsRestricted = setting.Service.DefaultUserIsRestricted
|
u.IsRestricted = setting.Service.DefaultUserIsRestricted
|
||||||
|
@ -630,6 +635,9 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
|
||||||
if overwrite.EmailNotificationsPreference != nil {
|
if overwrite.EmailNotificationsPreference != nil {
|
||||||
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
|
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
|
||||||
}
|
}
|
||||||
|
if overwrite.UINotificationsPreference != nil {
|
||||||
|
u.UINotificationsPreference = *overwrite.UINotificationsPreference
|
||||||
|
}
|
||||||
if overwrite.MaxRepoCreation != nil {
|
if overwrite.MaxRepoCreation != nil {
|
||||||
u.MaxRepoCreation = *overwrite.MaxRepoCreation
|
u.MaxRepoCreation = *overwrite.MaxRepoCreation
|
||||||
}
|
}
|
||||||
|
@ -924,7 +932,7 @@ func GetUserEmailsByNames(ctx context.Context, names []string) []string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if u.IsMailable() && u.EmailNotificationsPreference != EmailNotificationsDisabled {
|
if u.IsMailable() && u.EmailNotificationsPreference != NotificationsDisabled {
|
||||||
mails = append(mails, u.Email)
|
mails = append(mails, u.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -944,7 +952,7 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([
|
||||||
Where("`type` = ?", UserTypeIndividual).
|
Where("`type` = ?", UserTypeIndividual).
|
||||||
And("`prohibit_login` = ?", false).
|
And("`prohibit_login` = ?", false).
|
||||||
And("`is_active` = ?", true).
|
And("`is_active` = ?", true).
|
||||||
In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsOnMention, EmailNotificationsAndYourOwn).
|
In("`email_notifications_preference`", NotificationsEnabled, NotificationsOnMention, NotificationsAndYourOwn).
|
||||||
Find(&ous)
|
Find(&ous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -953,7 +961,7 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([
|
||||||
Where("`type` = ?", UserTypeIndividual).
|
Where("`type` = ?", UserTypeIndividual).
|
||||||
And("`prohibit_login` = ?", false).
|
And("`prohibit_login` = ?", false).
|
||||||
And("`is_active` = ?", true).
|
And("`is_active` = ?", true).
|
||||||
In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsAndYourOwn).
|
In("`email_notifications_preference`", NotificationsEnabled, NotificationsAndYourOwn).
|
||||||
Find(&ous)
|
Find(&ous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,21 +137,43 @@ func TestEmailNotificationPreferences(t *testing.T) {
|
||||||
expected string
|
expected string
|
||||||
userID int64
|
userID int64
|
||||||
}{
|
}{
|
||||||
{user_model.EmailNotificationsEnabled, 1},
|
{user_model.NotificationsEnabled, 1},
|
||||||
{user_model.EmailNotificationsEnabled, 2},
|
{user_model.NotificationsEnabled, 2},
|
||||||
{user_model.EmailNotificationsOnMention, 3},
|
{user_model.NotificationsOnMention, 3},
|
||||||
{user_model.EmailNotificationsOnMention, 4},
|
{user_model.NotificationsOnMention, 4},
|
||||||
{user_model.EmailNotificationsEnabled, 5},
|
{user_model.NotificationsEnabled, 5},
|
||||||
{user_model.EmailNotificationsEnabled, 6},
|
{user_model.NotificationsEnabled, 6},
|
||||||
{user_model.EmailNotificationsDisabled, 7},
|
{user_model.NotificationsDisabled, 7},
|
||||||
{user_model.EmailNotificationsEnabled, 8},
|
{user_model.NotificationsEnabled, 8},
|
||||||
{user_model.EmailNotificationsOnMention, 9},
|
{user_model.NotificationsOnMention, 9},
|
||||||
} {
|
} {
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.userID})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.userID})
|
||||||
assert.Equal(t, test.expected, user.EmailNotificationsPreference)
|
assert.Equal(t, test.expected, user.EmailNotificationsPreference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUINotificationPreferences(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
expected string
|
||||||
|
userID int64
|
||||||
|
}{
|
||||||
|
{user_model.NotificationsEnabled, 1},
|
||||||
|
{user_model.NotificationsEnabled, 2},
|
||||||
|
{user_model.NotificationsOnMention, 3},
|
||||||
|
{user_model.NotificationsOnMention, 4},
|
||||||
|
{user_model.NotificationsEnabled, 5},
|
||||||
|
{user_model.NotificationsEnabled, 6},
|
||||||
|
{user_model.NotificationsDisabled, 7},
|
||||||
|
{user_model.NotificationsEnabled, 8},
|
||||||
|
{user_model.NotificationsOnMention, 9},
|
||||||
|
} {
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.userID})
|
||||||
|
assert.Equal(t, test.expected, user.UINotificationsPreference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHashPasswordDeterministic(t *testing.T) {
|
func TestHashPasswordDeterministic(t *testing.T) {
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
u := &user_model.User{}
|
u := &user_model.User{}
|
||||||
|
|
|
@ -4,12 +4,67 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/text/collate"
|
"golang.org/x/text/collate"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func naturalSortGetRune(str string, pos int) (r rune, size int, has bool) {
|
||||||
|
if pos >= len(str) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
r, size = utf8.DecodeRuneInString(str[pos:])
|
||||||
|
if r == utf8.RuneError {
|
||||||
|
r, size = rune(str[pos]), 1 // if invalid input, treat it as a single byte ascii
|
||||||
|
}
|
||||||
|
return r, size, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func naturalSortAdvance(str string, pos int) (end int, isNumber bool) {
|
||||||
|
end = pos
|
||||||
|
for {
|
||||||
|
r, size, has := naturalSortGetRune(str, end)
|
||||||
|
if !has {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
isCurRuneNum := '0' <= r && r <= '9'
|
||||||
|
if end == pos {
|
||||||
|
isNumber = isCurRuneNum
|
||||||
|
end += size
|
||||||
|
} else if isCurRuneNum == isNumber {
|
||||||
|
end += size
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end, isNumber
|
||||||
|
}
|
||||||
|
|
||||||
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
||||||
func NaturalSortLess(s1, s2 string) bool {
|
func NaturalSortLess(s1, s2 string) bool {
|
||||||
|
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
|
||||||
|
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
|
||||||
|
// So we need to handle the number parts by ourselves
|
||||||
c := collate.New(language.English, collate.Numeric)
|
c := collate.New(language.English, collate.Numeric)
|
||||||
return c.CompareString(s1, s2) < 0
|
pos1, pos2 := 0, 0
|
||||||
|
for pos1 < len(s1) && pos2 < len(s2) {
|
||||||
|
end1, isNum1 := naturalSortAdvance(s1, pos1)
|
||||||
|
end2, isNum2 := naturalSortAdvance(s2, pos2)
|
||||||
|
part1, part2 := s1[pos1:end1], s2[pos2:end2]
|
||||||
|
if isNum1 && isNum2 {
|
||||||
|
if part1 != part2 {
|
||||||
|
if len(part1) != len(part2) {
|
||||||
|
return len(part1) < len(part2)
|
||||||
|
}
|
||||||
|
return part1 < part2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cmp := c.CompareString(part1, part2); cmp != 0 {
|
||||||
|
return cmp < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos1, pos2 = end1, end2
|
||||||
|
}
|
||||||
|
return len(s1) < len(s2)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,36 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNaturalSortLess(t *testing.T) {
|
func TestNaturalSortLess(t *testing.T) {
|
||||||
test := func(s1, s2 string, less bool) {
|
testLess := func(s1, s2 string) {
|
||||||
assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2)
|
assert.True(t, NaturalSortLess(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||||
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
|
}
|
||||||
|
testEqual := func(s1, s2 string) {
|
||||||
|
assert.False(t, NaturalSortLess(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
}
|
}
|
||||||
test("v1.20.0", "v1.2.0", false)
|
|
||||||
test("v1.20.0", "v1.29.0", true)
|
|
||||||
test("v1.20.0", "v1.20.0", false)
|
|
||||||
test("abc", "bcd", true)
|
|
||||||
test("a-1-a", "a-1-b", true)
|
|
||||||
test("2", "12", true)
|
|
||||||
test("a", "ab", true)
|
|
||||||
|
|
||||||
test("A", "b", true)
|
testEqual("", "")
|
||||||
test("a", "B", true)
|
testLess("", "a")
|
||||||
|
testLess("", "1")
|
||||||
|
|
||||||
test("cafe", "café", true)
|
testLess("v1.2", "v1.2.0")
|
||||||
test("café", "cafe", false)
|
testLess("v1.2.0", "v1.10.0")
|
||||||
test("caff", "café", false)
|
testLess("v1.20.0", "v1.29.0")
|
||||||
|
testEqual("v1.20.0", "v1.20.0")
|
||||||
|
|
||||||
|
testLess("a", "A")
|
||||||
|
testLess("a", "B")
|
||||||
|
testLess("A", "b")
|
||||||
|
testLess("A", "ab")
|
||||||
|
|
||||||
|
testLess("abc", "bcd")
|
||||||
|
testLess("a-1-a", "a-1-b")
|
||||||
|
testLess("2", "12")
|
||||||
|
|
||||||
|
testLess("cafe", "café")
|
||||||
|
testLess("café", "caff")
|
||||||
|
|
||||||
|
testLess("A-2", "A-11")
|
||||||
|
testLess("0.txt", "1.txt")
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,10 +86,10 @@ type RenderContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Links struct {
|
type Links struct {
|
||||||
AbsolutePrefix bool
|
AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias
|
||||||
Base string
|
Base string // base prefix for pre-provided links and medias (images, videos)
|
||||||
BranchPath string
|
BranchPath string // actually it is the ref path, eg: "branch/features/feat-12", "tag/v1.0"
|
||||||
TreePath string
|
TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Links) Prefix() string {
|
func (l *Links) Prefix() string {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
var Admin struct {
|
var Admin struct {
|
||||||
DisableRegularOrgCreation bool
|
DisableRegularOrgCreation bool
|
||||||
DefaultEmailNotification string
|
DefaultEmailNotification string
|
||||||
|
DefaultUINotification string
|
||||||
UserDisabledFeatures container.Set[string]
|
UserDisabledFeatures container.Set[string]
|
||||||
ExternalUserDisableFeatures container.Set[string]
|
ExternalUserDisableFeatures container.Set[string]
|
||||||
}
|
}
|
||||||
|
@ -19,6 +20,7 @@ func loadAdminFrom(rootCfg ConfigProvider) {
|
||||||
sec := rootCfg.Section("admin")
|
sec := rootCfg.Section("admin")
|
||||||
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
|
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
|
||||||
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
|
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
|
||||||
|
Admin.DefaultUINotification = sec.Key("DEFAULT_UI_NOTIFICATIONS").MustString("enabled")
|
||||||
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
|
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
|
||||||
Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...)
|
Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@ type MarkupOption struct {
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Mode string
|
Mode string
|
||||||
// Context to render
|
// URL path for rendering issue, media and file links
|
||||||
|
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Context string
|
Context string
|
||||||
|
@ -53,7 +54,8 @@ type MarkdownOption struct {
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Mode string
|
Mode string
|
||||||
// Context to render
|
// URL path for rendering issue, media and file links
|
||||||
|
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Context string
|
Context string
|
||||||
|
|
47
options/gitignore/IAR
Normal file
47
options/gitignore/IAR
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# Compiled binaries
|
||||||
|
*.o
|
||||||
|
*.bin
|
||||||
|
*.elf
|
||||||
|
*.hex
|
||||||
|
*.map
|
||||||
|
*.out
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Trash
|
||||||
|
*.bak
|
||||||
|
thumbs.db
|
||||||
|
*.~*
|
||||||
|
|
||||||
|
# IAR Settings
|
||||||
|
**/settings/*.crun
|
||||||
|
**/settings/*.dbgdt
|
||||||
|
**/settings/*.cspy
|
||||||
|
**/settings/*.cspy.*
|
||||||
|
**/settings/*.xcl
|
||||||
|
**/settings/*.dni
|
||||||
|
**/settings/*.wsdt
|
||||||
|
**/settings/*.wspos
|
||||||
|
|
||||||
|
# IAR Debug Exe
|
||||||
|
**/Exe/*.sim
|
||||||
|
|
||||||
|
# IAR Debug Obj
|
||||||
|
**/Obj/*.pbd
|
||||||
|
**/Obj/*.pbd.*
|
||||||
|
**/Obj/*.pbi
|
||||||
|
**/Obj/*.pbi.*
|
||||||
|
|
||||||
|
# IAR project "Debug" directory
|
||||||
|
Debug/
|
||||||
|
|
||||||
|
# IAR project "Release" directory
|
||||||
|
Release/
|
||||||
|
|
||||||
|
# IAR project settings directory
|
||||||
|
settings/
|
||||||
|
|
||||||
|
# IAR backup files
|
||||||
|
Backup*
|
||||||
|
|
||||||
|
# IAR .dep files
|
||||||
|
*.dep
|
|
@ -42,10 +42,3 @@ fastlane/report.xml
|
||||||
fastlane/Preview.html
|
fastlane/Preview.html
|
||||||
fastlane/screenshots/**/*.png
|
fastlane/screenshots/**/*.png
|
||||||
fastlane/test_output
|
fastlane/test_output
|
||||||
|
|
||||||
# Code Injection
|
|
||||||
#
|
|
||||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
|
||||||
# https://github.com/johnno1962/injectionforxcode
|
|
||||||
|
|
||||||
iOSInjectionProject/
|
|
||||||
|
|
|
@ -35,6 +35,3 @@ override.tf.json
|
||||||
# Ignore CLI configuration files
|
# Ignore CLI configuration files
|
||||||
.terraformrc
|
.terraformrc
|
||||||
terraform.rc
|
terraform.rc
|
||||||
|
|
||||||
# Ignore hcl file
|
|
||||||
.terraform.lock.hcl
|
|
||||||
|
|
|
@ -680,6 +680,7 @@ block.list.none = You have not blocked any users.
|
||||||
profile = Profile
|
profile = Profile
|
||||||
account = Account
|
account = Account
|
||||||
appearance = Appearance
|
appearance = Appearance
|
||||||
|
notifications = Notifications
|
||||||
password = Password
|
password = Password
|
||||||
security = Security
|
security = Security
|
||||||
avatar = Avatar
|
avatar = Avatar
|
||||||
|
@ -694,6 +695,7 @@ account_link = Linked Accounts
|
||||||
organization = Organizations
|
organization = Organizations
|
||||||
uid = UID
|
uid = UID
|
||||||
webauthn = Two-Factor Authentication (Security Keys)
|
webauthn = Two-Factor Authentication (Security Keys)
|
||||||
|
manage_notifications = Manage Notifications
|
||||||
|
|
||||||
public_profile = Public Profile
|
public_profile = Public Profile
|
||||||
biography_placeholder = Tell us a little bit about yourself! (You can use Markdown)
|
biography_placeholder = Tell us a little bit about yourself! (You can use Markdown)
|
||||||
|
@ -763,6 +765,8 @@ manage_emails = Manage Email Addresses
|
||||||
manage_themes = Select default theme
|
manage_themes = Select default theme
|
||||||
manage_openid = Manage OpenID Addresses
|
manage_openid = Manage OpenID Addresses
|
||||||
email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.
|
email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.
|
||||||
|
email_notifications_desc = Choose what type of Email notifications you want to receive.
|
||||||
|
ui_notifications_desc = Choose what type of UI notifications you want to receive.
|
||||||
theme_desc = This will be your default theme across the site.
|
theme_desc = This will be your default theme across the site.
|
||||||
theme_colorblindness_help = Colorblindness Theme Support
|
theme_colorblindness_help = Colorblindness Theme Support
|
||||||
theme_colorblindness_prompt = Gitea just gets some themes with basic colorblindness support, which only have a few colors defined. The work is still in progress. More improvements could be done by defining more colors in the theme CSS files.
|
theme_colorblindness_prompt = Gitea just gets some themes with basic colorblindness support, which only have a few colors defined. The work is still in progress. More improvements could be done by defining more colors in the theme CSS files.
|
||||||
|
@ -789,6 +793,7 @@ add_openid = Add OpenID URI
|
||||||
add_email_confirmation_sent = A confirmation email has been sent to "%s". Please check your inbox within the next %s to confirm your email address.
|
add_email_confirmation_sent = A confirmation email has been sent to "%s". Please check your inbox within the next %s to confirm your email address.
|
||||||
add_email_success = The new email address has been added.
|
add_email_success = The new email address has been added.
|
||||||
email_preference_set_success = Email preference has been set successfully.
|
email_preference_set_success = Email preference has been set successfully.
|
||||||
|
ui_preference_set_success = UI preference has been set successfully.
|
||||||
add_openid_success = The new OpenID address has been added.
|
add_openid_success = The new OpenID address has been added.
|
||||||
keep_email_private = Hide Email Address
|
keep_email_private = Hide Email Address
|
||||||
keep_email_private_popup = This will hide your email address from your profile, as well as when you make a pull request or edit a file using the web interface. Pushed commits will not be modified. Use %s in commits to associate them with your account.
|
keep_email_private_popup = This will hide your email address from your profile, as well as when you make a pull request or edit a file using the web interface. Pushed commits will not be modified. Use %s in commits to associate them with your account.
|
||||||
|
@ -976,11 +981,12 @@ confirm_delete_account = Confirm Deletion
|
||||||
delete_account_title = Delete User Account
|
delete_account_title = Delete User Account
|
||||||
delete_account_desc = Are you sure you want to permanently delete this user account?
|
delete_account_desc = Are you sure you want to permanently delete this user account?
|
||||||
|
|
||||||
email_notifications.enable = Enable Email Notifications
|
notifications.enable = Enable Notifications
|
||||||
email_notifications.onmention = Only Email on Mention
|
notifications.onmention = Only Notify on Mention
|
||||||
email_notifications.disable = Disable Email Notifications
|
notifications.andyourown = Receive All And Your Own
|
||||||
email_notifications.submit = Set Email Preference
|
notifications.disable = Disable Notifications
|
||||||
email_notifications.andyourown = And Your Own Notifications
|
notifications.submit_email = Set Email Preference
|
||||||
|
notifications.submit_ui = Set UI Preference
|
||||||
|
|
||||||
visibility = User visibility
|
visibility = User visibility
|
||||||
visibility.public = Public
|
visibility.public = Public
|
||||||
|
|
|
@ -588,6 +588,8 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
||||||
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
||||||
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
||||||
|
r.Get("/info/{packagename}", rubygems.GetPackageInfo)
|
||||||
|
r.Get("/versions", rubygems.GetAllPackagesVersions)
|
||||||
r.Group("/api/v1/gems", func() {
|
r.Group("/api/v1/gems", func() {
|
||||||
r.Post("/", rubygems.UploadPackageFile)
|
r.Post("/", rubygems.UploadPackageFile)
|
||||||
r.Delete("/yank", rubygems.DeletePackage)
|
r.Delete("/yank", rubygems.DeletePackage)
|
||||||
|
|
|
@ -6,6 +6,7 @@ package rubygems
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
|
"crypto/md5"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -227,12 +228,7 @@ func UploadPackageFile(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename string
|
filename := makeGemFullFileName(rp.Name, rp.Version, rp.Metadata.Platform)
|
||||||
if rp.Metadata.Platform == "" || rp.Metadata.Platform == "ruby" {
|
|
||||||
filename = strings.ToLower(fmt.Sprintf("%s-%s.gem", rp.Name, rp.Version))
|
|
||||||
} else {
|
|
||||||
filename = strings.ToLower(fmt.Sprintf("%s-%s-%s.gem", rp.Name, rp.Version, rp.Metadata.Platform))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = packages_service.CreatePackageAndAddFile(
|
_, _, err = packages_service.CreatePackageAndAddFile(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -300,6 +296,136 @@ func DeletePackage(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPackageInfo returns a custom text based format for the single rubygem with a line for each version of the rubygem
|
||||||
|
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
|
||||||
|
func GetPackageInfo(ctx *context.Context) {
|
||||||
|
packageName := ctx.Params("packagename")
|
||||||
|
versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, packageName)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(versions) == 0 {
|
||||||
|
apiError(ctx, http.StatusNotFound, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
infoContent, err := makePackageInfo(ctx, versions)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.PlainText(http.StatusOK, infoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems.
|
||||||
|
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
|
||||||
|
func GetAllPackagesVersions(ctx *context.Context) {
|
||||||
|
packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &strings.Builder{}
|
||||||
|
out.WriteString("---\n")
|
||||||
|
for _, pkg := range packages {
|
||||||
|
versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, pkg.Name)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(versions) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := makePackageInfo(ctx, versions)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// format: RUBYGEM [-]VERSION_PLATFORM[,VERSION_PLATFORM],...] MD5
|
||||||
|
_, _ = fmt.Fprintf(out, "%s ", pkg.Name)
|
||||||
|
for i, v := range versions {
|
||||||
|
sep := util.Iif(i == len(versions)-1, "", ",")
|
||||||
|
_, _ = fmt.Fprintf(out, "%s%s", v.Version, sep)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info)))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.PlainText(http.StatusOK, out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) {
|
||||||
|
out.WriteString(prefix)
|
||||||
|
if len(reqs) == 0 {
|
||||||
|
reqs = []rubygems_module.VersionRequirement{{Restriction: ">=", Version: "0"}}
|
||||||
|
}
|
||||||
|
for i, req := range reqs {
|
||||||
|
sep := util.Iif(i == 0, "", "&")
|
||||||
|
_, _ = fmt.Fprintf(out, "%s%s %s", sep, req.Restriction, req.Version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) {
|
||||||
|
// format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...]
|
||||||
|
// DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT]
|
||||||
|
// REQUIREMENT: KEY:VALUE (always contains "checksum")
|
||||||
|
pd, err := packages_model.GetPackageDescriptor(ctx, version)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := pd.Metadata.(*rubygems_module.Metadata)
|
||||||
|
fullFilename := makeGemFullFileName(pd.Package.Name, version.Version, metadata.Platform)
|
||||||
|
file, err := packages_model.GetFileForVersionByName(ctx, version.ID, fullFilename, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
blob, err := packages_model.GetBlobByID(ctx, file.BlobID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &strings.Builder{}
|
||||||
|
buf.WriteString(version.Version)
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
for i, dep := range metadata.RuntimeDependencies {
|
||||||
|
sep := util.Iif(i == 0, "", ",")
|
||||||
|
writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(buf, "|checksum:%s", blob.HashSHA256)
|
||||||
|
if len(metadata.RequiredRubyVersion) != 0 {
|
||||||
|
writePackageVersionRequirements(",ruby:", metadata.RequiredRubyVersion, buf)
|
||||||
|
}
|
||||||
|
if len(metadata.RequiredRubygemsVersion) != 0 {
|
||||||
|
writePackageVersionRequirements(",rubygems:", metadata.RequiredRubygemsVersion, buf)
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) {
|
||||||
|
ret := "---\n"
|
||||||
|
for _, v := range versions {
|
||||||
|
dep, err := makePackageVersionDependency(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ret += dep + "\n"
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeGemFullFileName(gemName, version, platform string) string {
|
||||||
|
var basename string
|
||||||
|
if platform == "" || platform == "ruby" {
|
||||||
|
basename = fmt.Sprintf("%s-%s", gemName, version)
|
||||||
|
} else {
|
||||||
|
basename = fmt.Sprintf("%s-%s-%s", gemName, version, platform)
|
||||||
|
}
|
||||||
|
return strings.ToLower(basename) + ".gem"
|
||||||
|
}
|
||||||
|
|
||||||
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
|
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
|
||||||
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
go_context "context"
|
go_context "context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -19,36 +20,40 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const AppURL = "http://localhost:3000/"
|
||||||
AppURL = "http://localhost:3000/"
|
|
||||||
Repo = "gogits/gogs"
|
|
||||||
FullURL = AppURL + Repo + "/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
|
func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
context := "/gogits/gogs"
|
||||||
|
if !wiki {
|
||||||
|
context += path.Join("/src/branch/main", path.Dir(filePath))
|
||||||
|
}
|
||||||
options := api.MarkupOption{
|
options := api.MarkupOption{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Text: text,
|
Text: text,
|
||||||
Context: Repo,
|
Context: context,
|
||||||
Wiki: true,
|
Wiki: wiki,
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
Markup(ctx)
|
Markup(ctx)
|
||||||
assert.Equal(t, responseBody, resp.Body.String())
|
assert.Equal(t, expectedBody, resp.Body.String())
|
||||||
assert.Equal(t, responseCode, resp.Code)
|
assert.Equal(t, expectedCode, resp.Code)
|
||||||
resp.Body.Reset()
|
resp.Body.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderMarkdown(t *testing.T, mode, text, responseBody string, responseCode int) {
|
func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
context := "/gogits/gogs"
|
||||||
|
if !wiki {
|
||||||
|
context += "/src/branch/main"
|
||||||
|
}
|
||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Text: text,
|
Text: text,
|
||||||
Context: Repo,
|
Context: context,
|
||||||
Wiki: true,
|
Wiki: wiki,
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
|
@ -65,7 +70,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
testCasesCommon := []string{
|
testCasesWiki := []string{
|
||||||
// dear imgui wiki markdown extract: special wiki syntax
|
// dear imgui wiki markdown extract: special wiki syntax
|
||||||
`Wiki! Enjoy :)
|
`Wiki! Enjoy :)
|
||||||
- [[Links, Language bindings, Engine bindings|Links]]
|
- [[Links, Language bindings, Engine bindings|Links]]
|
||||||
|
@ -74,20 +79,20 @@ func TestAPI_RenderGFM(t *testing.T) {
|
||||||
// rendered
|
// rendered
|
||||||
`<p>Wiki! Enjoy :)</p>
|
`<p>Wiki! Enjoy :)</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
<li><a href="http://localhost:3000/gogits/gogs/wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||||
<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
|
<li><a href="http://localhost:3000/gogits/gogs/wiki/Tips" rel="nofollow">Tips</a></li>
|
||||||
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
|
<li>Bezier widget (by <a href="http://localhost:3000/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
`,
|
`,
|
||||||
// Guard wiki sidebar: special syntax
|
// Guard wiki sidebar: special syntax
|
||||||
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
||||||
// rendered
|
// rendered
|
||||||
`<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
||||||
`,
|
`,
|
||||||
// special syntax
|
// special syntax
|
||||||
`[[Name|Link]]`,
|
`[[Name|Link]]`,
|
||||||
// rendered
|
// rendered
|
||||||
`<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
|
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Link" rel="nofollow">Name</a></p>
|
||||||
`,
|
`,
|
||||||
// empty
|
// empty
|
||||||
``,
|
``,
|
||||||
|
@ -95,7 +100,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
||||||
``,
|
``,
|
||||||
}
|
}
|
||||||
|
|
||||||
testCasesDocument := []string{
|
testCasesWikiDocument := []string{
|
||||||
// wine-staging wiki home extract: special wiki syntax, images
|
// wine-staging wiki home extract: special wiki syntax, images
|
||||||
`## What is Wine Staging?
|
`## What is Wine Staging?
|
||||||
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
|
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
|
||||||
|
@ -111,31 +116,48 @@ Here are some links to the most important topics. You can find the full list of
|
||||||
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
|
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
|
||||||
<h2 id="user-content-quick-links">Quick Links</h2>
|
<h2 id="user-content-quick-links">Quick Links</h2>
|
||||||
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
|
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
|
||||||
<p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
|
<p><a href="http://localhost:3000/gogits/gogs/wiki/Configuration" rel="nofollow">Configuration</a>
|
||||||
<a href="` + FullURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + FullURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
<a href="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" rel="nofollow"><img src="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCasesCommon); i += 2 {
|
for i := 0; i < len(testCasesWiki); i += 2 {
|
||||||
text := testCasesCommon[i]
|
text := testCasesWiki[i]
|
||||||
response := testCasesCommon[i+1]
|
response := testCasesWiki[i+1]
|
||||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkdown(t, "comment", text, response, http.StatusOK)
|
testRenderMarkdown(t, "comment", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "comment", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCasesDocument); i += 2 {
|
for i := 0; i < len(testCasesWikiDocument); i += 2 {
|
||||||
text := testCasesDocument[i]
|
text := testCasesWikiDocument[i]
|
||||||
response := testCasesDocument[i+1]
|
response := testCasesWikiDocument[i+1]
|
||||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
|
input := "[Link](test.md)\n![Image](image.png)"
|
||||||
testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "gfm", false, "", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "file", false, "path/new-file.md", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/path/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "file", true, "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
|
||||||
|
testRenderMarkup(t, "unknown", true, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var simpleCases = []string{
|
var simpleCases = []string{
|
||||||
|
@ -160,7 +182,7 @@ func TestAPI_RenderSimple(t *testing.T) {
|
||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: "markdown",
|
Mode: "markdown",
|
||||||
Text: "",
|
Text: "",
|
||||||
Context: Repo,
|
Context: "/gogits/gogs",
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
for i := 0; i < len(simpleCases); i += 2 {
|
for i := 0; i < len(simpleCases); i += 2 {
|
||||||
|
|
|
@ -7,63 +7,67 @@ package common
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
"mvdan.cc/xurls/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
||||||
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPrefix, filePath string, wiki bool) {
|
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string, wiki bool) {
|
||||||
var markupType string
|
// urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
|
||||||
relativePath := ""
|
// filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file")
|
||||||
|
// filePath will be used as RenderContext.RelativePath
|
||||||
|
|
||||||
if len(text) == 0 {
|
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
||||||
_, _ = ctx.Write([]byte(""))
|
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
||||||
return
|
|
||||||
|
var markupType, relativePath string
|
||||||
|
|
||||||
|
links := markup.Links{AbsolutePrefix: true}
|
||||||
|
if urlPathContext != "" {
|
||||||
|
links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case "markdown":
|
case "markdown":
|
||||||
// Raw markdown
|
// Raw markdown
|
||||||
if err := markdown.RenderRaw(&markup.RenderContext{
|
if err := markdown.RenderRaw(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Links: markup.Links{
|
Links: links,
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: urlPrefix,
|
|
||||||
},
|
|
||||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case "comment":
|
case "comment":
|
||||||
// Comment as markdown
|
// Issue & comment content
|
||||||
markupType = markdown.MarkupName
|
markupType = markdown.MarkupName
|
||||||
case "gfm":
|
case "gfm":
|
||||||
// Github Flavored Markdown as document
|
// GitHub Flavored Markdown
|
||||||
markupType = markdown.MarkupName
|
markupType = markdown.MarkupName
|
||||||
case "file":
|
case "file":
|
||||||
// File as document based on file extension
|
markupType = "" // render the repo file content by its extension
|
||||||
markupType = ""
|
|
||||||
relativePath = filePath
|
relativePath = filePath
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
|
fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5)
|
||||||
// check if urlPrefix is already set to a URL
|
if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
|
||||||
linkRegex, _ := xurls.StrictMatchingScheme("https?://")
|
// absolute base prefix is something like "https://host/subpath/{user}/{repo}"
|
||||||
m := linkRegex.FindStringIndex(urlPrefix)
|
absoluteBasePrefix := fmt.Sprintf("%s%s/%s", httplib.GuessCurrentAppURL(ctx), fields[0], fields[1])
|
||||||
if m == nil {
|
|
||||||
urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
|
fileDir := path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md"
|
||||||
}
|
refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
|
||||||
|
refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
|
||||||
|
|
||||||
|
links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := map[string]string{}
|
meta := map[string]string{}
|
||||||
|
@ -81,12 +85,9 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := markup.Render(&markup.RenderContext{
|
if err := markup.Render(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Repo: repoCtx,
|
Repo: repoCtx,
|
||||||
Links: markup.Links{
|
Links: links,
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: urlPrefix,
|
|
||||||
},
|
|
||||||
Metas: meta,
|
Metas: meta,
|
||||||
IsWiki: wiki,
|
IsWiki: wiki,
|
||||||
Type: markupType,
|
Type: markupType,
|
||||||
|
|
|
@ -154,10 +154,10 @@ func EmailPost(ctx *context.Context) {
|
||||||
// Set Email Notification Preference
|
// Set Email Notification Preference
|
||||||
if ctx.FormString("_method") == "NOTIFICATION" {
|
if ctx.FormString("_method") == "NOTIFICATION" {
|
||||||
preference := ctx.FormString("preference")
|
preference := ctx.FormString("preference")
|
||||||
if !(preference == user_model.EmailNotificationsEnabled ||
|
if !(preference == user_model.NotificationsEnabled ||
|
||||||
preference == user_model.EmailNotificationsOnMention ||
|
preference == user_model.NotificationsOnMention ||
|
||||||
preference == user_model.EmailNotificationsDisabled ||
|
preference == user_model.NotificationsDisabled ||
|
||||||
preference == user_model.EmailNotificationsAndYourOwn) {
|
preference == user_model.NotificationsAndYourOwn) {
|
||||||
log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
|
log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
|
||||||
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
|
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
|
||||||
return
|
return
|
||||||
|
@ -316,6 +316,8 @@ func loadAccountData(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["Emails"] = emails
|
ctx.Data["Emails"] = emails
|
||||||
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
||||||
|
ctx.Data["UINotificationsPreference"] = ctx.Doer.UINotificationsPreference
|
||||||
|
|
||||||
ctx.Data["ActivationsPending"] = pendingActivation
|
ctx.Data["ActivationsPending"] = pendingActivation
|
||||||
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
|
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
|
||||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||||
|
|
116
routers/web/user/setting/notifications.go
Normal file
116
routers/web/user/setting/notifications.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tplSettingsNotifications base.TplName = "user/settings/notifications"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notifications render manage access token page
|
||||||
|
func Notifications(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("settings.notifications")
|
||||||
|
ctx.Data["PageIsSettingsNotifications"] = true
|
||||||
|
ctx.Data["Email"] = ctx.Doer.Email
|
||||||
|
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
|
||||||
|
|
||||||
|
loadNotificationsData(ctx)
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, tplSettingsNotifications)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationPost response for change user's notification preferences
|
||||||
|
func NotificationPost(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
|
ctx.Data["PageIsSettingsNotifications"] = true
|
||||||
|
|
||||||
|
// Set Email Notification Preference
|
||||||
|
if ctx.FormString("_method") == "EMAIL" {
|
||||||
|
preference := ctx.FormString("preference")
|
||||||
|
if !(preference == user_model.NotificationsEnabled ||
|
||||||
|
preference == user_model.NotificationsOnMention ||
|
||||||
|
preference == user_model.NotificationsDisabled ||
|
||||||
|
preference == user_model.NotificationsAndYourOwn) {
|
||||||
|
log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
|
||||||
|
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts := &user.UpdateOptions{
|
||||||
|
EmailNotificationsPreference: optional.Some(preference),
|
||||||
|
}
|
||||||
|
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||||
|
log.Error("Set Email Notifications failed: %v", err)
|
||||||
|
ctx.ServerError("UpdateUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("Email notifications preference made %s: %s", preference, ctx.Doer.Name)
|
||||||
|
ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/notifications")
|
||||||
|
return
|
||||||
|
// Set UI Notification Preference
|
||||||
|
} else if ctx.FormString("_method") == "UI" {
|
||||||
|
preference := ctx.FormString("preference")
|
||||||
|
if !(preference == user_model.NotificationsEnabled ||
|
||||||
|
preference == user_model.NotificationsOnMention ||
|
||||||
|
preference == user_model.NotificationsDisabled ||
|
||||||
|
preference == user_model.NotificationsAndYourOwn) {
|
||||||
|
log.Error("UI notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
|
||||||
|
ctx.ServerError("SetUIPreference", errors.New("option unrecognized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts := &user.UpdateOptions{
|
||||||
|
UINotificationsPreference: optional.Some(preference),
|
||||||
|
}
|
||||||
|
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||||
|
log.Error("Set UI Notifications failed: %v", err)
|
||||||
|
ctx.ServerError("UpdateUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("UI notifications preference made %s: %s", preference, ctx.Doer.Name)
|
||||||
|
ctx.Flash.Success(ctx.Tr("settings.ui_preference_set_success"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/notifications")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.HasError() {
|
||||||
|
loadAccountData(ctx)
|
||||||
|
ctx.HTML(http.StatusOK, tplSettingsAccount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNotificationsData(ctx *context.Context) {
|
||||||
|
emlist, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetEmailAddresses", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type UserEmail struct {
|
||||||
|
user_model.EmailAddress
|
||||||
|
}
|
||||||
|
emails := make([]*UserEmail, len(emlist))
|
||||||
|
for i, em := range emlist {
|
||||||
|
if !em.IsActivated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var email UserEmail
|
||||||
|
email.EmailAddress = *em
|
||||||
|
emails[i] = &email
|
||||||
|
}
|
||||||
|
ctx.Data["Emails"] = emails
|
||||||
|
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
||||||
|
ctx.Data["UINotificationsPreference"] = ctx.Doer.UINotificationsPreference
|
||||||
|
}
|
|
@ -560,6 +560,11 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Get("/change_password", auth.MustChangePassword)
|
m.Get("/change_password", auth.MustChangePassword)
|
||||||
m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost)
|
m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost)
|
||||||
m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost)
|
m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost)
|
||||||
|
m.Group("/notifications", func() {
|
||||||
|
m.Get("", user_setting.Notifications)
|
||||||
|
m.Post("", user_setting.NotificationPost)
|
||||||
|
})
|
||||||
|
|
||||||
m.Post("/avatar/delete", user_setting.DeleteAvatar)
|
m.Post("/avatar/delete", user_setting.DeleteAvatar)
|
||||||
m.Group("/account", func() {
|
m.Group("/account", func() {
|
||||||
m.Combo("").Get(user_setting.Account).Post(web.Bind(forms.ChangePasswordForm{}), user_setting.AccountPost)
|
m.Combo("").Get(user_setting.Account).Post(web.Bind(forms.ChangePasswordForm{}), user_setting.AccountPost)
|
||||||
|
|
|
@ -170,11 +170,12 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (
|
||||||
Email: email,
|
Email: email,
|
||||||
Language: cfg.DefaultLanguage,
|
Language: cfg.DefaultLanguage,
|
||||||
}
|
}
|
||||||
emailNotificationPreference := user_model.EmailNotificationsDisabled
|
notificationPreference := user_model.NotificationsDisabled
|
||||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||||
IsActive: optional.Some(cfg.AutoActivateUsers),
|
IsActive: optional.Some(cfg.AutoActivateUsers),
|
||||||
KeepEmailPrivate: optional.Some(true),
|
KeepEmailPrivate: optional.Some(true),
|
||||||
EmailNotificationsPreference: &emailNotificationPreference,
|
EmailNotificationsPreference: ¬ificationPreference,
|
||||||
|
UINotificationsPreference: ¬ificationPreference,
|
||||||
}
|
}
|
||||||
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
|
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -93,7 +93,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
|
||||||
visited := make(container.Set[int64], len(unfiltered)+len(mentions)+1)
|
visited := make(container.Set[int64], len(unfiltered)+len(mentions)+1)
|
||||||
|
|
||||||
// Avoid mailing the doer
|
// Avoid mailing the doer
|
||||||
if ctx.Doer.EmailNotificationsPreference != user_model.EmailNotificationsAndYourOwn && !ctx.ForceDoerNotification {
|
if ctx.Doer.EmailNotificationsPreference != user_model.NotificationsAndYourOwn && !ctx.ForceDoerNotification {
|
||||||
visited.Add(ctx.Doer.ID)
|
visited.Add(ctx.Doer.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,9 +134,9 @@ func mailIssueCommentBatch(ctx *mailCommentContext, users []*user_model.User, vi
|
||||||
}
|
}
|
||||||
// At this point we exclude:
|
// At this point we exclude:
|
||||||
// user that don't have all mails enabled or users only get mail on mention and this is one ...
|
// user that don't have all mails enabled or users only get mail on mention and this is one ...
|
||||||
if !(user.EmailNotificationsPreference == user_model.EmailNotificationsEnabled ||
|
if !(user.EmailNotificationsPreference == user_model.NotificationsEnabled ||
|
||||||
user.EmailNotificationsPreference == user_model.EmailNotificationsAndYourOwn ||
|
user.EmailNotificationsPreference == user_model.NotificationsAndYourOwn ||
|
||||||
fromMention && user.EmailNotificationsPreference == user_model.EmailNotificationsOnMention) {
|
fromMention && user.EmailNotificationsPreference == user_model.NotificationsOnMention) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ func (m *mailNotifier) PullRequestCodeComment(ctx context.Context, pr *issues_mo
|
||||||
|
|
||||||
func (m *mailNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) {
|
func (m *mailNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) {
|
||||||
// mail only sent to added assignees and not self-assignee
|
// mail only sent to added assignees and not self-assignee
|
||||||
if !removed && doer.ID != assignee.ID && assignee.EmailNotificationsPreference != user_model.EmailNotificationsDisabled {
|
if !removed && doer.ID != assignee.ID && assignee.EmailNotificationsPreference != user_model.NotificationsDisabled {
|
||||||
ct := fmt.Sprintf("Assigned #%d.", issue.Index)
|
ct := fmt.Sprintf("Assigned #%d.", issue.Index)
|
||||||
if err := SendIssueAssignedMail(ctx, issue, doer, ct, comment, []*user_model.User{assignee}); err != nil {
|
if err := SendIssueAssignedMail(ctx, issue, doer, ct, comment, []*user_model.User{assignee}); err != nil {
|
||||||
log.Error("Error in SendIssueAssignedMail for issue[%d] to assignee[%d]: %v", issue.ID, assignee.ID, err)
|
log.Error("Error in SendIssueAssignedMail for issue[%d] to assignee[%d]: %v", issue.ID, assignee.ID, err)
|
||||||
|
@ -123,7 +123,7 @@ func (m *mailNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mailNotifier) PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
|
func (m *mailNotifier) PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
|
||||||
if isRequest && doer.ID != reviewer.ID && reviewer.EmailNotificationsPreference != user_model.EmailNotificationsDisabled {
|
if isRequest && doer.ID != reviewer.ID && reviewer.EmailNotificationsPreference != user_model.NotificationsDisabled {
|
||||||
ct := fmt.Sprintf("Requested to review %s.", issue.HTMLURL())
|
ct := fmt.Sprintf("Requested to review %s.", issue.HTMLURL())
|
||||||
if err := SendIssueAssignedMail(ctx, issue, doer, ct, comment, []*user_model.User{reviewer}); err != nil {
|
if err := SendIssueAssignedMail(ctx, issue, doer, ct, comment, []*user_model.User{reviewer}); err != nil {
|
||||||
log.Error("Error in SendIssueAssignedMail for issue[%d] to reviewer[%d]: %v", issue.ID, reviewer.ID, err)
|
log.Error("Error in SendIssueAssignedMail for issue[%d] to reviewer[%d]: %v", issue.ID, reviewer.ID, err)
|
||||||
|
|
|
@ -6,10 +6,13 @@ package repository
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -83,3 +86,13 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) {
|
||||||
assert.Equal(t, 2, count)
|
assert.Equal(t, 2, count)
|
||||||
assert.Equal(t, unadoptedList[1], repoNames[0])
|
assert.Equal(t, unadoptedList[1], repoNames[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdoptRepository(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
assert.NoError(t, unittest.CopyDir(filepath.Join(setting.RepoRootPath, "user2", "repo1.git"), filepath.Join(setting.RepoRootPath, "user2", "test-adopt.git")))
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
_, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
repoTestAdopt := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "test-adopt"})
|
||||||
|
assert.Equal(t, "sha1", repoTestAdopt.ObjectFormatName)
|
||||||
|
}
|
||||||
|
|
|
@ -75,6 +75,11 @@ func (ns *notificationService) CreateIssueComment(ctx context.Context, doer *use
|
||||||
}
|
}
|
||||||
_ = ns.issueQueue.Push(opts)
|
_ = ns.issueQueue.Push(opts)
|
||||||
for _, mention := range mentions {
|
for _, mention := range mentions {
|
||||||
|
// Avoid notifying users according to their setting.
|
||||||
|
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
|
||||||
|
mention.UINotificationsPreference != user_model.NotificationsOnMention {
|
||||||
|
continue
|
||||||
|
}
|
||||||
opts := issueNotificationOpts{
|
opts := issueNotificationOpts{
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
NotificationAuthorID: doer.ID,
|
NotificationAuthorID: doer.ID,
|
||||||
|
@ -92,7 +97,13 @@ func (ns *notificationService) NewIssue(ctx context.Context, issue *issues_model
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
NotificationAuthorID: issue.Poster.ID,
|
NotificationAuthorID: issue.Poster.ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, mention := range mentions {
|
for _, mention := range mentions {
|
||||||
|
// Avoid notifying users according to their setting.
|
||||||
|
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
|
||||||
|
mention.UINotificationsPreference != user_model.NotificationsOnMention {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
NotificationAuthorID: issue.Poster.ID,
|
NotificationAuthorID: issue.Poster.ID,
|
||||||
|
@ -145,6 +156,15 @@ func (ns *notificationService) NewPullRequest(ctx context.Context, pr *issues_mo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, id := range repoWatchers {
|
for _, id := range repoWatchers {
|
||||||
|
// Exclude users based on their notification prefs.
|
||||||
|
user, err := user_model.GetUserByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetUserByID: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.UINotificationsPreference != user_model.NotificationsEnabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
toNotify.Add(id)
|
toNotify.Add(id)
|
||||||
}
|
}
|
||||||
issueParticipants, err := issues_model.GetParticipantsIDsByIssueID(ctx, pr.IssueID)
|
issueParticipants, err := issues_model.GetParticipantsIDsByIssueID(ctx, pr.IssueID)
|
||||||
|
@ -153,12 +173,30 @@ func (ns *notificationService) NewPullRequest(ctx context.Context, pr *issues_mo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, id := range issueParticipants {
|
for _, id := range issueParticipants {
|
||||||
|
// Exclude users based on their notification prefs.
|
||||||
|
user, err := user_model.GetUserByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetUserByID: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.UINotificationsPreference != user_model.NotificationsEnabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
toNotify.Add(id)
|
toNotify.Add(id)
|
||||||
}
|
}
|
||||||
delete(toNotify, pr.Issue.PosterID)
|
// Check if user should not be mentioned on their own actions.
|
||||||
|
if pr.Issue.Poster.UINotificationsPreference != user_model.NotificationsAndYourOwn {
|
||||||
|
delete(toNotify, pr.Issue.PosterID)
|
||||||
|
}
|
||||||
for _, mention := range mentions {
|
for _, mention := range mentions {
|
||||||
|
// Exclude users based on their notification preferences.
|
||||||
|
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
|
||||||
|
mention.UINotificationsPreference != user_model.NotificationsOnMention {
|
||||||
|
continue
|
||||||
|
}
|
||||||
toNotify.Add(mention.ID)
|
toNotify.Add(mention.ID)
|
||||||
}
|
}
|
||||||
|
// Exclude users based on their notification preferences.
|
||||||
for receiverID := range toNotify {
|
for receiverID := range toNotify {
|
||||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||||
IssueID: pr.Issue.ID,
|
IssueID: pr.Issue.ID,
|
||||||
|
@ -178,6 +216,11 @@ func (ns *notificationService) PullRequestReview(ctx context.Context, pr *issues
|
||||||
}
|
}
|
||||||
_ = ns.issueQueue.Push(opts)
|
_ = ns.issueQueue.Push(opts)
|
||||||
for _, mention := range mentions {
|
for _, mention := range mentions {
|
||||||
|
// Exclude users based on their notification preferences.
|
||||||
|
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
|
||||||
|
mention.UINotificationsPreference != user_model.NotificationsOnMention {
|
||||||
|
continue
|
||||||
|
}
|
||||||
opts := issueNotificationOpts{
|
opts := issueNotificationOpts{
|
||||||
IssueID: pr.Issue.ID,
|
IssueID: pr.Issue.ID,
|
||||||
NotificationAuthorID: r.Reviewer.ID,
|
NotificationAuthorID: r.Reviewer.ID,
|
||||||
|
@ -192,6 +235,11 @@ func (ns *notificationService) PullRequestReview(ctx context.Context, pr *issues
|
||||||
|
|
||||||
func (ns *notificationService) PullRequestCodeComment(ctx context.Context, pr *issues_model.PullRequest, c *issues_model.Comment, mentions []*user_model.User) {
|
func (ns *notificationService) PullRequestCodeComment(ctx context.Context, pr *issues_model.PullRequest, c *issues_model.Comment, mentions []*user_model.User) {
|
||||||
for _, mention := range mentions {
|
for _, mention := range mentions {
|
||||||
|
// Exclude users based on their notification preferences.
|
||||||
|
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
|
||||||
|
mention.UINotificationsPreference != user_model.NotificationsOnMention {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||||
IssueID: pr.Issue.ID,
|
IssueID: pr.Issue.ID,
|
||||||
NotificationAuthorID: c.Poster.ID,
|
NotificationAuthorID: c.Poster.ID,
|
||||||
|
|
|
@ -35,6 +35,7 @@ type UpdateOptions struct {
|
||||||
IsActive optional.Option[bool]
|
IsActive optional.Option[bool]
|
||||||
IsAdmin optional.Option[bool]
|
IsAdmin optional.Option[bool]
|
||||||
EmailNotificationsPreference optional.Option[string]
|
EmailNotificationsPreference optional.Option[string]
|
||||||
|
UINotificationsPreference optional.Option[string]
|
||||||
SetLastLogin bool
|
SetLastLogin bool
|
||||||
RepoAdminChangeTeamAccess optional.Option[bool]
|
RepoAdminChangeTeamAccess optional.Option[bool]
|
||||||
}
|
}
|
||||||
|
@ -152,6 +153,12 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
|
||||||
cols = append(cols, "email_notifications_preference")
|
cols = append(cols, "email_notifications_preference")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.UINotificationsPreference.Has() {
|
||||||
|
u.UINotificationsPreference = opts.UINotificationsPreference.Value()
|
||||||
|
|
||||||
|
cols = append(cols, "ui_notifications_preference")
|
||||||
|
}
|
||||||
|
|
||||||
if opts.SetLastLogin {
|
if opts.SetLastLogin {
|
||||||
u.SetLastLogin()
|
u.SetLastLogin()
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ func TestUpdateUser(t *testing.T) {
|
||||||
DiffViewStyle: optional.Some("split"),
|
DiffViewStyle: optional.Some("split"),
|
||||||
AllowCreateOrganization: optional.Some(false),
|
AllowCreateOrganization: optional.Some(false),
|
||||||
EmailNotificationsPreference: optional.Some("disabled"),
|
EmailNotificationsPreference: optional.Some("disabled"),
|
||||||
|
UINotificationsPreference: optional.Some("disabled"),
|
||||||
SetLastLogin: true,
|
SetLastLogin: true,
|
||||||
}
|
}
|
||||||
assert.NoError(t, UpdateUser(db.DefaultContext, user, opts))
|
assert.NoError(t, UpdateUser(db.DefaultContext, user, opts))
|
||||||
|
@ -68,6 +69,7 @@ func TestUpdateUser(t *testing.T) {
|
||||||
assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle)
|
assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle)
|
||||||
assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization)
|
assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization)
|
||||||
assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference)
|
assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference)
|
||||||
|
assert.Equal(t, opts.UINotificationsPreference.Value(), user.UINotificationsPreference)
|
||||||
|
|
||||||
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})
|
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})
|
||||||
assert.Equal(t, opts.KeepEmailPrivate.Value(), user.KeepEmailPrivate)
|
assert.Equal(t, opts.KeepEmailPrivate.Value(), user.KeepEmailPrivate)
|
||||||
|
@ -88,6 +90,7 @@ func TestUpdateUser(t *testing.T) {
|
||||||
assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle)
|
assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle)
|
||||||
assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization)
|
assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization)
|
||||||
assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference)
|
assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference)
|
||||||
|
assert.Equal(t, opts.UINotificationsPreference.Value(), user.UINotificationsPreference)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateAuth(t *testing.T) {
|
func TestUpdateAuth(t *testing.T) {
|
||||||
|
|
|
@ -76,9 +76,11 @@
|
||||||
{{ctx.Locale.Tr "admin.dashboard.system_status"}}
|
{{ctx.Locale.Tr "admin.dashboard.system_status"}}
|
||||||
</h4>
|
</h4>
|
||||||
{{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
|
{{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
|
||||||
<div class="no-loading-indicator tw-hidden"></div>
|
<div class="ui attached table segment">
|
||||||
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".no-loading-indicator" class="ui attached table segment">
|
<div class="no-loading-indicator tw-hidden"></div>
|
||||||
{{template "admin/system_status" .}}
|
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".no-loading-indicator">
|
||||||
|
{{template "admin/system_status" .}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "admin/layout_footer" .}}
|
{{template "admin/layout_footer" .}}
|
||||||
|
|
4
templates/swagger/v1_json.tmpl
generated
4
templates/swagger/v1_json.tmpl
generated
|
@ -22404,7 +22404,7 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"Context": {
|
"Context": {
|
||||||
"description": "Context to render\n\nin: body",
|
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"Mode": {
|
"Mode": {
|
||||||
|
@ -22427,7 +22427,7 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"Context": {
|
"Context": {
|
||||||
"description": "Context to render\n\nin: body",
|
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"FilePath": {
|
"FilePath": {
|
||||||
|
|
|
@ -40,29 +40,6 @@
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
<div class="ui list">
|
<div class="ui list">
|
||||||
{{if $.EnableNotifyMail}}
|
|
||||||
<div class="item">
|
|
||||||
<div class="tw-mb-2">{{ctx.Locale.Tr "settings.email_desc"}}</div>
|
|
||||||
<form action="{{AppSubUrl}}/user/settings/account/email" class="ui form" method="post">
|
|
||||||
{{$.CsrfTokenHtml}}
|
|
||||||
<input name="_method" type="hidden" value="NOTIFICATION">
|
|
||||||
<div class="tw-flex tw-flex-wrap tw-gap-2">
|
|
||||||
<div class="ui selection dropdown">
|
|
||||||
<input name="preference" type="hidden" value="{{.EmailNotificationsPreference}}">
|
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
||||||
<div class="text"></div>
|
|
||||||
<div class="menu">
|
|
||||||
<div data-value="enabled" class="{{if eq .EmailNotificationsPreference "enabled"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.enable"}}</div>
|
|
||||||
<div data-value="andyourown" class="{{if eq .EmailNotificationsPreference "andyourown"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.andyourown"}}</div>
|
|
||||||
<div data-value="onmention" class="{{if eq .EmailNotificationsPreference "onmention"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.onmention"}}</div>
|
|
||||||
<div data-value="disabled" class="{{if eq .EmailNotificationsPreference "disabled"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.disable"}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "settings.email_notifications.submit"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{range .Emails}}
|
{{range .Emails}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
{{if not .IsPrimary}}
|
{{if not .IsPrimary}}
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
<a class="{{if .PageIsSettingsAppearance}}active {{end}}item" href="{{AppSubUrl}}/user/settings/appearance">
|
<a class="{{if .PageIsSettingsAppearance}}active {{end}}item" href="{{AppSubUrl}}/user/settings/appearance">
|
||||||
{{ctx.Locale.Tr "settings.appearance"}}
|
{{ctx.Locale.Tr "settings.appearance"}}
|
||||||
</a>
|
</a>
|
||||||
|
<a class="{{if .PageIsSettingsNotifications}}active {{end}}item" href="{{AppSubUrl}}/user/settings/notifications">
|
||||||
|
{{ctx.Locale.Tr "settings.notifications"}}
|
||||||
|
</a>
|
||||||
<a class="{{if .PageIsSettingsSecurity}}active {{end}}item" href="{{AppSubUrl}}/user/settings/security">
|
<a class="{{if .PageIsSettingsSecurity}}active {{end}}item" href="{{AppSubUrl}}/user/settings/security">
|
||||||
{{ctx.Locale.Tr "settings.security"}}
|
{{ctx.Locale.Tr "settings.security"}}
|
||||||
</a>
|
</a>
|
||||||
|
|
96
templates/user/settings/notifications.tmpl
Normal file
96
templates/user/settings/notifications.tmpl
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings notifications")}}
|
||||||
|
<div class="user-setting-content">
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{ctx.Locale.Tr "settings.manage_notifications"}}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<div class="ui list">
|
||||||
|
{{if $.EnableNotifyMail}}
|
||||||
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "settings.email_notifications_desc"}}</h5>
|
||||||
|
<form action="{{AppSubUrl}}/user/settings/notifications" class="ui form" method="post">
|
||||||
|
{{$.CsrfTokenHtml}}
|
||||||
|
<input name="_method" type="hidden" value="EMAIL">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="disabled" class="toggle-target-disabled" {{if eq
|
||||||
|
.EmailNotificationsPreference "disabled"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.disable"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="enabled" class="toggle-target-disabled" {{if eq
|
||||||
|
.EmailNotificationsPreference "enabled"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.enable"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="andyourown" class="toggle-target-disabled" {{if eq
|
||||||
|
.EmailNotificationsPreference "andyourown"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.andyourown"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="onmention" class="toggle-target-disabled" {{if eq
|
||||||
|
.EmailNotificationsPreference "onmention"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.onmention"}}</label>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.notifications.submit_email"}}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<div class="ui list">
|
||||||
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "settings.ui_notifications_desc"}}</h5>
|
||||||
|
<form action="{{AppSubUrl}}/user/settings/notifications" class="ui form" method="post">
|
||||||
|
{{$.CsrfTokenHtml}}
|
||||||
|
<input name="_method" type="hidden" value="UI">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="disabled" class="toggle-target-disabled" {{if eq
|
||||||
|
.UINotificationsPreference "disabled"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.disable"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="enabled" class="toggle-target-disabled" {{if eq
|
||||||
|
.UINotificationsPreference "enabled"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.enable"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="andyourown" class="toggle-target-disabled" {{if eq
|
||||||
|
.UINotificationsPreference "andyourown"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.andyourown"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input name="preference" type="radio" value="onmention" class="toggle-target-disabled" {{if eq
|
||||||
|
.UINotificationsPreference "onmention"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "settings.notifications.onmention"}}</label>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.notifications.submit_ui"}}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "user/settings/layout_footer" .}}
|
|
@ -4,7 +4,11 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
@ -21,101 +25,167 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type tarFile struct {
|
||||||
|
Name string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeArchiveFileTar(files []*tarFile) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
tarWriter := tar.NewWriter(buf)
|
||||||
|
for _, file := range files {
|
||||||
|
_ = tarWriter.WriteHeader(&tar.Header{
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
Name: file.Name,
|
||||||
|
Mode: 0o644,
|
||||||
|
Size: int64(len(file.Data)),
|
||||||
|
})
|
||||||
|
_, _ = tarWriter.Write(file.Data)
|
||||||
|
}
|
||||||
|
_ = tarWriter.Close()
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeArchiveFileGz(data []byte) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
gzWriter, _ := gzip.NewWriterLevel(buf, gzip.NoCompression)
|
||||||
|
_, _ = gzWriter.Write(data)
|
||||||
|
_ = gzWriter.Close()
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRubyGem(name, version string) []byte {
|
||||||
|
metadataContent := fmt.Sprintf(`--- !ruby/object:Gem::Specification
|
||||||
|
name: %s
|
||||||
|
version: !ruby/object:Gem::Version
|
||||||
|
version: %s
|
||||||
|
platform: ruby
|
||||||
|
authors:
|
||||||
|
- Gitea
|
||||||
|
autorequire:
|
||||||
|
bindir: bin
|
||||||
|
cert_chain: []
|
||||||
|
date: 2021-08-23 00:00:00.000000000 Z
|
||||||
|
dependencies:
|
||||||
|
- !ruby/object:Gem::Dependency
|
||||||
|
name: runtime-dep
|
||||||
|
requirement: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: 1.2.0
|
||||||
|
- - "<"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '2.0'
|
||||||
|
type: :runtime
|
||||||
|
prerelease: false
|
||||||
|
version_requirements: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: 1.2.0
|
||||||
|
- - "<"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '2.0'
|
||||||
|
- !ruby/object:Gem::Dependency
|
||||||
|
name: dev-dep
|
||||||
|
requirement: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - "~>"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '5.2'
|
||||||
|
type: :development
|
||||||
|
prerelease: false
|
||||||
|
version_requirements: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - "~>"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '5.2'
|
||||||
|
description: RubyGems package test
|
||||||
|
email: rubygems@gitea.io
|
||||||
|
executables: []
|
||||||
|
extensions: []
|
||||||
|
extra_rdoc_files: []
|
||||||
|
files:
|
||||||
|
- lib/gitea.rb
|
||||||
|
homepage: https://gitea.io/
|
||||||
|
licenses:
|
||||||
|
- MIT
|
||||||
|
metadata: {}
|
||||||
|
post_install_message:
|
||||||
|
rdoc_options: []
|
||||||
|
require_paths:
|
||||||
|
- lib
|
||||||
|
required_ruby_version: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: 2.3.0
|
||||||
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '1.0'
|
||||||
|
requirements: []
|
||||||
|
rubyforge_project:
|
||||||
|
rubygems_version: 2.7.6.2
|
||||||
|
signing_key:
|
||||||
|
specification_version: 4
|
||||||
|
summary: Gitea package
|
||||||
|
test_files: []
|
||||||
|
`, name, version)
|
||||||
|
|
||||||
|
metadataGz := makeArchiveFileGz([]byte(metadataContent))
|
||||||
|
dataTarGz := makeArchiveFileGz(makeArchiveFileTar([]*tarFile{
|
||||||
|
{
|
||||||
|
Name: "lib/gitea.rb",
|
||||||
|
Data: []byte("class Gitea\nend"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
checksumsYaml := fmt.Sprintf(`---
|
||||||
|
SHA256:
|
||||||
|
metadata.gz: %x
|
||||||
|
data.tar.gz: %x
|
||||||
|
SHA512:
|
||||||
|
metadata.gz: %x
|
||||||
|
data.tar.gz: %x
|
||||||
|
`, sha256.Sum256(metadataGz), sha256.Sum256(dataTarGz), sha512.Sum512(metadataGz), sha512.Sum512(dataTarGz))
|
||||||
|
|
||||||
|
files := []*tarFile{
|
||||||
|
{
|
||||||
|
Name: "data.tar.gz",
|
||||||
|
Data: dataTarGz,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "metadata.gz",
|
||||||
|
Data: metadataGz,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "checksums.yaml.gz",
|
||||||
|
Data: makeArchiveFileGz([]byte(checksumsYaml)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return makeArchiveFileTar(files)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPackageRubyGems(t *testing.T) {
|
func TestPackageRubyGems(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
packageName := "gitea"
|
testGemName := "gitea"
|
||||||
packageVersion := "1.0.5"
|
testGemVersion := "1.0.5"
|
||||||
packageFilename := "gitea-1.0.5.gem"
|
testGemContent := makeRubyGem(testGemName, testGemVersion)
|
||||||
|
testGemContentChecksum := fmt.Sprintf("%x", sha256.Sum256(testGemContent))
|
||||||
|
|
||||||
gemContent, _ := base64.StdEncoding.DecodeString(`bWV0YWRhdGEuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
testAnotherGemName := "gitea-another"
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAw
|
testAnotherGemVersion := "0.99"
|
||||||
MAAwMDAwMDAwADAwMDAwMDAxMDQxADE0MTEwNzcyMzY2ADAxMzQ0MQAgMAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAw
|
|
||||||
MDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf
|
|
||||||
iwgA9vQjYQID1VVNb9QwEL37V5he9pRsmlJAFlQckCoOXAriQIUix5nNmsYf2JOqKwS/nYmz2d3Q
|
|
||||||
qqCCKpFdadfjmfdm5nmcLMv4k9DXm6Wrv4BCcQ5GiPcelF5pJVE7y6w0IHirESS7hhDJJu4I+jhu
|
|
||||||
Mc53Tsd5kZ8y30lcuWAEH2KY7HHtQhQs4+cJkwwuwNdeB6JhtbaNDoLTL1MQsFJrqQnr8jNrJJJH
|
|
||||||
WZTHWfEiK094UYj0zYvp4Z9YAx5sA1ZpSCS3M30zeWwo2bG60FvUBjIKJts2GwMW76r0Yr9NzjN3
|
|
||||||
YhwsGX2Ozl4dpcWwvK9d43PQtDIv9igvHwSyIIwFmXHjqTqxLY8MPkCADmQk80p2EfZ6VbM6/ue6
|
|
||||||
/1D0Bq7/qeA/zh6W82leHmhFWUHn/JbsEfT6q7QbiCpoj8l0QcEUFLmX6kq2wBEiMjBSd+Pwt7T5
|
|
||||||
Ot0kuXYMbkD1KOuOBnWYb7hBsAP4bhlkFRqnqpWefMZ/pHCn6+WIFGq2dgY8EQq+RvRRLJcTyZJ1
|
|
||||||
WhHqGPTu7QdmACXdJFLwb9+ZdxErbSPKrqsMxJhAWCJ1qaqRdtu6yktcT/STsamG0qp7rsa5EL/K
|
|
||||||
MBua30uw4ynzExqYWRJDfx8/kQWN3PwsDh2jYLr1W+pZcAmCs9splvnz/Flesqhbq21bXcGG/OLh
|
|
||||||
+2fv/JTF3hgZyCW9OaZjxoZjdnBGfgKpxZyJ1QYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGF0
|
|
||||||
YS50YXIuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAwMAAw
|
|
||||||
MDAwMDAwADAwMDAwMDAwMjQyADE0MTEwNzcyMzY2ADAxMzM2MQAgMAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfiwgA
|
|
||||||
9vQjYQID7M/NCsMgDABgz32KrA/QxersK/Q17ExXIcyhlr7+HLv1sJ02KPhBCPk5JOyn881nsl2c
|
|
||||||
xI+gRDRaC3zbZ8RBCamlxGHolTFlX11kLwDFH6wp21hO2RYi/rD3bb5/7iCubFOCMbBtABzNkIjn
|
|
||||||
bvGlAnisOUE7EnOALUR2p7b06e6aV4iqqqrquJ4AAAD//wMA+sA/NQAIAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGNoZWNr
|
|
||||||
c3Vtcy55YW1sLmd6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNDQ0ADAwMDAwMDAAMDAw
|
|
||||||
MDAwMAAwMDAwMDAwMDQ1MAAxNDExMDc3MjM2NgAwMTQ2MTIAIDAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDB3aGVlbAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4sIAPb0
|
|
||||||
I2ECA2WQOa4UQAxE8znFXGCQ21vbPyMj5wRuL0Qk6EecnmZCyKyy9FSvXq/X4/u3ryj68Xg+f/Zn
|
|
||||||
VHzGlx+/P57qvU4XxWalBKftSXOgCjNYkdRycrC5Axem+W4HqS12PNEv7836jF9vnlHxwSyxKY+y
|
|
||||||
go0cPblyHzkrZ4HF1GSVhe7mOOoasXNk2fnbUxb+19Pp9tobD/QlJKMX7y204PREh6nQ5hG9Alw6
|
|
||||||
x4TnmtA+aekGfm6wAseog2LSgpR4Q7cYnAH3K4qAQa6A6JCC1gpuY7P+9YxE5SZ+j0eVGbaBTwBQ
|
|
||||||
iIqRUyyzLCoFCBdYNWxniapTavD97blXTzFvgoVoAsKBAtlU48cdaOmeZDpwV01OtcGwjscfeUrY
|
|
||||||
B9QBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|
||||||
|
|
||||||
root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name)
|
root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name)
|
||||||
|
|
||||||
uploadFile := func(t *testing.T, expectedStatus int) {
|
uploadFile := func(t *testing.T, content []byte, expectedStatus int) {
|
||||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(gemContent)).
|
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(content)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, expectedStatus)
|
MakeRequest(t, req, expectedStatus)
|
||||||
}
|
}
|
||||||
|
@ -123,7 +193,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
||||||
t.Run("Upload", func(t *testing.T) {
|
t.Run("Upload", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
uploadFile(t, http.StatusCreated)
|
uploadFile(t, testGemContent, http.StatusCreated)
|
||||||
|
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -133,34 +203,33 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, pd.SemVer)
|
assert.NotNil(t, pd.SemVer)
|
||||||
assert.IsType(t, &rubygems.Metadata{}, pd.Metadata)
|
assert.IsType(t, &rubygems.Metadata{}, pd.Metadata)
|
||||||
assert.Equal(t, packageName, pd.Package.Name)
|
assert.Equal(t, testGemName, pd.Package.Name)
|
||||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
assert.Equal(t, testGemVersion, pd.Version.Version)
|
||||||
|
|
||||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, pfs, 1)
|
assert.Len(t, pfs, 1)
|
||||||
assert.Equal(t, packageFilename, pfs[0].Name)
|
assert.Equal(t, fmt.Sprintf("%s-%s.gem", testGemName, testGemVersion), pfs[0].Name)
|
||||||
assert.True(t, pfs[0].IsLead)
|
assert.True(t, pfs[0].IsLead)
|
||||||
|
|
||||||
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(4608), pb.Size)
|
assert.EqualValues(t, len(testGemContent), pb.Size)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UploadExists", func(t *testing.T) {
|
t.Run("UploadExists", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
uploadFile(t, testGemContent, http.StatusConflict)
|
||||||
uploadFile(t, http.StatusConflict)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Download", func(t *testing.T) {
|
t.Run("Download", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/gems/%s", root, packageFilename)).
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/gems/%s-%s.gem", root, testGemName, testGemVersion)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
assert.Equal(t, gemContent, resp.Body.Bytes())
|
assert.Equal(t, testGemContent, resp.Body.Bytes())
|
||||||
|
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -171,7 +240,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
||||||
t.Run("DownloadGemspec", func(t *testing.T) {
|
t.Run("DownloadGemspec", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/quick/Marshal.4.8/%sspec.rz", root, packageFilename)).
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/quick/Marshal.4.8/%s-%s.gemspec.rz", root, testGemName, testGemVersion)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
@ -206,22 +275,63 @@ gAAAAP//MS06Gw==`)
|
||||||
enumeratePackages(t, "prerelease_specs.4.8.gz", b)
|
enumeratePackages(t, "prerelease_specs.4.8.gz", b)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Delete", func(t *testing.T) {
|
t.Run("UploadAnother", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
uploadFile(t, makeRubyGem(testAnotherGemName, testAnotherGemVersion), http.StatusCreated)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PackageInfo", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, testGemName)).AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
expected := fmt.Sprintf(`---
|
||||||
|
1.0.5 runtime-dep:>= 1.2.0&< 2.0|checksum:%s,ruby:>= 2.3.0,rubygems:>= 1.0
|
||||||
|
`, testGemContentChecksum)
|
||||||
|
assert.Equal(t, expected, resp.Body.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Versions", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, `---
|
||||||
|
gitea 1.0.5 08843c2dd0ea19910e6b056b98e38f1c
|
||||||
|
gitea-another 0.99 8b639e4048d282941485368ec42609be
|
||||||
|
`, resp.Body.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
deleteGemPackage := func(t *testing.T, packageName, packageVersion string) {
|
||||||
body := bytes.Buffer{}
|
body := bytes.Buffer{}
|
||||||
writer := multipart.NewWriter(&body)
|
writer := multipart.NewWriter(&body)
|
||||||
writer.WriteField("gem_name", packageName)
|
_ = writer.WriteField("gem_name", packageName)
|
||||||
writer.WriteField("version", packageVersion)
|
_ = writer.WriteField("version", packageVersion)
|
||||||
writer.Close()
|
_ = writer.Close()
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body).
|
req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body).
|
||||||
SetHeader("Content-Type", writer.FormDataContentType()).
|
SetHeader("Content-Type", writer.FormDataContentType()).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("DeleteAll", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
deleteGemPackage(t, testGemName, testGemVersion)
|
||||||
|
deleteGemPackage(t, testAnotherGemName, testAnotherGemVersion)
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Empty(t, pvs)
|
assert.Empty(t, pvs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("PackageInfoAfterDelete", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, testGemName)).AddBasicAuth(user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("VersionsAfterDelete", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "---\n", resp.Body.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user