You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

convert.go 15KB

Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
4 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
4 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
4 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
4 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
4 years ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add API endpoint for accessing repo topics (#7963) * Create API endpoints for repo topics. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Generate swagger Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add documentation to functions Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Grammar fix Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix function comment Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Can't use FindTopics when looking for a single repo topic, as it doesnt use exact match Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add PUT ​/repos​/{owner}​/{repo}​/topics and remove GET ​/repos​/{owner}​/{repo}​/topics * Ignore if topic is sent twice in same request, refactoring. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix topic dropdown with api changes. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Style fix Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Update API documentation Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Better way to handle duplicate topics in slice Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make response element TopicName an array of strings, instead of using an array of TopicName Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add test cases for API Repo Topics. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix format of tests Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix comments Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix unit tests after adding some more topics to the test fixture. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Update models/topic.go Limit multiple if else if ... Co-Authored-By: Antoine GIRARD <sapk@users.noreply.github.com> * Engine as first parameter in function Co-Authored-By: Antoine GIRARD <sapk@users.noreply.github.com> * Replace magic numbers with http status code constants. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix variable scope Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Test one read with login and one with token Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add some more tests Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Apply suggestions from code review Use empty struct for efficiency Co-Authored-By: Lauris BH <lauris@nix.lv> * Add test case to check access for user with write access Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix access, repo admin required to change topics Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Correct first test to be without token Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Any repo reader should be able to access topics. * No need for string pointer Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
4 years ago
Add API endpoint for accessing repo topics (#7963) * Create API endpoints for repo topics. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Generate swagger Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add documentation to functions Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Grammar fix Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix function comment Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Can't use FindTopics when looking for a single repo topic, as it doesnt use exact match Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add PUT ​/repos​/{owner}​/{repo}​/topics and remove GET ​/repos​/{owner}​/{repo}​/topics * Ignore if topic is sent twice in same request, refactoring. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix topic dropdown with api changes. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Style fix Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Update API documentation Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Better way to handle duplicate topics in slice Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make response element TopicName an array of strings, instead of using an array of TopicName Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add test cases for API Repo Topics. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix format of tests Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix comments Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix unit tests after adding some more topics to the test fixture. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Update models/topic.go Limit multiple if else if ... Co-Authored-By: Antoine GIRARD <sapk@users.noreply.github.com> * Engine as first parameter in function Co-Authored-By: Antoine GIRARD <sapk@users.noreply.github.com> * Replace magic numbers with http status code constants. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix variable scope Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Test one read with login and one with token Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add some more tests Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Apply suggestions from code review Use empty struct for efficiency Co-Authored-By: Lauris BH <lauris@nix.lv> * Add test case to check access for user with write access Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix access, repo admin required to change topics Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Correct first test to be without token Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Any repo reader should be able to access topics. * No need for string pointer Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
4 years ago
Record OAuth client type at registration (#21316) The OAuth spec [defines two types of client](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1), confidential and public. Previously Gitea assumed all clients to be confidential. > OAuth defines two client types, based on their ability to authenticate securely with the authorization server (i.e., ability to > maintain the confidentiality of their client credentials): > > confidential > Clients capable of maintaining the confidentiality of their credentials (e.g., client implemented on a secure server with > restricted access to the client credentials), or capable of secure client authentication using other means. > > **public > Clients incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means.** > > The client type designation is based on the authorization server's definition of secure authentication and its acceptable exposure levels of client credentials. The authorization server SHOULD NOT make assumptions about the client type. https://datatracker.ietf.org/doc/html/rfc8252#section-8.4 > Authorization servers MUST record the client type in the client registration details in order to identify and process requests accordingly. Require PKCE for public clients: https://datatracker.ietf.org/doc/html/rfc8252#section-8.1 > Authorization servers SHOULD reject authorization requests from native apps that don't use PKCE by returning an error message Fixes #21299 Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package convert
  5. import (
  6. "context"
  7. "fmt"
  8. "strconv"
  9. "strings"
  10. "time"
  11. asymkey_model "code.gitea.io/gitea/models/asymkey"
  12. "code.gitea.io/gitea/models/auth"
  13. git_model "code.gitea.io/gitea/models/git"
  14. issues_model "code.gitea.io/gitea/models/issues"
  15. "code.gitea.io/gitea/models/organization"
  16. "code.gitea.io/gitea/models/perm"
  17. access_model "code.gitea.io/gitea/models/perm/access"
  18. repo_model "code.gitea.io/gitea/models/repo"
  19. "code.gitea.io/gitea/models/unit"
  20. user_model "code.gitea.io/gitea/models/user"
  21. "code.gitea.io/gitea/modules/container"
  22. "code.gitea.io/gitea/modules/git"
  23. "code.gitea.io/gitea/modules/log"
  24. api "code.gitea.io/gitea/modules/structs"
  25. "code.gitea.io/gitea/modules/util"
  26. "code.gitea.io/gitea/services/gitdiff"
  27. )
  28. // ToEmail convert models.EmailAddress to api.Email
  29. func ToEmail(email *user_model.EmailAddress) *api.Email {
  30. return &api.Email{
  31. Email: email.Email,
  32. Verified: email.IsActivated,
  33. Primary: email.IsPrimary,
  34. }
  35. }
  36. // ToEmail convert models.EmailAddress to api.Email
  37. func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email {
  38. return &api.Email{
  39. Email: email.Email,
  40. Verified: email.IsActivated,
  41. Primary: email.IsPrimary,
  42. UserID: email.UID,
  43. UserName: email.Name,
  44. }
  45. }
  46. // ToBranch convert a git.Commit and git.Branch to an api.Branch
  47. func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
  48. if bp == nil {
  49. var hasPerm bool
  50. var canPush bool
  51. var err error
  52. if user != nil {
  53. hasPerm, err = access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
  54. if err != nil {
  55. return nil, err
  56. }
  57. perms, err := access_model.GetUserRepoPermission(ctx, repo, user)
  58. if err != nil {
  59. return nil, err
  60. }
  61. canPush = issues_model.CanMaintainerWriteToBranch(ctx, perms, branchName, user)
  62. }
  63. return &api.Branch{
  64. Name: branchName,
  65. Commit: ToPayloadCommit(ctx, repo, c),
  66. Protected: false,
  67. RequiredApprovals: 0,
  68. EnableStatusCheck: false,
  69. StatusCheckContexts: []string{},
  70. UserCanPush: canPush,
  71. UserCanMerge: hasPerm,
  72. }, nil
  73. }
  74. branch := &api.Branch{
  75. Name: branchName,
  76. Commit: ToPayloadCommit(ctx, repo, c),
  77. Protected: true,
  78. RequiredApprovals: bp.RequiredApprovals,
  79. EnableStatusCheck: bp.EnableStatusCheck,
  80. StatusCheckContexts: bp.StatusCheckContexts,
  81. }
  82. if isRepoAdmin {
  83. branch.EffectiveBranchProtectionName = bp.RuleName
  84. }
  85. if user != nil {
  86. permission, err := access_model.GetUserRepoPermission(ctx, repo, user)
  87. if err != nil {
  88. return nil, err
  89. }
  90. bp.Repo = repo
  91. branch.UserCanPush = bp.CanUserPush(ctx, user)
  92. branch.UserCanMerge = git_model.IsUserMergeWhitelisted(ctx, bp, user.ID, permission)
  93. }
  94. return branch, nil
  95. }
  96. // getWhitelistEntities returns the names of the entities that are in the whitelist
  97. func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T, whitelistIDs []int64) []string {
  98. whitelistUserIDsSet := container.SetOf(whitelistIDs...)
  99. whitelistNames := make([]string, 0)
  100. for _, entity := range entities {
  101. switch v := any(entity).(type) {
  102. case *user_model.User:
  103. if whitelistUserIDsSet.Contains(v.ID) {
  104. whitelistNames = append(whitelistNames, v.Name)
  105. }
  106. case *organization.Team:
  107. if whitelistUserIDsSet.Contains(v.ID) {
  108. whitelistNames = append(whitelistNames, v.Name)
  109. }
  110. }
  111. }
  112. return whitelistNames
  113. }
  114. // ToBranchProtection convert a ProtectedBranch to api.BranchProtection
  115. func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
  116. readers, err := access_model.GetRepoReaders(ctx, repo)
  117. if err != nil {
  118. log.Error("GetRepoReaders: %v", err)
  119. }
  120. pushWhitelistUsernames := getWhitelistEntities(readers, bp.WhitelistUserIDs)
  121. mergeWhitelistUsernames := getWhitelistEntities(readers, bp.MergeWhitelistUserIDs)
  122. approvalsWhitelistUsernames := getWhitelistEntities(readers, bp.ApprovalsWhitelistUserIDs)
  123. teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
  124. if err != nil {
  125. log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
  126. }
  127. pushWhitelistTeams := getWhitelistEntities(teamReaders, bp.WhitelistTeamIDs)
  128. mergeWhitelistTeams := getWhitelistEntities(teamReaders, bp.MergeWhitelistTeamIDs)
  129. approvalsWhitelistTeams := getWhitelistEntities(teamReaders, bp.ApprovalsWhitelistTeamIDs)
  130. branchName := ""
  131. if !git_model.IsRuleNameSpecial(bp.RuleName) {
  132. branchName = bp.RuleName
  133. }
  134. return &api.BranchProtection{
  135. BranchName: branchName,
  136. RuleName: bp.RuleName,
  137. EnablePush: bp.CanPush,
  138. EnablePushWhitelist: bp.EnableWhitelist,
  139. PushWhitelistUsernames: pushWhitelistUsernames,
  140. PushWhitelistTeams: pushWhitelistTeams,
  141. PushWhitelistDeployKeys: bp.WhitelistDeployKeys,
  142. EnableMergeWhitelist: bp.EnableMergeWhitelist,
  143. MergeWhitelistUsernames: mergeWhitelistUsernames,
  144. MergeWhitelistTeams: mergeWhitelistTeams,
  145. EnableStatusCheck: bp.EnableStatusCheck,
  146. StatusCheckContexts: bp.StatusCheckContexts,
  147. RequiredApprovals: bp.RequiredApprovals,
  148. EnableApprovalsWhitelist: bp.EnableApprovalsWhitelist,
  149. ApprovalsWhitelistUsernames: approvalsWhitelistUsernames,
  150. ApprovalsWhitelistTeams: approvalsWhitelistTeams,
  151. BlockOnRejectedReviews: bp.BlockOnRejectedReviews,
  152. BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
  153. BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch,
  154. DismissStaleApprovals: bp.DismissStaleApprovals,
  155. IgnoreStaleApprovals: bp.IgnoreStaleApprovals,
  156. RequireSignedCommits: bp.RequireSignedCommits,
  157. ProtectedFilePatterns: bp.ProtectedFilePatterns,
  158. UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
  159. Created: bp.CreatedUnix.AsTime(),
  160. Updated: bp.UpdatedUnix.AsTime(),
  161. }
  162. }
  163. // ToTag convert a git.Tag to an api.Tag
  164. func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
  165. return &api.Tag{
  166. Name: t.Name,
  167. Message: strings.TrimSpace(t.Message),
  168. ID: t.ID.String(),
  169. Commit: ToCommitMeta(repo, t),
  170. ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),
  171. TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"),
  172. }
  173. }
  174. // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
  175. func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
  176. verif := asymkey_model.ParseCommitWithSignature(ctx, c)
  177. commitVerification := &api.PayloadCommitVerification{
  178. Verified: verif.Verified,
  179. Reason: verif.Reason,
  180. }
  181. if c.Signature != nil {
  182. commitVerification.Signature = c.Signature.Signature
  183. commitVerification.Payload = c.Signature.Payload
  184. }
  185. if verif.SigningUser != nil {
  186. commitVerification.Signer = &api.PayloadUser{
  187. Name: verif.SigningUser.Name,
  188. Email: verif.SigningUser.Email,
  189. }
  190. }
  191. return commitVerification
  192. }
  193. // ToPublicKey convert asymkey_model.PublicKey to api.PublicKey
  194. func ToPublicKey(apiLink string, key *asymkey_model.PublicKey) *api.PublicKey {
  195. return &api.PublicKey{
  196. ID: key.ID,
  197. Key: key.Content,
  198. URL: fmt.Sprintf("%s%d", apiLink, key.ID),
  199. Title: key.Name,
  200. Fingerprint: key.Fingerprint,
  201. Created: key.CreatedUnix.AsTime(),
  202. }
  203. }
  204. // ToGPGKey converts models.GPGKey to api.GPGKey
  205. func ToGPGKey(key *asymkey_model.GPGKey) *api.GPGKey {
  206. subkeys := make([]*api.GPGKey, len(key.SubsKey))
  207. for id, k := range key.SubsKey {
  208. subkeys[id] = &api.GPGKey{
  209. ID: k.ID,
  210. PrimaryKeyID: k.PrimaryKeyID,
  211. KeyID: k.KeyID,
  212. PublicKey: k.Content,
  213. Created: k.CreatedUnix.AsTime(),
  214. Expires: k.ExpiredUnix.AsTime(),
  215. CanSign: k.CanSign,
  216. CanEncryptComms: k.CanEncryptComms,
  217. CanEncryptStorage: k.CanEncryptStorage,
  218. CanCertify: k.CanSign,
  219. Verified: k.Verified,
  220. }
  221. }
  222. emails := make([]*api.GPGKeyEmail, len(key.Emails))
  223. for i, e := range key.Emails {
  224. emails[i] = ToGPGKeyEmail(e)
  225. }
  226. return &api.GPGKey{
  227. ID: key.ID,
  228. PrimaryKeyID: key.PrimaryKeyID,
  229. KeyID: key.KeyID,
  230. PublicKey: key.Content,
  231. Created: key.CreatedUnix.AsTime(),
  232. Expires: key.ExpiredUnix.AsTime(),
  233. Emails: emails,
  234. SubsKey: subkeys,
  235. CanSign: key.CanSign,
  236. CanEncryptComms: key.CanEncryptComms,
  237. CanEncryptStorage: key.CanEncryptStorage,
  238. CanCertify: key.CanSign,
  239. Verified: key.Verified,
  240. }
  241. }
  242. // ToGPGKeyEmail convert models.EmailAddress to api.GPGKeyEmail
  243. func ToGPGKeyEmail(email *user_model.EmailAddress) *api.GPGKeyEmail {
  244. return &api.GPGKeyEmail{
  245. Email: email.Email,
  246. Verified: email.IsActivated,
  247. }
  248. }
  249. // ToGitHook convert git.Hook to api.GitHook
  250. func ToGitHook(h *git.Hook) *api.GitHook {
  251. return &api.GitHook{
  252. Name: h.Name(),
  253. IsActive: h.IsActive,
  254. Content: h.Content,
  255. }
  256. }
  257. // ToDeployKey convert asymkey_model.DeployKey to api.DeployKey
  258. func ToDeployKey(apiLink string, key *asymkey_model.DeployKey) *api.DeployKey {
  259. return &api.DeployKey{
  260. ID: key.ID,
  261. KeyID: key.KeyID,
  262. Key: key.Content,
  263. Fingerprint: key.Fingerprint,
  264. URL: fmt.Sprintf("%s%d", apiLink, key.ID),
  265. Title: key.Name,
  266. Created: key.CreatedUnix.AsTime(),
  267. ReadOnly: key.Mode == perm.AccessModeRead, // All deploy keys are read-only.
  268. }
  269. }
  270. // ToOrganization convert user_model.User to api.Organization
  271. func ToOrganization(ctx context.Context, org *organization.Organization) *api.Organization {
  272. return &api.Organization{
  273. ID: org.ID,
  274. AvatarURL: org.AsUser().AvatarLink(ctx),
  275. Name: org.Name,
  276. UserName: org.Name,
  277. FullName: org.FullName,
  278. Email: org.Email,
  279. Description: org.Description,
  280. Website: org.Website,
  281. Location: org.Location,
  282. Visibility: org.Visibility.String(),
  283. RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
  284. }
  285. }
  286. // ToTeam convert models.Team to api.Team
  287. func ToTeam(ctx context.Context, team *organization.Team, loadOrg ...bool) (*api.Team, error) {
  288. teams, err := ToTeams(ctx, []*organization.Team{team}, len(loadOrg) != 0 && loadOrg[0])
  289. if err != nil || len(teams) == 0 {
  290. return nil, err
  291. }
  292. return teams[0], nil
  293. }
  294. // ToTeams convert models.Team list to api.Team list
  295. func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]*api.Team, error) {
  296. cache := make(map[int64]*api.Organization)
  297. apiTeams := make([]*api.Team, 0, len(teams))
  298. for _, t := range teams {
  299. if err := t.LoadUnits(ctx); err != nil {
  300. return nil, err
  301. }
  302. apiTeam := &api.Team{
  303. ID: t.ID,
  304. Name: t.Name,
  305. Description: t.Description,
  306. IncludesAllRepositories: t.IncludesAllRepositories,
  307. CanCreateOrgRepo: t.CanCreateOrgRepo,
  308. Permission: t.AccessMode.ToString(),
  309. Units: t.GetUnitNames(),
  310. UnitsMap: t.GetUnitsMap(),
  311. }
  312. if loadOrgs {
  313. apiOrg, ok := cache[t.OrgID]
  314. if !ok {
  315. org, err := organization.GetOrgByID(ctx, t.OrgID)
  316. if err != nil {
  317. return nil, err
  318. }
  319. apiOrg = ToOrganization(ctx, org)
  320. cache[t.OrgID] = apiOrg
  321. }
  322. apiTeam.Organization = apiOrg
  323. }
  324. apiTeams = append(apiTeams, apiTeam)
  325. }
  326. return apiTeams, nil
  327. }
  328. // ToAnnotatedTag convert git.Tag to api.AnnotatedTag
  329. func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag {
  330. return &api.AnnotatedTag{
  331. Tag: t.Name,
  332. SHA: t.ID.String(),
  333. Object: ToAnnotatedTagObject(repo, c),
  334. Message: t.Message,
  335. URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()),
  336. Tagger: ToCommitUser(t.Tagger),
  337. Verification: ToVerification(ctx, c),
  338. }
  339. }
  340. // ToAnnotatedTagObject convert a git.Commit to an api.AnnotatedTagObject
  341. func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.AnnotatedTagObject {
  342. return &api.AnnotatedTagObject{
  343. SHA: commit.ID.String(),
  344. Type: string(git.ObjectCommit),
  345. URL: util.URLJoin(repo.APIURL(), "git/commits", commit.ID.String()),
  346. }
  347. }
  348. // ToTopicResponse convert from models.Topic to api.TopicResponse
  349. func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
  350. return &api.TopicResponse{
  351. ID: topic.ID,
  352. Name: topic.Name,
  353. RepoCount: topic.RepoCount,
  354. Created: topic.CreatedUnix.AsTime(),
  355. Updated: topic.UpdatedUnix.AsTime(),
  356. }
  357. }
  358. // ToOAuth2Application convert from auth.OAuth2Application to api.OAuth2Application
  359. func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {
  360. return &api.OAuth2Application{
  361. ID: app.ID,
  362. Name: app.Name,
  363. ClientID: app.ClientID,
  364. ClientSecret: app.ClientSecret,
  365. ConfidentialClient: app.ConfidentialClient,
  366. RedirectURIs: app.RedirectURIs,
  367. Created: app.CreatedUnix.AsTime(),
  368. }
  369. }
  370. // ToLFSLock convert a LFSLock to api.LFSLock
  371. func ToLFSLock(ctx context.Context, l *git_model.LFSLock) *api.LFSLock {
  372. u, err := user_model.GetUserByID(ctx, l.OwnerID)
  373. if err != nil {
  374. return nil
  375. }
  376. return &api.LFSLock{
  377. ID: strconv.FormatInt(l.ID, 10),
  378. Path: l.Path,
  379. LockedAt: l.Created.Round(time.Second),
  380. Owner: &api.LFSLockOwner{
  381. Name: u.Name,
  382. },
  383. }
  384. }
  385. // ToChangedFile convert a gitdiff.DiffFile to api.ChangedFile
  386. func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit string) *api.ChangedFile {
  387. status := "changed"
  388. if f.IsDeleted {
  389. status = "deleted"
  390. } else if f.IsCreated {
  391. status = "added"
  392. } else if f.IsRenamed && f.Type == gitdiff.DiffFileCopy {
  393. status = "copied"
  394. } else if f.IsRenamed && f.Type == gitdiff.DiffFileRename {
  395. status = "renamed"
  396. } else if f.Addition == 0 && f.Deletion == 0 {
  397. status = "unchanged"
  398. }
  399. file := &api.ChangedFile{
  400. Filename: f.GetDiffFileName(),
  401. Status: status,
  402. Additions: f.Addition,
  403. Deletions: f.Deletion,
  404. Changes: f.Addition + f.Deletion,
  405. HTMLURL: fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
  406. ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit),
  407. RawURL: fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
  408. }
  409. if status == "rename" {
  410. file.PreviousFilename = f.OldName
  411. }
  412. return file
  413. }