Compare commits

...

6 commits

Author SHA1 Message Date
tobi b945536f2a Merge branch 'main' into domain_permission_subscriptions 2024-10-28 16:26:47 +01:00
tobi cfe2b61bdd bwap bwap 2024-10-28 14:52:09 +01:00
dependabot[bot] 1954ccba50
[chore]: Bump github.com/minio/minio-go/v7 from 7.0.78 to 7.0.79 (#3488)
Bumps [github.com/minio/minio-go/v7](https://github.com/minio/minio-go) from 7.0.78 to 7.0.79.
- [Release notes](https://github.com/minio/minio-go/releases)
- [Commits](https://github.com/minio/minio-go/compare/v7.0.78...v7.0.79)

---
updated-dependencies:
- dependency-name: github.com/minio/minio-go/v7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 14:44:04 +01:00
kim 7ec6509e11
[bugfix] support classifying correct video codec without audio as webm (#3494)
* for webm support video:[vp8,vp9,av1] and audio:[NONE,vorbis,opus]

* improved unsupported data type error output
2024-10-28 14:09:21 +01:00
kim e86592bc32
[chore] pull in go-ffmpreg v0.4.1 (#3485)
* pull in go-ffmpreg v0.4.1

* bring back GTS_WAZERO_COMPILATION_CACHE
2024-10-28 10:55:48 +00:00
dependabot[bot] 8f4a96c7cd
[chore]: Bump github.com/tdewolff/minify/v2 from 2.21.0 to 2.21.1 (#3489)
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.21.0 to 2.21.1.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.21.0...v2.21.1)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:07:58 +00:00
40 changed files with 2196 additions and 244 deletions

8
go.mod
View file

@ -12,7 +12,7 @@ require (
codeberg.org/gruf/go-debug v1.3.0 codeberg.org/gruf/go-debug v1.3.0
codeberg.org/gruf/go-errors/v2 v2.3.2 codeberg.org/gruf/go-errors/v2 v2.3.2
codeberg.org/gruf/go-fastcopy v1.1.3 codeberg.org/gruf/go-fastcopy v1.1.3
codeberg.org/gruf/go-ffmpreg v0.3.1 codeberg.org/gruf/go-ffmpreg v0.4.2
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf
codeberg.org/gruf/go-kv v1.6.5 codeberg.org/gruf/go-kv v1.6.5
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
@ -42,7 +42,7 @@ require (
github.com/k3a/html2text v1.2.1 github.com/k3a/html2text v1.2.1
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/miekg/dns v1.1.62 github.com/miekg/dns v1.1.62
github.com/minio/minio-go/v7 v7.0.78 github.com/minio/minio-go/v7 v7.0.79
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/ncruces/go-sqlite3 v0.20.0 github.com/ncruces/go-sqlite3 v0.20.0
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
@ -53,7 +53,7 @@ require (
github.com/superseriousbusiness/activity v1.9.0-gts github.com/superseriousbusiness/activity v1.9.0-gts
github.com/superseriousbusiness/httpsig v1.2.0-SSB github.com/superseriousbusiness/httpsig v1.2.0-SSB
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
github.com/tdewolff/minify/v2 v2.21.0 github.com/tdewolff/minify/v2 v2.21.1
github.com/technologize/otel-go-contrib v1.1.1 github.com/technologize/otel-go-contrib v1.1.1
github.com/tetratelabs/wazero v1.8.1 github.com/tetratelabs/wazero v1.8.1
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
@ -197,7 +197,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect
github.com/tdewolff/parse/v2 v2.7.17 // indirect github.com/tdewolff/parse/v2 v2.7.18 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect

16
go.sum generated
View file

@ -46,8 +46,8 @@ codeberg.org/gruf/go-fastcopy v1.1.3 h1:Jo9VTQjI6KYimlw25PPc7YLA3Xm+XMQhaHwKnM7x
codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s= codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0= codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0=
codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q= codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q=
codeberg.org/gruf/go-ffmpreg v0.3.1 h1:5qE6sHQbLCbQ4RO7ZL4OKZBN4ViAYfDm9ExT8N0ZE7s= codeberg.org/gruf/go-ffmpreg v0.4.2 h1:HKkPapm/PWkxsnUdjyQOGpwl5Qoa2EBrUQ09s4R4/FA=
codeberg.org/gruf/go-ffmpreg v0.3.1/go.mod h1:Ar5nbt3tB2Wr0uoaqV3wDBNwAx+H+AB/mV7Kw7NlZTI= codeberg.org/gruf/go-ffmpreg v0.4.2/go.mod h1:Ar5nbt3tB2Wr0uoaqV3wDBNwAx+H+AB/mV7Kw7NlZTI=
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A=
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk=
codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0= codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0=
@ -413,8 +413,8 @@ github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.78 h1:LqW2zy52fxnI4gg8C2oZviTaKHcBV36scS+RzJnxUFs= github.com/minio/minio-go/v7 v7.0.79 h1:SvJZpj3hT0RN+4KiuX/FxLfPZdsuegy6d/2PiemM/bM=
github.com/minio/minio-go/v7 v7.0.78/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= github.com/minio/minio-go/v7 v7.0.79/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -539,10 +539,10 @@ github.com/superseriousbusiness/httpsig v1.2.0-SSB h1:BinBGKbf2LSuVT5+MuH0XynHN9
github.com/superseriousbusiness/httpsig v1.2.0-SSB/go.mod h1:+rxfATjFaDoDIVaJOTSP0gj6UrbicaYPEptvCLC9F28= github.com/superseriousbusiness/httpsig v1.2.0-SSB/go.mod h1:+rxfATjFaDoDIVaJOTSP0gj6UrbicaYPEptvCLC9F28=
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ= github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ=
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo= github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo=
github.com/tdewolff/minify/v2 v2.21.0 h1:nAPP1UVx0aK1xsQh/JiG3xyEnnqWw+agPstn+V6Pkto= github.com/tdewolff/minify/v2 v2.21.1 h1:AAf5iltw6+KlUvjRNPAPrANIXl3XEJNBBzuZom5iCAM=
github.com/tdewolff/minify/v2 v2.21.0/go.mod h1:hGcthJ6Vj51NG+9QRIfN/DpWj5loHnY3bfhThzWWq08= github.com/tdewolff/minify/v2 v2.21.1/go.mod h1:PoqFH8ugcuTUvKqVM9vOqXw4msxvuhL/DTmV5ZXhSCI=
github.com/tdewolff/parse/v2 v2.7.17 h1:uC10p6DaQQORDy72eaIyD+AvAkaIUOouQ0nWp4uD0D0= github.com/tdewolff/parse/v2 v2.7.18 h1:uSqjEMT2lwCj5oifBHDcWU2kN1pbLrRENgFWDJa57eI=
github.com/tdewolff/parse/v2 v2.7.17/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= github.com/tdewolff/parse/v2 v2.7.18/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=

View file

@ -0,0 +1,99 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package admin
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
// DomainPermissionDraftGETHandler swagger:operation GET /api/v1/admin/domain_permission_drafts/{id} domainPermissionDraftGet
//
// Get domain permission draft with the given ID.
//
// ---
// tags:
// - admin
//
// produces:
// - application/json
//
// parameters:
// -
// name: id
// required: true
// in: path
// description: ID of the domain permission draft.
// type: string
//
// security:
// - OAuth2 Bearer:
// - admin
//
// responses:
// '200':
// description: Domain permission draft.
// schema:
// "$ref": "#/definitions/domainPermission"
// '401':
// description: unauthorized
// '403':
// description: forbidden
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) DomainPermissionDraftGETHandler(c *gin.Context) {
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
return
}
if !*authed.User.Admin {
err := fmt.Errorf("user %s not an admin", authed.User.ID)
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return
}
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
permDraft, errWithCode := m.processor.Admin().DomainPermissionDraftGet(c.Request.Context(), id)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
apiutil.JSON(c, http.StatusOK, permDraft)
}

View file

@ -0,0 +1,173 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package admin
import (
"errors"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
// DomainPermissionDraftsPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts domainPermissionDraftCreate
//
// Create a domain permission draft with the given parameters.
//
// ---
// tags:
// - admin
//
// consumes:
// - multipart/form-data
//
// produces:
// - application/json
//
// parameters:
// -
// name: domain
// in: formData
// description: Domain to create the permission draft for.
// type: string
// -
// name: permission_type
// type: string
// description: Create a draft "allow" or a draft "block".
// in: query
// -
// name: obfuscate
// in: formData
// description: >-
// Obfuscate the name of the domain when serving it publicly.
// Eg., `example.org` becomes something like `ex***e.org`.
// type: boolean
// -
// name: public_comment
// in: formData
// description: >-
// Public comment about this domain permission.
// This will be displayed alongside the domain permission if you choose to share permissions.
// type: string
// -
// name: private_comment
// in: formData
// description: >-
// Private comment about this domain permission. Will only be shown to other admins, so this
// is a useful way of internally keeping track of why a certain domain ended up permissioned.
// type: string
//
// security:
// - OAuth2 Bearer:
// - admin
//
// responses:
// '200':
// description: The newly created domain permission draft.
// schema:
// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '403':
// description: forbidden
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) DomainPermissionDraftsPOSTHandler(c *gin.Context) {
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
return
}
if !*authed.User.Admin {
err := fmt.Errorf("user %s not an admin", authed.User.ID)
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
return
}
if authed.Account.IsMoving() {
apiutil.ForbiddenAfterMove(c)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return
}
// Parse + validate form.
form := new(apimodel.DomainPermissionRequest)
if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
}
if form.Domain == "" {
const errText = "domain must be set"
errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText)
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
var (
permType gtsmodel.DomainPermissionType
errText string
)
switch pt := form.PermissionType; pt {
case "block":
permType = gtsmodel.DomainPermissionBlock
case "allow":
permType = gtsmodel.DomainPermissionAllow
case "":
errText = "permission_type not set, must be one of block or allow"
default:
errText = fmt.Sprintf("permission_type %s not recognized, must be one of block or allow", pt)
}
if errText != "" {
errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText)
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
permDraft, errWithCode := m.processor.Admin().DomainPermissionDraftCreate(
c.Request.Context(),
authed.Account,
form.Domain,
permType,
form.Obfuscate,
form.PublicComment,
form.PrivateComment,
)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
apiutil.JSON(c, http.StatusOK, permDraft)
}

View file

@ -0,0 +1,184 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package admin
import (
"errors"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/paging"
)
// DomainPermissionDraftsGETHandler swagger:operation GET /api/v1/admin/domain_permission_drafts domainPermissionDraftsGet
//
// View domain permission drafts.
//
// The drafts will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer).
//
// The next and previous queries can be parsed from the returned Link header.
//
// Example:
//
// ```
// <https://example.org/api/v1/admin/domain_permission_drafts?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/domain_permission_drafts?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
// ````
//
// ---
// tags:
// - admin
//
// produces:
// - application/json
//
// parameters:
// -
// name: subscription_id
// type: string
// description: Show only drafts created by the given subscription ID.
// in: query
// -
// name: domain
// type: string
// description: Return only drafts that target the given domain.
// in: query
// -
// name: permission_type
// type: string
// description: Filter on "block" or "allow" type drafts.
// in: query
// -
// name: max_id
// type: string
// description: >-
// Return only items *OLDER* than the given max ID (for paging downwards).
// The item with the specified ID will not be included in the response.
// in: query
// -
// name: since_id
// type: string
// description: >-
// Return only items *NEWER* than the given since ID.
// The item with the specified ID will not be included in the response.
// in: query
// -
// name: min_id
// type: string
// description: >-
// Return only items immediately *NEWER* than the given min ID (for paging upwards).
// The item with the specified ID will not be included in the response.
// in: query
// -
// name: limit
// type: integer
// description: Number of items to return.
// default: 20
// minimum: 1
// maximum: 100
// in: query
//
// security:
// - OAuth2 Bearer:
// - admin
//
// responses:
// '200':
// description: Domain permission drafts.
// schema:
// type: array
// items:
// "$ref": "#/definitions/domainPermission"
// headers:
// Link:
// type: string
// description: Links to the next and previous queries.
// '400':
// description: bad request
// '401':
// description: unauthorized
// '403':
// description: forbidden
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) DomainPermissionDraftsGETHandler(c *gin.Context) {
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
return
}
if !*authed.User.Admin {
err := fmt.Errorf("user %s not an admin", authed.User.ID)
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return
}
permType := c.Query(apiutil.DomainPermissionPermTypeKey)
switch permType {
case "", "block", "allow":
// No problem.
default:
// Invalid.
text := fmt.Sprintf(
"permission_type %s not recognized, valid values are empty string, block, or allow",
permType,
)
errWithCode := gtserror.NewErrorBadRequest(errors.New(text), text)
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
page, errWithCode := paging.ParseIDPage(c, 1, 200, 20)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
resp, errWithCode := m.processor.Admin().DomainPermissionDraftsGet(
c.Request.Context(),
c.Query(apiutil.DomainPermissionSubscriptionIDKey),
c.Query(apiutil.DomainPermissionDomainKey),
gtsmodel.NewDomainPermissionType(permType),
page,
)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
if resp.LinkHeader != "" {
c.Header("Link", resp.LinkHeader)
}
apiutil.JSON(c, http.StatusOK, resp.Items)
}

View file

@ -63,28 +63,74 @@ type DomainPermission struct {
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
} }
// DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks).
//
// swagger:model domainPermission
type DomainPermissionSubscription struct {
// The ID of the domain permission subscription.
// example: 01FBW21XJA09XYX51KV5JVBW0F
// readonly: true
ID string `json:"id"`
// The type of domain permission subscription (allow, block).
// example: block
PermissionType string `json:"permission_type"`
// If true, domain permissions arising from this subscription will be created as drafts that must be approved by a moderator to take effect. If false, domain permissions from this subscription will come into force immediately.
// example: true
AsDraft bool `json:"as_draft"`
// ID of the account that created this subscription.
// example: 01FBW21XJA09XYX51KV5JVBW0F
// readonly: true
CreatedByAccountID string `json:"created_by_account_id"`
// MIME content type to expect at URI.
// example: text/csv
ContentType string `json:"content_type"`
// URI to call in order to fetch the permissions list.
// example: https://www.example.org/blocklists/list1.csv
URI string `json:"uri"`
// (Optional) username to set for basic auth when doing a fetch of URI.
// example: admin123
FetchUsername string `json:"fetch_username"`
// (Optional) password to set for basic auth when doing a fetch of URI.
// example: admin123
FetchPassword string `json:"fetch_password"`
// Time at which the most recent fetch was attempted (ISO 8601 Datetime).
// example: 2021-07-30T09:20:25+00:00
// readonly: true
FetchedAt string `json:"fetched_at"`
// If most recent fetch attempt failed, this field will contain an error message related to the fetch attempt.
// example: Oopsie doopsie, we made a fucky wucky.
// readonly: true
Error string `json:"error"`
// Count of domain permission entries discovered at URI.
// example: 53
// readonly: true
Count uint64 `json:"count"`
}
// DomainPermissionRequest is the form submitted as a POST to create a new domain permission entry (allow/block). // DomainPermissionRequest is the form submitted as a POST to create a new domain permission entry (allow/block).
// //
// swagger:ignore // swagger:ignore
type DomainPermissionRequest struct { type DomainPermissionRequest struct {
// A list of domains for which this permission request should apply. // A list of domains for which this permission request should apply.
// Only used if import=true is specified. // Only used if import=true is specified.
Domains *multipart.FileHeader `form:"domains" json:"domains" xml:"domains"` Domains *multipart.FileHeader `form:"domains" json:"domains"`
// A single domain for which this permission request should apply. // A single domain for which this permission request should apply.
// Only used if import=true is NOT specified or if import=false. // Only used if import=true is NOT specified or if import=false.
// example: example.org // example: example.org
Domain string `form:"domain" json:"domain" xml:"domain"` Domain string `form:"domain" json:"domain"`
// Obfuscate the domain name when displaying this permission entry publicly. // Obfuscate the domain name when displaying this permission entry publicly.
// Ie., instead of 'example.org' show something like 'e**mpl*.or*'. // Ie., instead of 'example.org' show something like 'e**mpl*.or*'.
// example: false // example: false
Obfuscate bool `form:"obfuscate" json:"obfuscate" xml:"obfuscate"` Obfuscate bool `form:"obfuscate" json:"obfuscate"`
// Private comment for other admins on why this permission entry was created. // Private comment for other admins on why this permission entry was created.
// example: don't like 'em!!!! // example: don't like 'em!!!!
PrivateComment string `form:"private_comment" json:"private_comment" xml:"private_comment"` PrivateComment string `form:"private_comment" json:"private_comment"`
// Public comment on why this permission entry was created. // Public comment on why this permission entry was created.
// Will be visible to requesters at /api/v1/instance/peers if this endpoint is exposed. // Will be visible to requesters at /api/v1/instance/peers if this endpoint is exposed.
// example: foss dorks 😫 // example: foss dorks 😫
PublicComment string `form:"public_comment" json:"public_comment" xml:"public_comment"` PublicComment string `form:"public_comment" json:"public_comment"`
// Permission type to create (only applies to domain permission drafts, not explicit blocks and allows).
PermissionType string `form:"permission_type" json:"permission_type"`
} }
// DomainKeysExpireRequest is the form submitted as a POST to /api/v1/admin/domain_keys_expire to expire a domain's public keys. // DomainKeysExpireRequest is the form submitted as a POST to /api/v1/admin/domain_keys_expire to expire a domain's public keys.
@ -92,5 +138,5 @@ type DomainPermissionRequest struct {
// swagger:parameters domainKeysExpire // swagger:parameters domainKeysExpire
type DomainKeysExpireRequest struct { type DomainKeysExpireRequest struct {
// hostname/domain to expire keys for. // hostname/domain to expire keys for.
Domain string `form:"domain" json:"domain" xml:"domain"` Domain string `form:"domain" json:"domain"`
} }

View file

@ -71,6 +71,9 @@
DomainPermissionExportKey = "export" DomainPermissionExportKey = "export"
DomainPermissionImportKey = "import" DomainPermissionImportKey = "import"
DomainPermissionSubscriptionIDKey = "subscription_id"
DomainPermissionPermTypeKey = "permission_type"
DomainPermissionDomainKey = "domain"
/* Admin query keys */ /* Admin query keys */

View file

@ -74,6 +74,9 @@ func (c *Caches) Init() {
c.initConversationLastStatusIDs() c.initConversationLastStatusIDs()
c.initDomainAllow() c.initDomainAllow()
c.initDomainBlock() c.initDomainBlock()
c.initDomainPermissionDraft()
c.initDomainPermissionSubscription()
c.initDomainPermissionIgnore()
c.initEmoji() c.initEmoji()
c.initEmojiCategory() c.initEmojiCategory()
c.initFilter() c.initFilter()

76
internal/cache/db.go vendored
View file

@ -67,6 +67,15 @@ type DBCaches struct {
// DomainBlock provides access to the domain block database cache. // DomainBlock provides access to the domain block database cache.
DomainBlock *domain.Cache DomainBlock *domain.Cache
// DomainPermissionDraft provides access to the domain permission draft database cache.
DomainPermissionDraft StructCache[*gtsmodel.DomainPermissionDraft]
// DomainPermissionSubscription provides access to the domain permission subscription database cache.
DomainPermissionSubscription StructCache[*gtsmodel.DomainPermissionSubscription]
// DomainPermissionIgnore provides access to the domain permission ignore database cache.
DomainPermissionIgnore *domain.Cache
// Emoji provides access to the gtsmodel Emoji database cache. // Emoji provides access to the gtsmodel Emoji database cache.
Emoji StructCache[*gtsmodel.Emoji] Emoji StructCache[*gtsmodel.Emoji]
@ -548,6 +557,73 @@ func (c *Caches) initDomainBlock() {
c.DB.DomainBlock = new(domain.Cache) c.DB.DomainBlock = new(domain.Cache)
} }
func (c *Caches) initDomainPermissionDraft() {
// Calculate maximum cache size.
cap := calculateResultCacheMax(
sizeofDomainPermissionDraft(), // model in-mem size.
config.GetCacheDomainPermissionDraftMemRation(),
)
log.Infof(nil, "cache size = %d", cap)
copyF := func(d1 *gtsmodel.DomainPermissionDraft) *gtsmodel.DomainPermissionDraft {
d2 := new(gtsmodel.DomainPermissionDraft)
*d2 = *d1
// Don't include ptr fields that
// will be populated separately.
d2.CreatedByAccount = nil
return d2
}
c.DB.DomainPermissionDraft.Init(structr.CacheConfig[*gtsmodel.DomainPermissionDraft]{
Indices: []structr.IndexConfig{
{Fields: "ID"},
{Fields: "Domain", Multiple: true},
{Fields: "SubscriptionID", Multiple: true},
},
MaxSize: cap,
IgnoreErr: ignoreErrors,
Copy: copyF,
})
}
func (c *Caches) initDomainPermissionSubscription() {
// Calculate maximum cache size.
cap := calculateResultCacheMax(
sizeofDomainPermissionSubscription(), // model in-mem size.
config.GetCacheDomainPermissionSubscriptionMemRation(),
)
log.Infof(nil, "cache size = %d", cap)
copyF := func(d1 *gtsmodel.DomainPermissionSubscription) *gtsmodel.DomainPermissionSubscription {
d2 := new(gtsmodel.DomainPermissionSubscription)
*d2 = *d1
// Don't include ptr fields that
// will be populated separately.
d2.CreatedByAccount = nil
return d2
}
c.DB.DomainPermissionSubscription.Init(structr.CacheConfig[*gtsmodel.DomainPermissionSubscription]{
Indices: []structr.IndexConfig{
{Fields: "ID"},
{Fields: "URI"},
},
MaxSize: cap,
IgnoreErr: ignoreErrors,
Copy: copyF,
})
}
func (c *Caches) initDomainPermissionIgnore() {
c.DB.DomainPermissionIgnore = new(domain.Cache)
}
func (c *Caches) initEmoji() { func (c *Caches) initEmoji() {
// Calculate maximum cache size. // Calculate maximum cache size.
cap := calculateResultCacheMax( cap := calculateResultCacheMax(

View file

@ -342,6 +342,35 @@ func sizeofConversation() uintptr {
})) }))
} }
func sizeofDomainPermissionDraft() uintptr {
return uintptr(size.Of(&gtsmodel.DomainPermissionDraft{
ID: exampleID,
CreatedAt: exampleTime,
UpdatedAt: exampleTime,
PermissionType: gtsmodel.DomainPermissionBlock,
Domain: "example.org",
CreatedByAccountID: exampleID,
PrivateComment: exampleTextSmall,
PublicComment: exampleTextSmall,
Obfuscate: util.Ptr(false),
SubscriptionID: exampleID,
}))
}
func sizeofDomainPermissionSubscription() uintptr {
return uintptr(size.Of(&gtsmodel.DomainPermissionSubscription{
ID: exampleID,
CreatedAt: exampleTime,
PermissionType: gtsmodel.DomainPermissionBlock,
CreatedByAccountID: exampleID,
URI: exampleURI,
FetchUsername: "username",
FetchPassword: "password",
FetchedAt: exampleTime,
AsDraft: util.Ptr(true),
}))
}
func sizeofEmoji() uintptr { func sizeofEmoji() uintptr {
return uintptr(size.Of(&gtsmodel.Emoji{ return uintptr(size.Of(&gtsmodel.Emoji{
ID: exampleID, ID: exampleID,

View file

@ -206,6 +206,8 @@ type CacheConfiguration struct {
ClientMemRatio float64 `name:"client-mem-ratio"` ClientMemRatio float64 `name:"client-mem-ratio"`
ConversationMemRatio float64 `name:"conversation-mem-ratio"` ConversationMemRatio float64 `name:"conversation-mem-ratio"`
ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"` ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"`
DomainPermissionDraftMemRation float64 `name:"domain-permission-draft-mem-ratio"`
DomainPermissionSubscriptionMemRation float64 `name:"domain-permission-subscription-mem-ratio"`
EmojiMemRatio float64 `name:"emoji-mem-ratio"` EmojiMemRatio float64 `name:"emoji-mem-ratio"`
EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"` EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
FilterMemRatio float64 `name:"filter-mem-ratio"` FilterMemRatio float64 `name:"filter-mem-ratio"`

View file

@ -169,6 +169,8 @@
ClientMemRatio: 0.1, ClientMemRatio: 0.1,
ConversationMemRatio: 1, ConversationMemRatio: 1,
ConversationLastStatusIDsMemRatio: 2, ConversationLastStatusIDsMemRatio: 2,
DomainPermissionDraftMemRation: 0.5,
DomainPermissionSubscriptionMemRation: 0.5,
EmojiMemRatio: 3, EmojiMemRatio: 3,
EmojiCategoryMemRatio: 0.1, EmojiCategoryMemRatio: 0.1,
FilterMemRatio: 0.5, FilterMemRatio: 0.5,

View file

@ -3106,6 +3106,68 @@ func SetCacheConversationLastStatusIDsMemRatio(v float64) {
global.SetCacheConversationLastStatusIDsMemRatio(v) global.SetCacheConversationLastStatusIDsMemRatio(v)
} }
// GetCacheDomainPermissionDraftMemRation safely fetches the Configuration value for state's 'Cache.DomainPermissionDraftMemRation' field
func (st *ConfigState) GetCacheDomainPermissionDraftMemRation() (v float64) {
st.mutex.RLock()
v = st.config.Cache.DomainPermissionDraftMemRation
st.mutex.RUnlock()
return
}
// SetCacheDomainPermissionDraftMemRation safely sets the Configuration value for state's 'Cache.DomainPermissionDraftMemRation' field
func (st *ConfigState) SetCacheDomainPermissionDraftMemRation(v float64) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.Cache.DomainPermissionDraftMemRation = v
st.reloadToViper()
}
// CacheDomainPermissionDraftMemRationFlag returns the flag name for the 'Cache.DomainPermissionDraftMemRation' field
func CacheDomainPermissionDraftMemRationFlag() string {
return "cache-domain-permission-draft-mem-ratio"
}
// GetCacheDomainPermissionDraftMemRation safely fetches the value for global configuration 'Cache.DomainPermissionDraftMemRation' field
func GetCacheDomainPermissionDraftMemRation() float64 {
return global.GetCacheDomainPermissionDraftMemRation()
}
// SetCacheDomainPermissionDraftMemRation safely sets the value for global configuration 'Cache.DomainPermissionDraftMemRation' field
func SetCacheDomainPermissionDraftMemRation(v float64) {
global.SetCacheDomainPermissionDraftMemRation(v)
}
// GetCacheDomainPermissionSubscriptionMemRation safely fetches the Configuration value for state's 'Cache.DomainPermissionSubscriptionMemRation' field
func (st *ConfigState) GetCacheDomainPermissionSubscriptionMemRation() (v float64) {
st.mutex.RLock()
v = st.config.Cache.DomainPermissionSubscriptionMemRation
st.mutex.RUnlock()
return
}
// SetCacheDomainPermissionSubscriptionMemRation safely sets the Configuration value for state's 'Cache.DomainPermissionSubscriptionMemRation' field
func (st *ConfigState) SetCacheDomainPermissionSubscriptionMemRation(v float64) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.Cache.DomainPermissionSubscriptionMemRation = v
st.reloadToViper()
}
// CacheDomainPermissionSubscriptionMemRationFlag returns the flag name for the 'Cache.DomainPermissionSubscriptionMemRation' field
func CacheDomainPermissionSubscriptionMemRationFlag() string {
return "cache-domain-permission-subscription-mem-ratio"
}
// GetCacheDomainPermissionSubscriptionMemRation safely fetches the value for global configuration 'Cache.DomainPermissionSubscriptionMemRation' field
func GetCacheDomainPermissionSubscriptionMemRation() float64 {
return global.GetCacheDomainPermissionSubscriptionMemRation()
}
// SetCacheDomainPermissionSubscriptionMemRation safely sets the value for global configuration 'Cache.DomainPermissionSubscriptionMemRation' field
func SetCacheDomainPermissionSubscriptionMemRation(v float64) {
global.SetCacheDomainPermissionSubscriptionMemRation(v)
}
// GetCacheEmojiMemRatio safely fetches the Configuration value for state's 'Cache.EmojiMemRatio' field // GetCacheEmojiMemRatio safely fetches the Configuration value for state's 'Cache.EmojiMemRatio' field
func (st *ConfigState) GetCacheEmojiMemRatio() (v float64) { func (st *ConfigState) GetCacheEmojiMemRatio() (v float64) {
st.mutex.RLock() st.mutex.RLock()

View file

@ -19,12 +19,18 @@
import ( import (
"context" "context"
"errors"
"net/url" "net/url"
"slices"
"github.com/miekg/dns"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/paging"
"github.com/superseriousbusiness/gotosocial/internal/state" "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/uptrace/bun" "github.com/uptrace/bun"
@ -328,3 +334,701 @@ func (d *domainDB) AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, e
} }
return false, nil return false, nil
} }
func (d *domainDB) getDomainPermissionDraft(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.DomainPermissionDraft) error,
keyParts ...any,
) (*gtsmodel.DomainPermissionDraft, error) {
// Fetch perm draft from database cache with loader callback.
permDraft, err := d.state.Caches.DB.DomainPermissionDraft.LoadOne(
lookup,
// Only called if not cached.
func() (*gtsmodel.DomainPermissionDraft, error) {
var permDraft gtsmodel.DomainPermissionDraft
if err := dbQuery(&permDraft); err != nil {
return nil, err
}
return &permDraft, nil
},
keyParts...,
)
if err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return permDraft, nil
}
if permDraft.CreatedByAccount == nil {
// Not set, fetch from database.
permDraft.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
permDraft.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return permDraft, nil
}
func (d *domainDB) GetDomainPermissionDraftByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionDraft, error) {
return d.getDomainPermissionDraft(
ctx,
"ID",
func(permDraft *gtsmodel.DomainPermissionDraft) error {
return d.db.
NewSelect().
Model(permDraft).
Where("? = ?", bun.Ident("domain_permission_draft.id"), id).
Scan(ctx)
},
id,
)
}
func (d *domainDB) GetDomainPermissionDrafts(
ctx context.Context,
permType gtsmodel.DomainPermissionType,
permSubID string,
domain string,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionDraft,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
permDraftIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_drafts"),
bun.Ident("domain_permission_draft"),
).
// Select only IDs from table
Column("domain_permission_draft.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_draft.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_draft.id"),
minID,
)
}
// Return only items with
// given permission type.
if permType != gtsmodel.DomainPermissionUnknown {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.permission_type"),
permType,
)
}
// Return only items with
// given subscription ID.
if permSubID != "" {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.subscription_id"),
permSubID,
)
}
// Return only items
// with given domain.
if domain != "" {
var err error
// Normalize domain as punycode.
domain, err = util.Punify(domain)
if err != nil {
return nil, gtserror.Newf("error punifying domain %s: %w", domain, err)
}
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.domain"),
domain,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_draft.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_draft.id"),
)
}
if err := q.Scan(ctx, &permDraftIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(permDraftIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(permDraftIDs)
}
// Allocate return slice (will be at most len permDraftIDs)
permDrafts := make([]*gtsmodel.DomainPermissionDraft, 0, len(permDraftIDs))
for _, id := range permDraftIDs {
permDraft, err := d.GetDomainPermissionDraftByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission draft %q: %v", id, err)
continue
}
// Append to return slice
permDrafts = append(permDrafts, permDraft)
}
return permDrafts, nil
}
func (d *domainDB) PutDomainPermissionDraft(
ctx context.Context,
permDraft *gtsmodel.DomainPermissionDraft,
) error {
var err error
// Normalize the domain as punycode
permDraft.Domain, err = util.Punify(permDraft.Domain)
if err != nil {
return gtserror.Newf("error punifying domain %s: %w", permDraft.Domain, err)
}
return d.state.Caches.DB.DomainPermissionDraft.Store(
permDraft,
func() error {
_, err := d.db.
NewInsert().
Model(permDraft).
Exec(ctx)
return err
},
)
}
func (d *domainDB) DeleteDomainPermissionDraft(
ctx context.Context,
id string,
) error {
// Delete the permDraft from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_drafts"),
bun.Ident("domain_permission_draft"),
).
Where(
"? = ?",
bun.Ident("domain_permission_draft.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Invalidate any cached model by ID.
d.state.Caches.DB.DomainPermissionDraft.Invalidate("ID", id)
return nil
}
func (d *domainDB) getDomainPermissionSubscription(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.DomainPermissionSubscription) error,
keyParts ...any,
) (*gtsmodel.DomainPermissionSubscription, error) {
// Fetch perm subscription from database cache with loader callback.
permSub, err := d.state.Caches.DB.DomainPermissionSubscription.LoadOne(
lookup,
// Only called if not cached.
func() (*gtsmodel.DomainPermissionSubscription, error) {
var permSub gtsmodel.DomainPermissionSubscription
if err := dbQuery(&permSub); err != nil {
return nil, err
}
return &permSub, nil
},
keyParts...,
)
if err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return permSub, nil
}
if permSub.CreatedByAccount == nil {
// Not set, fetch from database.
permSub.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
permSub.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return permSub, nil
}
func (d *domainDB) GetDomainPermissionSubscriptionByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionSubscription, error) {
return d.getDomainPermissionSubscription(
ctx,
"ID",
func(permSub *gtsmodel.DomainPermissionSubscription) error {
return d.db.
NewSelect().
Model(permSub).
Where("? = ?", bun.Ident("domain_permission_subscription.id"), id).
Scan(ctx)
},
id,
)
}
func (d *domainDB) GetDomainPermissionSubscriptions(
ctx context.Context,
permType *gtsmodel.DomainPermissionType,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionSubscription,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
permSubIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_subscriptions"),
bun.Ident("domain_permission_subscription"),
).
// Select only IDs from table
Column("domain_permission_subscription.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_subscription.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_subscription.id"),
minID,
)
}
// Return only items with
// given subscription ID.
if permType != nil {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_subscription.permission_type"),
*permType,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_subscription.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_subscription.id"),
)
}
if err := q.Scan(ctx, &permSubIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(permSubIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(permSubIDs)
}
// Allocate return slice (will be at most len permSubIDs).
permSubs := make([]*gtsmodel.DomainPermissionSubscription, 0, len(permSubIDs))
for _, id := range permSubIDs {
permSub, err := d.GetDomainPermissionSubscriptionByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission subscription %q: %v", id, err)
continue
}
// Append to return slice
permSubs = append(permSubs, permSub)
}
return permSubs, nil
}
func (d *domainDB) PutDomainPermissionSubscription(
ctx context.Context,
permSubscription *gtsmodel.DomainPermissionSubscription,
) error {
return d.state.Caches.DB.DomainPermissionSubscription.Store(
permSubscription,
func() error {
_, err := d.db.
NewInsert().
Model(permSubscription).
Exec(ctx)
return err
},
)
}
func (d *domainDB) DeleteDomainPermissionSubscription(
ctx context.Context,
id string,
) error {
// Delete the permSub from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_subscriptions"),
bun.Ident("domain_permission_subscription"),
).
Where(
"? = ?",
bun.Ident("domain_permission_subscription.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Invalidate any cached model by ID.
d.state.Caches.DB.DomainPermissionSubscription.Invalidate("ID", id)
return nil
}
func (d *domainDB) PutDomainPermissionIgnore(
ctx context.Context,
ignore *gtsmodel.DomainPermissionIgnore,
) error {
// Normalize the domain as punycode
var err error
ignore.Domain, err = util.Punify(ignore.Domain)
if err != nil {
return err
}
// Attempt to store domain perm ignore in DB
if _, err := d.db.NewInsert().
Model(ignore).
Exec(ctx); err != nil {
return err
}
// Clear the domain perm ignore cache (for later reload)
d.state.Caches.DB.DomainPermissionIgnore.Clear()
return nil
}
func (d *domainDB) IsDomainPermissionIgnored(ctx context.Context, domain string) (bool, error) {
// Normalize the domain as punycode
domain, err := util.Punify(domain)
if err != nil {
return false, err
}
// Check if our host and given domain are equal
// or part of the same second-level domain; we
// always ignore such perms as creating blocks
// or allows in such cases may break things.
if dns.CompareDomainName(domain, config.GetHost()) >= 2 {
return true, nil
}
// Func to scan list of all
// ignored domain perms from DB.
loadF := func() ([]string, error) {
var domains []string
if err := d.db.
NewSelect().
Table("domain_ignores").
Column("domain").
Scan(ctx, &domains); err != nil {
return nil, err
}
return domains, nil
}
// Check the cache for a domain perm ignore,
// hydrating the cache with loadF if necessary.
return d.state.Caches.DB.DomainPermissionIgnore.Matches(domain, loadF)
}
func (d *domainDB) GetDomainPermissionIgnoreByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionIgnore, error) {
ignore := new(gtsmodel.DomainPermissionIgnore)
q := d.db.
NewSelect().
Model(ignore).
Where("? = ?", bun.Ident("domain_permission_ignore.id"), id)
if err := q.Scan(ctx); err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return ignore, nil
}
if ignore.CreatedByAccount == nil {
// Not set, fetch from database.
var err error
ignore.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
ignore.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return ignore, nil
}
func (d *domainDB) GetDomainPermissionIgnores(
ctx context.Context,
permType *gtsmodel.DomainPermissionType,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionIgnore,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
ignoreIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_ignores"),
bun.Ident("domain_permission_ignore"),
).
// Select only IDs from table
Column("domain_permission_ignore.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_ignore.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_ignore.id"),
minID,
)
}
// Return only items with
// given subscription ID.
if permType != nil {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_ignore.permission_type"),
*permType,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_ignore.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_ignore.id"),
)
}
if err := q.Scan(ctx, &ignoreIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(ignoreIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(ignoreIDs)
}
// Allocate return slice (will be at most len permSubIDs).
ignores := make([]*gtsmodel.DomainPermissionIgnore, 0, len(ignoreIDs))
for _, id := range ignoreIDs {
ignore, err := d.GetDomainPermissionIgnoreByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission ignore %q: %v", id, err)
continue
}
// Append to return slice
ignores = append(ignores, ignore)
}
return ignores, nil
}
func (d *domainDB) DeleteDomainPermissionIgnore(
ctx context.Context,
id string,
) error {
// Delete the permSub from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_ignores"),
bun.Ident("domain_permission_ignore"),
).
Where(
"? = ?",
bun.Ident("domain_permission_ignore.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Clear the domain perm ignore cache (for later reload)
d.state.Caches.DB.DomainPermissionIgnore.Clear()
return nil
}

View file

@ -0,0 +1,94 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package migrations
import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
)
func init() {
up := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
// Create `domain_permission_drafts`.
if _, err := tx.
NewCreateTable().
Model((*gtsmodel.DomainPermissionDraft)(nil)).
IfNotExists().
Exec(ctx); err != nil {
return err
}
// Create `domain_permission_subscriptions`.
if _, err := tx.
NewCreateTable().
Model((*gtsmodel.DomainPermissionSubscription)(nil)).
IfNotExists().
Exec(ctx); err != nil {
return err
}
// Create `domain_permission_ignores`.
if _, err := tx.
NewCreateTable().
Model((*gtsmodel.DomainPermissionIgnore)(nil)).
IfNotExists().
Exec(ctx); err != nil {
return err
}
// Create indexes. Indices. Indie sexes.
for table, indexes := range map[string]map[string][]string{
"domain_permission_drafts": {
"domain_permission_drafts_domain_idx": {"domain"},
"domain_permission_drafts_subscription_id_idx": {"subscription_id"},
},
"domain_permission_subscriptions": {
"domain_permission_subscriptions_permission_type_idx": {"permission_type"},
},
} {
for index, columns := range indexes {
if _, err := tx.
NewCreateIndex().
Table(table).
Index(index).
Column(columns...).
IfNotExists().
Exec(ctx); err != nil {
return err
}
}
}
return nil
})
}
down := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
return nil
})
}
if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}

View file

@ -22,6 +22,7 @@
"net/url" "net/url"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/paging"
) )
// Domain contains DB functions related to domains and domain blocks. // Domain contains DB functions related to domains and domain blocks.
@ -78,4 +79,48 @@ type Domain interface {
// AreURIsBlocked calls IsURIBlocked for each URI. // AreURIsBlocked calls IsURIBlocked for each URI.
// Will return true if even one of the given URIs is blocked. // Will return true if even one of the given URIs is blocked.
AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, error) AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, error)
/*
Domain permission draft stuff.
*/
// GetDomainPermissionDraftByID gets one DomainPermissionDraft with the given ID.
GetDomainPermissionDraftByID(ctx context.Context, id string) (*gtsmodel.DomainPermissionDraft, error)
// GetDomainPermissionDrafts returns a page of
// DomainPermissionDrafts using the given parameters.
GetDomainPermissionDrafts(
ctx context.Context,
permType gtsmodel.DomainPermissionType,
permSubID string,
domain string,
page *paging.Page,
) ([]*gtsmodel.DomainPermissionDraft, error)
// PutDomainPermissionDraft stores one DomainPermissionDraft.
PutDomainPermissionDraft(ctx context.Context, permDraft *gtsmodel.DomainPermissionDraft) error
// DeleteDomainPermissionDraft deletes one DomainPermissionDraft with the given id.
DeleteDomainPermissionDraft(ctx context.Context, id string) error
/*
Domain permission subscription stuff.
*/
// GetDomainPermissionSubscriptionByID gets one DomainPermissionSubscription with the given ID.
GetDomainPermissionSubscriptionByID(ctx context.Context, id string) (*gtsmodel.DomainPermissionSubscription, error)
// GetDomainPermissionSubscriptions returns a page of
// DomainPermissionSubscriptions using the given parameters.
GetDomainPermissionSubscriptions(
ctx context.Context,
permType *gtsmodel.DomainPermissionType,
page *paging.Page,
) ([]*gtsmodel.DomainPermissionSubscription, error)
// PutDomainPermissionSubscription stores one DomainPermissionSubscription.
PutDomainPermissionSubscription(ctx context.Context, permSub *gtsmodel.DomainPermissionSubscription) error
// DeleteDomainPermissionSubscription deletes one DomainPermissionSubscription with the given id.
DeleteDomainPermissionSubscription(ctx context.Context, id string) error
} }

View file

@ -0,0 +1,78 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gtsmodel
import "time"
type DomainPermissionDraft struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created.
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was last updated.
PermissionType DomainPermissionType `bun:",notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // Permission type of the draft.
Domain string `bun:",nullzero,notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // Domain to block or allow. Eg. 'whatever.com'.
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription.
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
PrivateComment string `bun:",nullzero"` // Private comment on this perm, viewable to admins.
PublicComment string `bun:",nullzero"` // Public comment on this perm, viewable (optionally) by everyone.
Obfuscate *bool `bun:",nullzero,notnull,default:false"` // Obfuscate domain name when displaying it publicly.
SubscriptionID string `bun:"type:CHAR(26),nullzero,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // ID of the subscription that created this draft, if any.
}
func (d *DomainPermissionDraft) GetID() string {
return d.ID
}
func (d *DomainPermissionDraft) GetCreatedAt() time.Time {
return d.CreatedAt
}
func (d *DomainPermissionDraft) GetUpdatedAt() time.Time {
return d.UpdatedAt
}
func (d *DomainPermissionDraft) GetDomain() string {
return d.Domain
}
func (d *DomainPermissionDraft) GetCreatedByAccountID() string {
return d.CreatedByAccountID
}
func (d *DomainPermissionDraft) GetCreatedByAccount() *Account {
return d.CreatedByAccount
}
func (d *DomainPermissionDraft) GetPrivateComment() string {
return d.PrivateComment
}
func (d *DomainPermissionDraft) GetPublicComment() string {
return d.PublicComment
}
func (d *DomainPermissionDraft) GetObfuscate() *bool {
return d.Obfuscate
}
func (d *DomainPermissionDraft) GetSubscriptionID() string {
return d.SubscriptionID
}
func (d *DomainPermissionDraft) GetType() DomainPermissionType {
return DomainPermissionBlock
}

View file

@ -0,0 +1,32 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gtsmodel
import "time"
// DomainPermissionIgnore represents one domain that should be ignored
// when domain permission (ignores) are created from subscriptions.
type DomainPermissionIgnore struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created.
PermissionType DomainPermissionType `bun:",notnull,unique:domain_permission_ignores_permission_type_domain_uniq"` // Permission type of the ignore.
Domain string `bun:",nullzero,notnull,unique:domain_permission_ignores_permission_type_domain_uniq"` // Domain to ignore. Eg. 'whatever.com'.
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this ignore.
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
PrivateComment string `bun:",nullzero"` // Private comment on this ignore, viewable to admins.
}

View file

@ -0,0 +1,38 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gtsmodel
import "time"
type DomainPermissionSubscription struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created.
Title string `bun:",nullzero"` // Moderator-set title for this list.
PermissionType DomainPermissionType `bun:",notnull"` // Permission type of the subscription.
AsDraft *bool `bun:",nullzero,notnull,default:true"` // Create domain permission entries resulting from this subscription as drafts.
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription.
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
ContentType string `bun:",nullzero,notnull"` // Content type to expect from the URI.
URI string `bun:",unique,nullzero,notnull"` // URI of the domain permission list.
FetchUsername string `bun:",nullzero"` // Username to send when doing a GET of URI using basic auth.
FetchPassword string `bun:",nullzero"` // Password to send when doing a GET of URI using basic auth.
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // Time when fetch of URI was last attempted.
IsError *bool `bun:",nullzero,notnull,default:false"` // True if last fetch attempt of URI resulted in an error.
Error string `bun:",nullzero"` // If IsError=true, this field contains the error resulting from the attempted fetch.
Count uint64 `bun:""` // Count of domain permission entries discovered at URI.
}

View file

@ -392,18 +392,31 @@ func (res *result) GetFileType() (gtsmodel.FileType, string) {
case "matroska,webm": case "matroska,webm":
switch { switch {
case len(res.video) > 0: case len(res.video) > 0:
var isWebm bool
switch res.video[0].codec { switch res.video[0].codec {
case "vp8", "vp9", "av1": case "vp8", "vp9", "av1":
default:
return gtsmodel.FileTypeVideo, "mkv"
}
if len(res.audio) > 0 { if len(res.audio) > 0 {
switch res.audio[0].codec { switch res.audio[0].codec {
case "vorbis", "opus", "libopus": case "vorbis", "opus", "libopus":
// webm only supports [VP8/VP9/AV1]+[vorbis/opus] // webm only supports [VP8/VP9/AV1] +
// [vorbis/opus]
isWebm = true
}
} else {
// no audio with correct
// video codec also fine.
isWebm = true
}
}
if isWebm {
// Check for valid webm codec config.
return gtsmodel.FileTypeVideo, "webm" return gtsmodel.FileTypeVideo, "webm"
} }
}
// All else falls under generic mkv.
return gtsmodel.FileTypeVideo, "mkv"
case len(res.audio) > 0: case len(res.audio) > 0:
return gtsmodel.FileTypeAudio, "mka" return gtsmodel.FileTypeAudio, "mka"
} }

View file

@ -27,13 +27,8 @@
ffprobelib "codeberg.org/gruf/go-ffmpreg/embed/ffprobe" ffprobelib "codeberg.org/gruf/go-ffmpreg/embed/ffprobe"
"codeberg.org/gruf/go-ffmpreg/wasm" "codeberg.org/gruf/go-ffmpreg/wasm"
"github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
) )
// Use all core features required by ffmpeg / ffprobe
// (these should be the same but we OR just in case).
const corefeatures = wasm.CoreFeatures
var ( var (
// shared WASM runtime instance. // shared WASM runtime instance.
runtime wazero.Runtime runtime wazero.Runtime
@ -91,38 +86,26 @@ func compileFfprobe(ctx context.Context) error {
// initRuntime initializes the global wazero.Runtime, // initRuntime initializes the global wazero.Runtime,
// if already initialized this function is a no-op. // if already initialized this function is a no-op.
func initRuntime(ctx context.Context) error { func initRuntime(ctx context.Context) (err error) {
if runtime != nil { if runtime != nil {
return nil return nil
} }
var cache wazero.CompilationCache // Create new runtime config.
cfg := wazero.NewRuntimeConfig()
if dir := os.Getenv("GTS_WAZERO_COMPILATION_CACHE"); dir != "" { if dir := os.Getenv("GTS_WAZERO_COMPILATION_CACHE"); dir != "" {
var err error
// Use on-filesystem compilation cache given by env. // Use on-filesystem compilation cache given by env.
cache, err = wazero.NewCompilationCacheWithDir(dir) cache, err := wazero.NewCompilationCacheWithDir(dir)
if err != nil { if err != nil {
return err return err
} }
}
// Prepare config with cache. // Update runtime config with cache.
cfg := wazero.NewRuntimeConfig()
cfg = cfg.WithCoreFeatures(corefeatures)
cfg = cfg.WithCompilationCache(cache) cfg = cfg.WithCompilationCache(cache)
// Instantiate runtime with prepared config.
rt := wazero.NewRuntimeWithConfig(ctx, cfg)
// Instantiate wasi snapshot preview features into runtime.
_, err := wasi_snapshot_preview1.Instantiate(ctx, rt)
if err != nil {
return err
} }
// Set runtime. // Initialize new runtime from config.
runtime = rt runtime, err = wasm.NewRuntime(ctx, cfg)
return nil return
} }

View file

@ -158,7 +158,7 @@ func (p *ProcessingEmoji) store(ctx context.Context) error {
if err != nil && !isUnsupportedTypeErr(err) { if err != nil && !isUnsupportedTypeErr(err) {
return gtserror.Newf("ffprobe error: %w", err) return gtserror.Newf("ffprobe error: %w", err)
} else if result == nil { } else if result == nil {
log.Warn(ctx, "unsupported data type") log.Warnf(ctx, "unsupported data type by ffprobe: %v", err)
return nil return nil
} }

View file

@ -22,6 +22,7 @@
"os" "os"
errorsv2 "codeberg.org/gruf/go-errors/v2" errorsv2 "codeberg.org/gruf/go-errors/v2"
"codeberg.org/gruf/go-kv"
"codeberg.org/gruf/go-runners" "codeberg.org/gruf/go-runners"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
@ -166,7 +167,7 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
if err != nil && !isUnsupportedTypeErr(err) { if err != nil && !isUnsupportedTypeErr(err) {
return gtserror.Newf("ffprobe error: %w", err) return gtserror.Newf("ffprobe error: %w", err)
} else if result == nil { } else if result == nil {
log.Warn(ctx, "unsupported data type") log.Warnf(ctx, "unsupported data type by ffprobe: %v", err)
return nil return nil
} }
@ -214,7 +215,10 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
// metadata, in order to keep tags. // metadata, in order to keep tags.
default: default:
log.Warn(ctx, "unsupported data type: %s", result.format) log.WarnKVs(ctx, kv.Fields{
{K: "format", V: result.format},
{K: "msg", V: "unsupported data type"},
}...)
return nil return nil
} }

View file

@ -31,24 +31,6 @@
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
) )
// apiDomainPerm is a cheeky shortcut for returning
// the API version of the given domain permission
// (*gtsmodel.DomainBlock or *gtsmodel.DomainAllow),
// or an appropriate error if something goes wrong.
func (p *Processor) apiDomainPerm(
ctx context.Context,
domainPermission gtsmodel.DomainPermission,
export bool,
) (*apimodel.DomainPermission, gtserror.WithCode) {
apiDomainPerm, err := p.converter.DomainPermToAPIDomainPerm(ctx, domainPermission, export)
if err != nil {
err := gtserror.NewfAt(3, "error converting domain permission to api model: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return apiDomainPerm, nil
}
// DomainPermissionCreate creates an instance-level permission // DomainPermissionCreate creates an instance-level permission
// targeting the given domain, and then processes any side // targeting the given domain, and then processes any side
// effects of the permission creation. // effects of the permission creation.
@ -61,7 +43,7 @@ func (p *Processor) apiDomainPerm(
// from this call, and/or an error if something goes wrong. // from this call, and/or an error if something goes wrong.
func (p *Processor) DomainPermissionCreate( func (p *Processor) DomainPermissionCreate(
ctx context.Context, ctx context.Context,
permissionType gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
adminAcct *gtsmodel.Account, adminAcct *gtsmodel.Account,
domain string, domain string,
obfuscate bool, obfuscate bool,
@ -69,7 +51,7 @@ func (p *Processor) DomainPermissionCreate(
privateComment string, privateComment string,
subscriptionID string, subscriptionID string,
) (*apimodel.DomainPermission, string, gtserror.WithCode) { ) (*apimodel.DomainPermission, string, gtserror.WithCode) {
switch permissionType { switch permType {
// Explicitly block a domain. // Explicitly block a domain.
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
@ -97,7 +79,7 @@ func (p *Processor) DomainPermissionCreate(
// Weeping, roaring, red-faced. // Weeping, roaring, red-faced.
default: default:
err := gtserror.Newf("unrecognized permission type %d", permissionType) err := gtserror.Newf("unrecognized permission type %d", permType)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
} }
@ -109,11 +91,11 @@ func (p *Processor) DomainPermissionCreate(
// action resulting from this call, and/or an error if something goes wrong. // action resulting from this call, and/or an error if something goes wrong.
func (p *Processor) DomainPermissionDelete( func (p *Processor) DomainPermissionDelete(
ctx context.Context, ctx context.Context,
permissionType gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
adminAcct *gtsmodel.Account, adminAcct *gtsmodel.Account,
domainBlockID string, domainBlockID string,
) (*apimodel.DomainPermission, string, gtserror.WithCode) { ) (*apimodel.DomainPermission, string, gtserror.WithCode) {
switch permissionType { switch permType {
// Delete explicit domain block. // Delete explicit domain block.
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
@ -134,7 +116,7 @@ func (p *Processor) DomainPermissionDelete(
// You do the hokey-cokey and you turn // You do the hokey-cokey and you turn
// around, that's what it's all about. // around, that's what it's all about.
default: default:
err := gtserror.Newf("unrecognized permission type %d", permissionType) err := gtserror.Newf("unrecognized permission type %d", permType)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
} }
@ -152,14 +134,14 @@ func (p *Processor) DomainPermissionDelete(
// as they wish. // as they wish.
func (p *Processor) DomainPermissionsImport( func (p *Processor) DomainPermissionsImport(
ctx context.Context, ctx context.Context,
permissionType gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
account *gtsmodel.Account, account *gtsmodel.Account,
domainsF *multipart.FileHeader, domainsF *multipart.FileHeader,
) (*apimodel.MultiStatus, gtserror.WithCode) { ) (*apimodel.MultiStatus, gtserror.WithCode) {
// Ensure known permission type. // Ensure known permission type.
if permissionType != gtsmodel.DomainPermissionBlock && if permType != gtsmodel.DomainPermissionBlock &&
permissionType != gtsmodel.DomainPermissionAllow { permType != gtsmodel.DomainPermissionAllow {
err := gtserror.Newf("unrecognized permission type %d", permissionType) err := gtserror.Newf("unrecognized permission type %d", permType)
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }
@ -201,7 +183,7 @@ func (p *Processor) DomainPermissionsImport(
domainPerm, _, errWithCode = p.DomainPermissionCreate( domainPerm, _, errWithCode = p.DomainPermissionCreate(
ctx, ctx,
permissionType, permType,
account, account,
domain, domain,
obfuscate, obfuscate,
@ -240,7 +222,7 @@ func (p *Processor) DomainPermissionsImport(
// to an export. // to an export.
func (p *Processor) DomainPermissionsGet( func (p *Processor) DomainPermissionsGet(
ctx context.Context, ctx context.Context,
permissionType gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
account *gtsmodel.Account, account *gtsmodel.Account,
export bool, export bool,
) ([]*apimodel.DomainPermission, gtserror.WithCode) { ) ([]*apimodel.DomainPermission, gtserror.WithCode) {
@ -249,7 +231,7 @@ func (p *Processor) DomainPermissionsGet(
err error err error
) )
switch permissionType { switch permType {
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
var blocks []*gtsmodel.DomainBlock var blocks []*gtsmodel.DomainBlock
@ -279,7 +261,7 @@ func (p *Processor) DomainPermissionsGet(
} }
if err != nil { if err != nil {
err := gtserror.Newf("error getting %ss: %w", permissionType.String(), err) err := gtserror.Newf("error getting %ss: %w", permType.String(), err)
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }
@ -303,7 +285,7 @@ func (p *Processor) DomainPermissionsGet(
// suitable for writing out to an export. // suitable for writing out to an export.
func (p *Processor) DomainPermissionGet( func (p *Processor) DomainPermissionGet(
ctx context.Context, ctx context.Context,
permissionType gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
id string, id string,
export bool, export bool,
) (*apimodel.DomainPermission, gtserror.WithCode) { ) (*apimodel.DomainPermission, gtserror.WithCode) {
@ -312,7 +294,7 @@ func (p *Processor) DomainPermissionGet(
err error err error
) )
switch permissionType { switch permType {
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
domainPerm, err = p.state.DB.GetDomainBlockByID(ctx, id) domainPerm, err = p.state.DB.GetDomainBlockByID(ctx, id)
case gtsmodel.DomainPermissionAllow: case gtsmodel.DomainPermissionAllow:
@ -323,11 +305,11 @@ func (p *Processor) DomainPermissionGet(
if err != nil { if err != nil {
if errors.Is(err, db.ErrNoEntries) { if errors.Is(err, db.ErrNoEntries) {
err = fmt.Errorf("no domain %s exists with id %s", permissionType.String(), id) err = fmt.Errorf("no domain %s exists with id %s", permType.String(), id)
return nil, gtserror.NewErrorNotFound(err, err.Error()) return nil, gtserror.NewErrorNotFound(err, err.Error())
} }
err = gtserror.Newf("error getting domain %s with id %s: %w", permissionType.String(), id, err) err = gtserror.Newf("error getting domain %s with id %s: %w", permType.String(), id, err)
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }

View file

@ -42,7 +42,7 @@ type domainPermAction struct {
// Type of permission // Type of permission
// to create or delete. // to create or delete.
permissionType gtsmodel.DomainPermissionType permType gtsmodel.DomainPermissionType
// Domain to target // Domain to target
// with the permission. // with the permission.
@ -89,9 +89,9 @@ func (suite *DomainBlockTestSuite) runDomainPermTest(t domainPermTest) {
var actionID string var actionID string
switch action.createOrDelete { switch action.createOrDelete {
case "create": case "create":
_, actionID = suite.createDomainPerm(action.permissionType, action.domain) _, actionID = suite.createDomainPerm(action.permType, action.domain)
case "delete": case "delete":
_, actionID = suite.deleteDomainPerm(action.permissionType, action.domain) _, actionID = suite.deleteDomainPerm(action.permType, action.domain)
default: default:
panic("createOrDelete was not 'create' or 'delete'") panic("createOrDelete was not 'create' or 'delete'")
} }
@ -118,16 +118,16 @@ func (suite *DomainBlockTestSuite) runDomainPermTest(t domainPermTest) {
} }
} }
// create given permissionType with default values. // create given permType with default values.
func (suite *DomainBlockTestSuite) createDomainPerm( func (suite *DomainBlockTestSuite) createDomainPerm(
permissionType gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
domain string, domain string,
) (*apimodel.DomainPermission, string) { ) (*apimodel.DomainPermission, string) {
ctx := context.Background() ctx := context.Background()
apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionCreate( apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionCreate(
ctx, ctx,
permissionType, permType,
suite.testAccounts["admin_account"], suite.testAccounts["admin_account"],
domain, domain,
false, false,
@ -144,7 +144,7 @@ func (suite *DomainBlockTestSuite) createDomainPerm(
// delete given permission type. // delete given permission type.
func (suite *DomainBlockTestSuite) deleteDomainPerm( func (suite *DomainBlockTestSuite) deleteDomainPerm(
permissionType gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
domain string, domain string,
) (*apimodel.DomainPermission, string) { ) (*apimodel.DomainPermission, string) {
var ( var (
@ -154,7 +154,7 @@ func (suite *DomainBlockTestSuite) deleteDomainPerm(
// To delete the permission, // To delete the permission,
// first get it from the db. // first get it from the db.
switch permissionType { switch permType {
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
domainPermission, _ = suite.db.GetDomainBlock(ctx, domain) domainPermission, _ = suite.db.GetDomainBlock(ctx, domain)
case gtsmodel.DomainPermissionAllow: case gtsmodel.DomainPermissionAllow:
@ -170,7 +170,7 @@ func (suite *DomainBlockTestSuite) deleteDomainPerm(
// Now use the ID to delete it. // Now use the ID to delete it.
apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionDelete( apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionDelete(
ctx, ctx,
permissionType, permType,
suite.testAccounts["admin_account"], suite.testAccounts["admin_account"],
domainPermission.GetID(), domainPermission.GetID(),
) )
@ -246,7 +246,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndUnblockDomain() {
actions: []domainPermAction{ actions: []domainPermAction{
{ {
createOrDelete: "create", createOrDelete: "create",
permissionType: gtsmodel.DomainPermissionBlock, permType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(_ context.Context, account *gtsmodel.Account) bool { expected: func(_ context.Context, account *gtsmodel.Account) bool {
// Domain was blocked, so each // Domain was blocked, so each
@ -256,7 +256,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndUnblockDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permissionType: gtsmodel.DomainPermissionBlock, permType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(_ context.Context, account *gtsmodel.Account) bool { expected: func(_ context.Context, account *gtsmodel.Account) bool {
// Domain was unblocked, so each // Domain was unblocked, so each
@ -279,7 +279,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
actions: []domainPermAction{ actions: []domainPermAction{
{ {
createOrDelete: "create", createOrDelete: "create",
permissionType: gtsmodel.DomainPermissionBlock, permType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was blocked, so each // Domain was blocked, so each
@ -316,7 +316,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
}, },
{ {
createOrDelete: "create", createOrDelete: "create",
permissionType: gtsmodel.DomainPermissionAllow, permType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was explicitly allowed, so each // Domain was explicitly allowed, so each
@ -355,7 +355,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permissionType: gtsmodel.DomainPermissionAllow, permType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the allow now, while there's // Deleting the allow now, while there's
@ -382,7 +382,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permissionType: gtsmodel.DomainPermissionBlock, permType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the block now should // Deleting the block now should
@ -421,7 +421,7 @@ func (suite *DomainBlockTestSuite) TestAllowAndBlockDomain() {
actions: []domainPermAction{ actions: []domainPermAction{
{ {
createOrDelete: "create", createOrDelete: "create",
permissionType: gtsmodel.DomainPermissionAllow, permType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was explicitly allowed, // Domain was explicitly allowed,
@ -458,7 +458,7 @@ func (suite *DomainBlockTestSuite) TestAllowAndBlockDomain() {
}, },
{ {
createOrDelete: "create", createOrDelete: "create",
permissionType: gtsmodel.DomainPermissionBlock, permType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Create a block. An allow existed, so // Create a block. An allow existed, so
@ -497,7 +497,7 @@ func (suite *DomainBlockTestSuite) TestAllowAndBlockDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permissionType: gtsmodel.DomainPermissionAllow, permType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the allow now, while there's // Deleting the allow now, while there's

View file

@ -0,0 +1,149 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package admin
import (
"context"
"errors"
"fmt"
"net/url"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/paging"
)
// DomainPermissionDraftGet returns one
// domain permission draft with the given id.
func (p *Processor) DomainPermissionDraftGet(
ctx context.Context,
id string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
return nil, gtserror.NewErrorInternalError(err)
}
if permDraft == nil {
err = fmt.Errorf("domain permission draft %s not found", id)
return nil, gtserror.NewErrorNotFound(err, err.Error())
}
return p.apiDomainPerm(ctx, permDraft, false)
}
// DomainPermissionDraftsGet returns a page of
// DomainPermissionDrafts with the given parameters.
func (p *Processor) DomainPermissionDraftsGet(
ctx context.Context,
subscriptionID string,
domain string,
permType gtsmodel.DomainPermissionType,
page *paging.Page,
) (*apimodel.PageableResponse, gtserror.WithCode) {
permDrafts, err := p.state.DB.GetDomainPermissionDrafts(
ctx,
permType,
subscriptionID,
domain,
page,
)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err := gtserror.Newf("db error: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
count := len(permDrafts)
if count == 0 {
return paging.EmptyResponse(), nil
}
// Get the lowest and highest
// ID values, used for paging.
lo := permDrafts[count-1].ID
hi := permDrafts[0].ID
// Convert each perm draft to API model.
items := make([]any, len(permDrafts))
for i, permDraft := range permDrafts {
apiPermDraft, err := p.apiDomainPerm(ctx, permDraft, false)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
items[i] = apiPermDraft
}
// Assemble next/prev page queries.
query := make(url.Values, 3)
if subscriptionID != "" {
query.Set(apiutil.DomainPermissionSubscriptionIDKey, subscriptionID)
}
if domain != "" {
query.Set(apiutil.DomainPermissionDomainKey, domain)
}
if permType != gtsmodel.DomainPermissionUnknown {
query.Set(apiutil.DomainPermissionPermTypeKey, permType.String())
}
return paging.PackageResponse(paging.ResponseParams{
Items: items,
Path: "/api/v1/admin/domain_permission_drafts",
Next: page.Next(lo, hi),
Prev: page.Prev(lo, hi),
Query: query,
}), nil
}
func (p *Processor) DomainPermissionDraftCreate(
ctx context.Context,
acct *gtsmodel.Account,
domain string,
permType gtsmodel.DomainPermissionType,
obfuscate bool,
publicComment string,
privateComment string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
permDraft := &gtsmodel.DomainPermissionDraft{
ID: id.NewULID(),
PermissionType: permType,
Domain: domain,
CreatedByAccountID: acct.ID,
CreatedByAccount: acct,
PrivateComment: privateComment,
PublicComment: publicComment,
Obfuscate: &obfuscate,
}
if err := p.state.DB.PutDomainPermissionDraft(ctx, permDraft); err != nil {
if errors.Is(err, db.ErrAlreadyExists) {
const text = "a domain permission draft already exists with this permission type and domain"
return nil, gtserror.NewErrorConflict(errors.New(text), text)
}
// Real error.
err := gtserror.Newf("db error putting domain permission draft: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiDomainPerm(ctx, permDraft, false)
}

View file

@ -22,6 +22,7 @@
"errors" "errors"
"time" "time"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -97,3 +98,20 @@ func (p *Processor) rangeDomainAccounts(
} }
} }
} }
// apiDomainPerm is a cheeky shortcut for returning
// the API version of the given domain permission,
// or an appropriate error if something goes wrong.
func (p *Processor) apiDomainPerm(
ctx context.Context,
domainPermission gtsmodel.DomainPermission,
export bool,
) (*apimodel.DomainPermission, gtserror.WithCode) {
apiDomainPerm, err := p.converter.DomainPermToAPIDomainPerm(ctx, domainPermission, export)
if err != nil {
err := gtserror.NewfAt(3, "error converting domain permission to api model: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return apiDomainPerm, nil
}

Binary file not shown.

Binary file not shown.

74
vendor/codeberg.org/gruf/go-ffmpreg/wasm/funcs.go generated vendored Normal file
View file

@ -0,0 +1,74 @@
package wasm
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
)
type snapshotskey struct{}
// withSetjmpLongjmp updates the context to contain wazero/experimental.Snapshotter{} support,
// and embeds the necessary snapshots map required for later calls to Setjmp() / Longjmp().
func withSetjmpLongjmp(ctx context.Context) context.Context {
snapshots := make(map[uint32]experimental.Snapshot, 10)
ctx = experimental.WithSnapshotter(ctx)
ctx = context.WithValue(ctx, snapshotskey{}, snapshots)
return ctx
}
func getSnapshots(ctx context.Context) map[uint32]experimental.Snapshot {
v, _ := ctx.Value(snapshotskey{}).(map[uint32]experimental.Snapshot)
return v
}
// setjmp implements the C function: setjmp(env jmp_buf)
func setjmp(ctx context.Context, mod api.Module, stack []uint64) {
// Input arguments.
envptr := api.DecodeU32(stack[0])
// Take snapshot of current execution environment.
snapshotter := experimental.GetSnapshotter(ctx)
snapshot := snapshotter.Snapshot()
// Get stored snapshots map.
snapshots := getSnapshots(ctx)
if snapshots == nil {
panic("setjmp / longjmp not supported")
}
// Set latest snapshot in map.
snapshots[envptr] = snapshot
// Set return.
stack[0] = 0
}
// longjmp implements the C function: int longjmp(env jmp_buf, value int)
func longjmp(ctx context.Context, mod api.Module, stack []uint64) {
// Input arguments.
envptr := api.DecodeU32(stack[0])
// val := stack[1]
// Get stored snapshots map.
snapshots := getSnapshots(ctx)
if snapshots == nil {
panic("setjmp / longjmp not supported")
}
// Get snapshot stored in map.
snapshot := snapshots[envptr]
if snapshot == nil {
panic("must first call setjmp")
}
// Set return.
stack[0] = 0
// Restore execution and
// return passed value arg.
snapshot.Restore(stack[1:])
}

View file

@ -6,19 +6,9 @@
"unsafe" "unsafe"
"github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/sys" "github.com/tetratelabs/wazero/sys"
) )
// CoreFeatures are the WebAssembly Core specification
// features our embedded binaries are compiled with.
const CoreFeatures = api.CoreFeatureSIMD |
api.CoreFeatureBulkMemoryOperations |
api.CoreFeatureNonTrappingFloatToIntConversion |
api.CoreFeatureMutableGlobal |
api.CoreFeatureReferenceTypes |
api.CoreFeatureSignExtensionOps
// Args encompasses a common set of // Args encompasses a common set of
// configuration options often passed to // configuration options often passed to
// wazero.Runtime on module instantiation. // wazero.Runtime on module instantiation.
@ -64,6 +54,9 @@ func Run(
modcfg = args.Config(modcfg) modcfg = args.Config(modcfg)
} }
// Enable setjmp longjmp.
ctx = withSetjmpLongjmp(ctx)
// Instantiate the module from precompiled wasm module data. // Instantiate the module from precompiled wasm module data.
mod, err := runtime.InstantiateModule(ctx, module, modcfg) mod, err := runtime.InstantiateModule(ctx, module, modcfg)

67
vendor/codeberg.org/gruf/go-ffmpreg/wasm/runtime.go generated vendored Normal file
View file

@ -0,0 +1,67 @@
package wasm
import (
"context"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)
// CoreFeatures are the WebAssembly Core specification
// features our embedded binaries are compiled with.
const CoreFeatures = api.CoreFeatureSIMD |
api.CoreFeatureBulkMemoryOperations |
api.CoreFeatureNonTrappingFloatToIntConversion |
api.CoreFeatureMutableGlobal |
api.CoreFeatureReferenceTypes |
api.CoreFeatureSignExtensionOps
// NewRuntime returns a new WebAssembly wazero.Runtime compatible with go-ffmpreg.
func NewRuntime(ctx context.Context, cfg wazero.RuntimeConfig) (wazero.Runtime, error) {
var err error
if cfg == nil {
// Ensure runtime config is set.
cfg = wazero.NewRuntimeConfig()
}
// Set core features ffmpeg compiled with.
cfg = cfg.WithCoreFeatures(CoreFeatures)
// Instantiate runtime with prepared config.
rt := wazero.NewRuntimeWithConfig(ctx, cfg)
// Prepare default "env" host module.
env := rt.NewHostModuleBuilder("env")
// Register setjmp host function.
env = env.NewFunctionBuilder().
WithGoModuleFunction(
api.GoModuleFunc(setjmp),
[]api.ValueType{api.ValueTypeI32},
[]api.ValueType{api.ValueTypeI32},
).Export("setjmp")
// Register longjmp host function.
env = env.NewFunctionBuilder().
WithGoModuleFunction(
api.GoModuleFunc(longjmp),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32},
[]api.ValueType{},
).Export("longjmp")
// Instantiate "env" module.
_, err = env.Instantiate(ctx)
if err != nil {
return nil, err
}
// Instantiate the wasi snapshot preview 1 in runtime.
_, err = wasi_snapshot_preview1.Instantiate(ctx, rt)
if err != nil {
return nil, err
}
return rt, nil
}

View file

@ -253,7 +253,7 @@ The full API Reference is available here.
* [setbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketencryption.go) * [setbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketencryption.go)
* [getbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketencryption.go) * [getbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketencryption.go)
* [deletebucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/deletebucketencryption.go) * [removebucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/removebucketencryption.go)
### Full Examples : Bucket replication Operations ### Full Examples : Bucket replication Operations

View file

@ -52,7 +52,7 @@ func (c *Client) putObjectMultipartStream(ctx context.Context, bucketName, objec
} else { } else {
info, err = c.putObjectMultipartStreamOptionalChecksum(ctx, bucketName, objectName, reader, size, opts) info, err = c.putObjectMultipartStreamOptionalChecksum(ctx, bucketName, objectName, reader, size, opts)
} }
if err != nil { if err != nil && s3utils.IsGoogleEndpoint(*c.endpointURL) {
errResp := ToErrorResponse(err) errResp := ToErrorResponse(err)
// Verify if multipart functionality is not available, if not // Verify if multipart functionality is not available, if not
// fall back to single PutObject operation. // fall back to single PutObject operation.

View file

@ -128,7 +128,7 @@ type Options struct {
// Global constants. // Global constants.
const ( const (
libraryName = "minio-go" libraryName = "minio-go"
libraryVersion = "v7.0.78" libraryVersion = "v7.0.79"
) )
// User Agent should always following the below style. // User Agent should always following the below style.

View file

@ -702,7 +702,7 @@ func compileTemplates(filenames ...string) (*template.Template, error) {
tmpl = tmpl.New(name) tmpl = tmpl.New(name)
} }
b, err := ioutil.ReadFile(filename) b, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -6,7 +6,6 @@
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"mime" "mime"
"net/http" "net/http"
@ -69,14 +68,14 @@ func (c *cmdMinifier) Minify(_ *M, w io.Writer, r io.Reader, _ map[string]string
if j := strings.Index(arg, "$in"); j != -1 { if j := strings.Index(arg, "$in"); j != -1 {
var err error var err error
ext := cmdArgExtension.FindString(arg[j+3:]) ext := cmdArgExtension.FindString(arg[j+3:])
if in, err = ioutil.TempFile("", "minify-in-*"+ext); err != nil { if in, err = os.CreateTemp("", "minify-in-*"+ext); err != nil {
return err return err
} }
cmd.Args[i] = arg[:j] + in.Name() + arg[j+3+len(ext):] cmd.Args[i] = arg[:j] + in.Name() + arg[j+3+len(ext):]
} else if j := strings.Index(arg, "$out"); j != -1 { } else if j := strings.Index(arg, "$out"); j != -1 {
var err error var err error
ext := cmdArgExtension.FindString(arg[j+4:]) ext := cmdArgExtension.FindString(arg[j+4:])
if out, err = ioutil.TempFile("", "minify-out-*"+ext); err != nil { if out, err = os.CreateTemp("", "minify-out-*"+ext); err != nil {
return err return err
} }
cmd.Args[i] = arg[:j] + out.Name() + arg[j+4+len(ext):] cmd.Args[i] = arg[:j] + out.Name() + arg[j+4+len(ext):]

View file

@ -510,14 +510,6 @@ func NewBinaryReader2File(filename string) (*BinaryReader2, error) {
return NewBinaryReader2(f), nil return NewBinaryReader2(f), nil
} }
func NewBinaryReader2Mmap(filename string) (*BinaryReader2, error) {
f, err := newBinaryReaderMmap(filename)
if err != nil {
return nil, err
}
return NewBinaryReader2(f), nil
}
func (r *BinaryReader2) Err() error { func (r *BinaryReader2) Err() error {
return r.err return r.err
} }

View file

@ -81,3 +81,11 @@ func (r *binaryReaderMmap) Bytes(n int, off int64) ([]byte, error) {
} }
return r.data[off : off+int64(n) : off+int64(n)], nil return r.data[off : off+int64(n) : off+int64(n)], nil
} }
func NewBinaryReader2Mmap(filename string) (*BinaryReader2, error) {
f, err := newBinaryReaderMmap(filename)
if err != nil {
return nil, err
}
return NewBinaryReader2(f), nil
}

8
vendor/modules.txt vendored
View file

@ -24,7 +24,7 @@ codeberg.org/gruf/go-fastcopy
# codeberg.org/gruf/go-fastpath/v2 v2.0.0 # codeberg.org/gruf/go-fastpath/v2 v2.0.0
## explicit; go 1.14 ## explicit; go 1.14
codeberg.org/gruf/go-fastpath/v2 codeberg.org/gruf/go-fastpath/v2
# codeberg.org/gruf/go-ffmpreg v0.3.1 # codeberg.org/gruf/go-ffmpreg v0.4.2
## explicit; go 1.22.0 ## explicit; go 1.22.0
codeberg.org/gruf/go-ffmpreg/embed/ffmpeg codeberg.org/gruf/go-ffmpreg/embed/ffmpeg
codeberg.org/gruf/go-ffmpreg/embed/ffprobe codeberg.org/gruf/go-ffmpreg/embed/ffprobe
@ -486,7 +486,7 @@ github.com/miekg/dns
# github.com/minio/md5-simd v1.1.2 # github.com/minio/md5-simd v1.1.2
## explicit; go 1.14 ## explicit; go 1.14
github.com/minio/md5-simd github.com/minio/md5-simd
# github.com/minio/minio-go/v7 v7.0.78 # github.com/minio/minio-go/v7 v7.0.79
## explicit; go 1.22 ## explicit; go 1.22
github.com/minio/minio-go/v7 github.com/minio/minio-go/v7
github.com/minio/minio-go/v7/pkg/cors github.com/minio/minio-go/v7/pkg/cors
@ -833,11 +833,11 @@ github.com/superseriousbusiness/oauth2/v4/generates
github.com/superseriousbusiness/oauth2/v4/manage github.com/superseriousbusiness/oauth2/v4/manage
github.com/superseriousbusiness/oauth2/v4/models github.com/superseriousbusiness/oauth2/v4/models
github.com/superseriousbusiness/oauth2/v4/server github.com/superseriousbusiness/oauth2/v4/server
# github.com/tdewolff/minify/v2 v2.21.0 # github.com/tdewolff/minify/v2 v2.21.1
## explicit; go 1.18 ## explicit; go 1.18
github.com/tdewolff/minify/v2 github.com/tdewolff/minify/v2
github.com/tdewolff/minify/v2/html github.com/tdewolff/minify/v2/html
# github.com/tdewolff/parse/v2 v2.7.17 # github.com/tdewolff/parse/v2 v2.7.18
## explicit; go 1.13 ## explicit; go 1.13
github.com/tdewolff/parse/v2 github.com/tdewolff/parse/v2
github.com/tdewolff/parse/v2/buffer github.com/tdewolff/parse/v2/buffer