Skip to main content
Glama

Storyden

by Southclaws
Mozilla Public License 2.0
229
threads.go8.56 kB
package bindings import ( "context" "net/url" "strconv" "time" "github.com/Southclaws/dt" "github.com/Southclaws/fault" "github.com/Southclaws/fault/fctx" "github.com/Southclaws/fault/ftag" "github.com/Southclaws/opt" "github.com/rs/xid" "github.com/samber/lo" "github.com/Southclaws/storyden/app/resources/account/account_querier" "github.com/Southclaws/storyden/app/resources/datagraph" "github.com/Southclaws/storyden/app/resources/profile/profile_querier" "github.com/Southclaws/storyden/app/resources/tag/tag_ref" "github.com/Southclaws/storyden/app/resources/post/thread_cache" "github.com/Southclaws/storyden/app/resources/post/thread_querier" "github.com/Southclaws/storyden/app/resources/visibility" "github.com/Southclaws/storyden/app/services/authentication/session" "github.com/Southclaws/storyden/app/services/reqinfo" thread_service "github.com/Southclaws/storyden/app/services/thread" "github.com/Southclaws/storyden/app/services/thread_mark" "github.com/Southclaws/storyden/app/transports/http/openapi" ) type Threads struct { thread_cache *thread_cache.Cache thread_svc thread_service.Service thread_mark_svc thread_mark.Service accountQuery *account_querier.Querier profileQuery *profile_querier.Querier } func NewThreads( thread_cache *thread_cache.Cache, thread_svc thread_service.Service, thread_mark_svc thread_mark.Service, accountQuery *account_querier.Querier, profileQuery *profile_querier.Querier, ) Threads { return Threads{thread_cache, thread_svc, thread_mark_svc, accountQuery, profileQuery} } func (i *Threads) ThreadCreate(ctx context.Context, request openapi.ThreadCreateRequestObject) (openapi.ThreadCreateResponseObject, error) { accountID, err := session.GetAccountID(ctx) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } status, err := opt.MapErr(opt.NewPtr(request.Body.Visibility), deserialiseThreadStatus) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } var meta map[string]any if request.Body.Meta != nil { meta = *request.Body.Meta } tags := opt.Map(opt.NewPtr(request.Body.Tags), func(tags []string) tag_ref.Names { return dt.Map(tags, deserialiseTagName) }) richContent, err := opt.MapErr(opt.NewPtr(request.Body.Body), datagraph.NewRichText) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.InvalidArgument)) } category := opt.NewPtrMap(request.Body.Category, func(cat openapi.Identifier) xid.ID { return openapi.ParseID(cat) }) url, err := opt.MapErr(opt.NewPtr(request.Body.Url), func(s string) (url.URL, error) { u, err := url.Parse(s) if err != nil { return url.URL{}, err } return *u, nil }) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.InvalidArgument)) } thread, err := i.thread_svc.Create(ctx, request.Body.Title, accountID, meta, thread_service.Partial{ Content: richContent, Category: category, Tags: tags, Visibility: status, URL: url, }, ) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } return openapi.ThreadCreate200JSONResponse{ ThreadCreateOKJSONResponse: openapi.ThreadCreateOKJSONResponse(serialiseThread(thread)), }, nil } func (i *Threads) ThreadUpdate(ctx context.Context, request openapi.ThreadUpdateRequestObject) (openapi.ThreadUpdateResponseObject, error) { postID, err := i.thread_mark_svc.Lookup(ctx, string(request.ThreadMark)) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } tags := opt.Map(opt.NewPtr(request.Body.Tags), func(tags []string) tag_ref.Names { return dt.Map(tags, deserialiseTagName) }) Visibility, err := opt.MapErr(opt.NewPtr(request.Body.Visibility), deserialiseThreadStatus) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } richContent, err := opt.MapErr(opt.NewPtr(request.Body.Body), datagraph.NewRichText) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.InvalidArgument)) } thread, err := i.thread_svc.Update(ctx, postID, thread_service.Partial{ Title: opt.NewPtr(request.Body.Title), Content: richContent, Tags: tags, Category: opt.NewPtrMap(request.Body.Category, deserialiseID), Visibility: Visibility, }) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } return openapi.ThreadUpdate200JSONResponse{ ThreadUpdateOKJSONResponse: openapi.ThreadUpdateOKJSONResponse(serialiseThread(thread)), }, nil } func (i *Threads) ThreadDelete(ctx context.Context, request openapi.ThreadDeleteRequestObject) (openapi.ThreadDeleteResponseObject, error) { postID, err := i.thread_mark_svc.Lookup(ctx, string(request.ThreadMark)) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } err = i.thread_svc.Delete(ctx, postID) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } return openapi.ThreadDelete200Response{}, nil } func (i *Threads) ThreadList(ctx context.Context, request openapi.ThreadListRequestObject) (openapi.ThreadListResponseObject, error) { pageSize := 50 page := opt.NewPtrMap(request.Params.Page, func(s string) int { v, err := strconv.ParseInt(s, 10, 32) if err != nil { return 0 } return max(1, int(v)) }).Or(1) query := opt.NewPtr(request.Params.Q) author, err := openapi.OptionalID(ctx, i.profileQuery, request.Params.Author) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } visibilities, err := opt.MapErr(opt.NewPtr(request.Params.Visibility), deserialiseVisibilityList) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } tags := opt.NewPtrMap(request.Params.Tags, func(t []openapi.Identifier) []xid.ID { return dt.Map(t, func(i openapi.Identifier) xid.ID { return openapi.ParseID(i) }) }) cats := deserialiseCategorySlugQueryParam(request.Params.Categories) page = max(0, page-1) result, err := i.thread_svc.List(ctx, page, pageSize, thread_service.Params{ Query: query, AccountID: author, Visibility: visibilities, Tags: tags, Categories: cats, }) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } page = result.CurrentPage + 1 nextPage := opt.Map(result.NextPage, func(i int) int { return i + 1 }) return openapi.ThreadList200JSONResponse{ ThreadListOKJSONResponse: openapi.ThreadListOKJSONResponse{ Body: openapi.ThreadListResult{ CurrentPage: page, NextPage: nextPage.Ptr(), PageSize: result.PageSize, Results: result.Results, Threads: dt.Map(result.Threads, serialiseThreadReference), TotalPages: result.TotalPages, }, Headers: openapi.ThreadListOKResponseHeaders{ CacheControl: "no-store", }, }, }, nil } func (i *Threads) ThreadGet(ctx context.Context, request openapi.ThreadGetRequestObject) (openapi.ThreadGetResponseObject, error) { postID, err := i.thread_mark_svc.Lookup(ctx, string(request.ThreadMark)) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } if i.thread_cache.IsNotModified(ctx, reqinfo.GetCacheQuery(ctx), xid.ID(postID)) { return openapi.ThreadGet304Response{ Headers: openapi.NotModifiedResponseHeaders{ CacheControl: "public, max-age=60, stale-while-revalidate=120", }, }, nil } pp := deserialisePageParams(request.Params.Page, 50) thread, err := i.thread_svc.Get(ctx, postID, pp) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } return openapi.ThreadGet200JSONResponse{ ThreadGetJSONResponse: openapi.ThreadGetJSONResponse{ Body: serialiseThread(thread), Headers: openapi.ThreadGetResponseHeaders{ CacheControl: "max-age=1", LastModified: thread.UpdatedAt.Format(time.RFC1123), }, }, }, nil } func deserialiseThreadStatus(in openapi.Visibility) (visibility.Visibility, error) { s, err := visibility.NewVisibility(string(in)) if err != nil { return visibility.Visibility{}, fault.Wrap(err, ftag.With(ftag.InvalidArgument)) } return s, nil } func deserialiseCategorySlugQueryParam(in *openapi.CategorySlugListQuery) opt.Optional[thread_querier.CategoryFilter] { // Do not filter by any categorise, return all threads. if in == nil { return opt.NewEmpty[thread_querier.CategoryFilter]() } // Fetch uncategorised threads only. _, isExplicitlyNull := lo.Find(*in, func(s string) bool { return s == "null" }) if isExplicitlyNull { return opt.New(thread_querier.CategoryFilter{ Uncategorised: true, }) } // Filter by these categories. return opt.New(thread_querier.CategoryFilter{ Slugs: *in, Uncategorised: false, }) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Southclaws/storyden'

If you have feedback or need assistance with the MCP directory API, please join our Discord server