Skip to content

Commit 85bd24f

Browse files
committed
Add API for changing Avatars
1 parent b4e4b7a commit 85bd24f

File tree

12 files changed

+652
-1
lines changed

12 files changed

+652
-1
lines changed

modules/structs/repo.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,9 @@ type NewIssuePinsAllowed struct {
380380
Issues bool `json:"issues"`
381381
PullRequests bool `json:"pull_requests"`
382382
}
383+
384+
// UpdateRepoAvatarUserOption options when updating the repo avatar
385+
type UpdateRepoAvatarOption struct {
386+
// image must be base64 encoded
387+
Image string `json:"image" binding:"Required"`
388+
}

modules/structs/user.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,9 @@ type RenameUserOption struct {
102102
// unique: true
103103
NewName string `json:"new_username" binding:"Required"`
104104
}
105+
106+
// UpdateUserAvatarUserOption options when updating the user avatar
107+
type UpdateUserAvatarOption struct {
108+
// image must be base64 encoded
109+
Image string `json:"image" binding:"Required"`
110+
}

routers/api/v1/api.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,11 @@ func Routes() *web.Route {
899899
Patch(bind(api.EditHookOption{}), user.EditHook).
900900
Delete(user.DeleteHook)
901901
}, reqWebhooksEnabled())
902+
903+
m.Group("/avatar", func() {
904+
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
905+
m.Delete("", user.DeleteAvatar)
906+
}, reqToken())
902907
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
903908

904909
// Repositories (requires repo scope, org scope)
@@ -1134,6 +1139,10 @@ func Routes() *web.Route {
11341139
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
11351140
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
11361141
m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
1142+
m.Group("/avatar", func() {
1143+
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
1144+
m.Delete("", repo.DeleteAvatar)
1145+
}, reqAdmin(), reqToken())
11371146
}, repoAssignment())
11381147
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
11391148

@@ -1314,6 +1323,10 @@ func Routes() *web.Route {
13141323
Patch(bind(api.EditHookOption{}), org.EditHook).
13151324
Delete(org.DeleteHook)
13161325
}, reqToken(), reqOrgOwnership(), reqWebhooksEnabled())
1326+
m.Group("/avatar", func() {
1327+
m.Post("", bind(api.UpdateUserAvatarOption{}), org.UpdateAvatar)
1328+
m.Delete("", org.DeleteAvatar)
1329+
}, reqToken(), reqOrgOwnership())
13171330
m.Get("/activities/feeds", org.ListOrgActivityFeeds)
13181331
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
13191332
m.Group("/teams/{teamid}", func() {

routers/api/v1/org/avatar.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package org
5+
6+
import (
7+
"encoding/base64"
8+
"fmt"
9+
"net/http"
10+
11+
"code.gitea.io/gitea/modules/context"
12+
"code.gitea.io/gitea/modules/setting"
13+
api "code.gitea.io/gitea/modules/structs"
14+
"code.gitea.io/gitea/modules/typesniffer"
15+
"code.gitea.io/gitea/modules/web"
16+
user_service "code.gitea.io/gitea/services/user"
17+
)
18+
19+
// UpdateAvatarupdates the Avatar of an Organisation
20+
func UpdateAvatar(ctx *context.APIContext) {
21+
// swagger:operation POST /orgs/{org}/avatar organization orgUpdateAvatar
22+
// ---
23+
// summary: Update Avatar
24+
// produces:
25+
// - application/json
26+
// parameters:
27+
// - name: org
28+
// in: path
29+
// description: name of the organization
30+
// - name: body
31+
// in: body
32+
// schema:
33+
// "$ref": "#/definitions/UpdateUserAvatarOption"
34+
// responses:
35+
// "204":
36+
// "$ref": "#/responses/empty"
37+
form := web.GetForm(ctx).(*api.UpdateUserAvatarOption)
38+
39+
content, err := base64.StdEncoding.DecodeString(form.Image)
40+
if err != nil {
41+
ctx.Error(http.StatusBadRequest, "DecodeImage", err)
42+
return
43+
}
44+
45+
if int64(len(content)) > setting.Avatar.MaxFileSize {
46+
ctx.Error(http.StatusBadRequest, "AvatarTooBig", fmt.Errorf("The avatar is to big"))
47+
return
48+
}
49+
50+
st := typesniffer.DetectContentType(content)
51+
if !(st.IsImage() && !st.IsSvgImage()) {
52+
ctx.Error(http.StatusBadRequest, "NotAnImage", fmt.Errorf("The avatar is not an image"))
53+
return
54+
}
55+
56+
err = user_service.UploadAvatar(ctx.Org.Organization.AsUser(), content)
57+
if err != nil {
58+
ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
59+
}
60+
61+
ctx.Status(http.StatusNoContent)
62+
}
63+
64+
// DeleteAvatar deletes the Avatar of an Organisation
65+
func DeleteAvatar(ctx *context.APIContext) {
66+
// swagger:operation DELETE /orgs/{org}/avatar organization orgDeleteAvatar
67+
// ---
68+
// summary: Delete Avatar
69+
// produces:
70+
// - application/json
71+
// parameters:
72+
// - name: org
73+
// in: path
74+
// description: name of the organization
75+
// responses:
76+
// "204":
77+
// "$ref": "#/responses/empty"
78+
err := user_service.DeleteAvatar(ctx.Org.Organization.AsUser())
79+
if err != nil {
80+
ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
81+
}
82+
83+
ctx.Status(http.StatusNoContent)
84+
}

routers/api/v1/repo/avatar.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repo
5+
6+
import (
7+
"encoding/base64"
8+
"fmt"
9+
"net/http"
10+
11+
"code.gitea.io/gitea/modules/context"
12+
"code.gitea.io/gitea/modules/setting"
13+
api "code.gitea.io/gitea/modules/structs"
14+
"code.gitea.io/gitea/modules/typesniffer"
15+
"code.gitea.io/gitea/modules/web"
16+
repo_service "code.gitea.io/gitea/services/repository"
17+
)
18+
19+
// UpdateVatar updates the Avatar of an Repo
20+
func UpdateAvatar(ctx *context.APIContext) {
21+
// swagger:operation POST /repos/{owner}/{repo}/avatar repository repoUpdateAvatar
22+
// ---
23+
// summary: Update avatar
24+
// produces:
25+
// - application/json
26+
// parameters:
27+
// - name: owner
28+
// in: path
29+
// description: owner of the repo
30+
// type: string
31+
// required: true
32+
// - name: repo
33+
// in: path
34+
// description: name of the repo
35+
// type: string
36+
// required: true
37+
// - name: body
38+
// in: body
39+
// schema:
40+
// "$ref": "#/definitions/UpdateRepoAvatarOption"
41+
// responses:
42+
// "204":
43+
// "$ref": "#/responses/empty"
44+
form := web.GetForm(ctx).(*api.UpdateRepoAvatarOption)
45+
46+
content, err := base64.StdEncoding.DecodeString(form.Image)
47+
if err != nil {
48+
ctx.Error(http.StatusBadRequest, "DecodeImage", err)
49+
return
50+
}
51+
52+
if int64(len(content)) > setting.Avatar.MaxFileSize {
53+
ctx.Error(http.StatusBadRequest, "AvatarTooBig", fmt.Errorf("The avatar is to big"))
54+
return
55+
}
56+
57+
st := typesniffer.DetectContentType(content)
58+
if !(st.IsImage() && !st.IsSvgImage()) {
59+
ctx.Error(http.StatusBadRequest, "NotAnImage", fmt.Errorf("The avatar is not an image"))
60+
return
61+
}
62+
63+
err = repo_service.UploadAvatar(ctx, ctx.Repo.Repository, content)
64+
if err != nil {
65+
ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
66+
}
67+
68+
ctx.Status(http.StatusNoContent)
69+
}
70+
71+
// UpdateAvatar deletes the Avatar of an Repo
72+
func DeleteAvatar(ctx *context.APIContext) {
73+
// swagger:operation DELETE /repos/{owner}/{repo}/avatar repository repoDeleteAvatar
74+
// ---
75+
// summary: Delete avatar
76+
// produces:
77+
// - application/json
78+
// parameters:
79+
// - name: owner
80+
// in: path
81+
// description: owner of the repo
82+
// type: string
83+
// required: true
84+
// - name: repo
85+
// in: path
86+
// description: name of the repo
87+
// type: string
88+
// required: true
89+
// responses:
90+
// "204":
91+
// "$ref": "#/responses/empty"
92+
err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository)
93+
if err != nil {
94+
ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
95+
}
96+
97+
ctx.Status(http.StatusNoContent)
98+
}

routers/api/v1/swagger/options.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,10 @@ type swaggerParameterBodies struct {
181181

182182
// in:body
183183
CreatePushMirrorOption api.CreatePushMirrorOption
184+
185+
// in:body
186+
UpdateUserAvatarOptions api.UpdateUserAvatarOption
187+
188+
// in:body
189+
UpdateRepoAvatarOptions api.UpdateRepoAvatarOption
184190
}

routers/api/v1/user/avatar.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package user
5+
6+
import (
7+
"encoding/base64"
8+
"fmt"
9+
"net/http"
10+
11+
"code.gitea.io/gitea/modules/context"
12+
"code.gitea.io/gitea/modules/setting"
13+
api "code.gitea.io/gitea/modules/structs"
14+
"code.gitea.io/gitea/modules/typesniffer"
15+
"code.gitea.io/gitea/modules/web"
16+
user_service "code.gitea.io/gitea/services/user"
17+
)
18+
19+
// UpdateAvatar updates the Avatar of an User
20+
func UpdateAvatar(ctx *context.APIContext) {
21+
// swagger:operation POST /user/avatar user userUpdateAvatar
22+
// ---
23+
// summary: Update Avatar
24+
// produces:
25+
// - application/json
26+
// parameters:
27+
// - name: body
28+
// in: body
29+
// schema:
30+
// "$ref": "#/definitions/UpdateUserAvatarOption"
31+
// responses:
32+
// "204":
33+
// "$ref": "#/responses/empty"
34+
form := web.GetForm(ctx).(*api.UpdateUserAvatarOption)
35+
36+
content, err := base64.StdEncoding.DecodeString(form.Image)
37+
if err != nil {
38+
ctx.Error(http.StatusBadRequest, "DecodeImage", err)
39+
return
40+
}
41+
42+
if int64(len(content)) > setting.Avatar.MaxFileSize {
43+
ctx.Error(http.StatusBadRequest, "AvatarTooBig", fmt.Errorf("The avatar is to big"))
44+
return
45+
}
46+
47+
st := typesniffer.DetectContentType(content)
48+
if !(st.IsImage() && !st.IsSvgImage()) {
49+
ctx.Error(http.StatusBadRequest, "NotAnImage", fmt.Errorf("The avatar is not an image"))
50+
return
51+
}
52+
53+
err = user_service.UploadAvatar(ctx.Doer, content)
54+
if err != nil {
55+
ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
56+
}
57+
58+
ctx.Status(http.StatusNoContent)
59+
}
60+
61+
// DeleteAvatar deletes the Avatar of an User
62+
func DeleteAvatar(ctx *context.APIContext) {
63+
// swagger:operation DELETE /user/avatar user userDeleteAvatar
64+
// ---
65+
// summary: Delete Avatar
66+
// produces:
67+
// - application/json
68+
// responses:
69+
// "204":
70+
// "$ref": "#/responses/empty"
71+
err := user_service.DeleteAvatar(ctx.Doer)
72+
if err != nil {
73+
ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
74+
}
75+
76+
ctx.Status(http.StatusNoContent)
77+
}

0 commit comments

Comments
 (0)