diff --git a/api/openapi.yaml b/api/openapi.yaml index bbe8eb7d5..62ea7c677 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1043,6 +1043,20 @@ paths: "404": { $ref: "#/components/responses/NotFound" } "200": { $ref: "#/components/responses/LinkListOK" } + /v1/links/{link_slug}: + get: + operationId: LinkGet + description: | + Get the details for a specific link. Such as where it's been posted, + which resources it's linked to and how many times it's been opened. + tags: [links] + security: [] + parameters: [{ $ref: "#/components/parameters/LinkSlugParam" }] + responses: + default: { $ref: "#/components/responses/InternalServerError" } + "404": { $ref: "#/components/responses/NotFound" } + "200": { $ref: "#/components/responses/LinkGetOK" } + components: # # 8888888b. d8888 8888888b. d8888 888b d888 8888888888 88888888888 8888888888 8888888b. .d8888b. @@ -1178,6 +1192,14 @@ components: schema: $ref: "#/components/schemas/Identifier" + LinkSlugParam: + description: Unique link Slug. + name: link_slug + in: path + required: true + schema: + type: string + # # 8888888b. 8888888888 .d88888b. 888 888 8888888888 .d8888b. 88888888888 .d8888b. # 888 Y88b 888 d88P" "Y88b 888 888 888 d88P Y88b 888 d88P Y88b @@ -1693,6 +1715,13 @@ components: properties: links: { $ref: "#/components/schemas/LinkList" } + LinkGetOK: + description: Link data. + content: + application/json: + schema: + $ref: "#/components/schemas/LinkWithRefs" + # # .d8888b. .d8888b. 888 888 8888888888 888b d888 d8888 .d8888b. # d88P Y88b d88P Y88b 888 888 888 8888b d8888 d88888 d88P Y88b @@ -2587,9 +2616,7 @@ components: required: [title, slug, short, pinned, author, tags, posts, category, reacts] properties: - posts: - type: array - items: { $ref: "#/components/schemas/PostProps" } + posts: { $ref: "#/components/schemas/PostList" } # # 8888888b. 888 @@ -2612,6 +2639,10 @@ components: - { $ref: "#/components/schemas/PostMetadata" } - { $ref: "#/components/schemas/PostCommonProps" } + PostList: + type: array + items: { $ref: "#/components/schemas/PostProps" } + PostMetadata: type: object allOf: @@ -3042,6 +3073,17 @@ components: domain: { $ref: "#/components/schemas/LinkDomain" } assets: { $ref: "#/components/schemas/AssetList" } + LinkWithRefs: + allOf: + - $ref: "#/components/schemas/Link" + - required: [threads, posts, clusters, items, collections] + properties: + threads: { $ref: "#/components/schemas/ThreadList" } + posts: { $ref: "#/components/schemas/PostList" } + clusters: { $ref: "#/components/schemas/ClusterList" } + items: { $ref: "#/components/schemas/ItemList" } + collections: { $ref: "#/components/schemas/CollectionList" } + LinkList: type: array items: { $ref: "#/components/schemas/Link" } diff --git a/app/resources/link_graph/db.go b/app/resources/link_graph/db.go new file mode 100644 index 000000000..e150ec40a --- /dev/null +++ b/app/resources/link_graph/db.go @@ -0,0 +1,42 @@ +package link_graph + +import ( + "context" + + "github.com/Southclaws/fault" + "github.com/Southclaws/fault/fctx" + + "github.com/Southclaws/storyden/internal/ent" + "github.com/Southclaws/storyden/internal/ent/link" +) + +type database struct { + db *ent.Client +} + +func New(db *ent.Client) Repository { + return &database{db} +} + +func (d *database) Get(ctx context.Context, slug string) (*WithRefs, error) { + query := d.db.Link.Query(). + Where(link.SlugEqualFold(slug)). + WithAssets(). + WithPosts(func(pq *ent.PostQuery) { + pq.WithAuthor() + }). + WithClusters(). + WithItems() + + r, err := query.First(ctx) + if err != nil { + return nil, fault.Wrap(err, fctx.With(ctx)) + } + + link, err := Map(r) + if err != nil { + return nil, fault.Wrap(err, fctx.With(ctx)) + } + + return link, nil +} diff --git a/app/resources/link_graph/link.go b/app/resources/link_graph/link.go new file mode 100644 index 000000000..179e77f34 --- /dev/null +++ b/app/resources/link_graph/link.go @@ -0,0 +1,93 @@ +package link_graph + +import ( + "context" + + "github.com/Southclaws/dt" + "github.com/Southclaws/fault" + "github.com/Southclaws/opt" + "github.com/rs/xid" + + "github.com/Southclaws/storyden/app/resources/asset" + "github.com/Southclaws/storyden/app/resources/datagraph" + "github.com/Southclaws/storyden/app/resources/reply" + "github.com/Southclaws/storyden/app/resources/thread" + "github.com/Southclaws/storyden/internal/ent" +) + +type ID = xid.ID + +type WithRefs struct { + ID ID + URL string + Slug string + Domain string + Title opt.Optional[string] + Description opt.Optional[string] + Assets []*asset.Asset + Threads []*thread.Thread + Replies []*reply.Reply + Clusters []*datagraph.Cluster + Items []*datagraph.Item +} + +func (l *WithRefs) AssetIDs() []asset.AssetID { + return dt.Map(l.Assets, func(a *asset.Asset) asset.AssetID { return a.ID }) +} + +type Repository interface { + Get(ctx context.Context, slug string) (*WithRefs, error) +} + +func Map(in *ent.Link) (*WithRefs, error) { + postEdge, err := in.Edges.PostsOrErr() + if err != nil { + return nil, fault.Wrap(err) + } + + clusterEdge, err := in.Edges.ClustersOrErr() + if err != nil { + return nil, fault.Wrap(err) + } + + itemEdge, err := in.Edges.ItemsOrErr() + if err != nil { + return nil, fault.Wrap(err) + } + + // Mapping + + threads, err := dt.MapErr(dt.Filter(postEdge, func(p *ent.Post) bool { return p.First }), thread.FromModel) + if err != nil { + return nil, fault.Wrap(err) + } + + replies, err := dt.MapErr(dt.Filter(postEdge, func(p *ent.Post) bool { return !p.First }), reply.FromModel) + if err != nil { + return nil, fault.Wrap(err) + } + + clusters, err := dt.MapErr(clusterEdge, datagraph.ClusterFromModel) + if err != nil { + return nil, fault.Wrap(err) + } + + items, err := dt.MapErr(itemEdge, datagraph.ItemFromModel) + if err != nil { + return nil, fault.Wrap(err) + } + + return &WithRefs{ + ID: in.ID, + URL: in.URL, + Slug: in.Slug, + Domain: in.Domain, + Title: opt.New(in.Title), + Description: opt.New(in.Description), + Assets: dt.Map(in.Edges.Assets, asset.FromModel), + Threads: threads, + Replies: replies, + Clusters: clusters, + Items: items, + }, nil +} diff --git a/app/resources/resources.go b/app/resources/resources.go index 707ce3546..bf2a3b733 100644 --- a/app/resources/resources.go +++ b/app/resources/resources.go @@ -13,6 +13,7 @@ import ( "github.com/Southclaws/storyden/app/resources/item" "github.com/Southclaws/storyden/app/resources/item_search" "github.com/Southclaws/storyden/app/resources/link" + "github.com/Southclaws/storyden/app/resources/link_graph" "github.com/Southclaws/storyden/app/resources/notification" "github.com/Southclaws/storyden/app/resources/post_search" "github.com/Southclaws/storyden/app/resources/rbac" @@ -44,6 +45,7 @@ func Build() fx.Option { item.New, item_search.New, link.New, + link_graph.New, ), ) } diff --git a/app/services/url/scraper.go b/app/services/url/scraper.go index 183d8800a..10579a0f4 100644 --- a/app/services/url/scraper.go +++ b/app/services/url/scraper.go @@ -3,7 +3,6 @@ package url import ( "context" "net/http" - "net/url" "github.com/PuerkitoBio/goquery" "github.com/Southclaws/dt" @@ -34,32 +33,26 @@ func New() Scraper { } func (s *scraper) Scrape(ctx context.Context, addr string) (*WebContent, error) { - u, err := url.Parse(addr) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, addr, nil) if err != nil { return nil, fault.Wrap(err, fctx.With(ctx)) } - req := &http.Request{ - URL: u, - // Very naively pretending to be a browser. - Header: http.Header{ - "Accept": []string{"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"}, - "Accept-Encoding": []string{"gzip, deflate, br"}, - "Accept-Language": []string{"en-GB,en-US;q=0.9,en;q=0.8"}, - "Cache-Control": []string{"no-cache"}, - "Dnt": []string{"1"}, - "Pragma": []string{"no-cache"}, - "Sec-Ch-Ua": []string{`"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"`}, - "Sec-Ch-Ua-Mobile": []string{"?0"}, - "Sec-Ch-Ua-Platform": []string{`"macOS"`}, - "Sec-Fetch-Dest": []string{"document"}, - "Sec-Fetch-Mode": []string{"navigate"}, - "Sec-Fetch-Site": []string{"none"}, - "Sec-Fetch-User": []string{"?1"}, - "Upgrade-Insecure-Requests": []string{"1"}, - "User-Agent": []string{`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36`}, - }, - } + req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") + req.Header.Add("Accept-Encoding", "*") + req.Header.Add("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") + req.Header.Add("Cache-Control", "no-cache") + req.Header.Add("Dnt", "1") + req.Header.Add("Pragma", "no-cache") + req.Header.Add("Sec-Ch-Ua", `"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"`) + req.Header.Add("Sec-Ch-Ua-Mobile", "?0") + req.Header.Add("Sec-Ch-Ua-Platform", `"macOS"`) + req.Header.Add("Sec-Fetch-Dest", "document") + req.Header.Add("Sec-Fetch-Mode", "navigate") + req.Header.Add("Sec-Fetch-Site", "none") + req.Header.Add("Sec-Fetch-User", "?1") + req.Header.Add("Upgrade-Insecure-Requests", "1") + req.Header.Add("User-Agent", `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36`) resp, err := http.DefaultClient.Do(req) if err != nil { diff --git a/app/transports/openapi/bindings/links.go b/app/transports/openapi/bindings/links.go index 578d5d893..3caacb7bf 100644 --- a/app/transports/openapi/bindings/links.go +++ b/app/transports/openapi/bindings/links.go @@ -10,6 +10,7 @@ import ( "github.com/Southclaws/fault/ftag" "github.com/Southclaws/storyden/app/resources/link" + "github.com/Southclaws/storyden/app/resources/link_graph" "github.com/Southclaws/storyden/app/services/hydrator/fetcher" "github.com/Southclaws/storyden/internal/openapi" ) @@ -17,15 +18,18 @@ import ( type Links struct { fr fetcher.Service lr link.Repository + lg link_graph.Repository } func NewLinks( fr fetcher.Service, lr link.Repository, + lg link_graph.Repository, ) Links { return Links{ fr: fr, lr: lr, + lg: lg, } } @@ -71,3 +75,29 @@ func (i *Links) LinkList(ctx context.Context, request openapi.LinkListRequestObj }, }, nil } + +func (i *Links) LinkGet(ctx context.Context, request openapi.LinkGetRequestObject) (openapi.LinkGetResponseObject, error) { + l, err := i.lg.Get(ctx, request.LinkSlug) + if err != nil { + return nil, fault.Wrap(err, fctx.With(ctx)) + } + + return openapi.LinkGet200JSONResponse{ + LinkGetOKJSONResponse: openapi.LinkGetOKJSONResponse(serialiseLinkWithRefs(l)), + }, nil +} + +func serialiseLinkWithRefs(in *link_graph.WithRefs) openapi.LinkWithRefs { + return openapi.LinkWithRefs{ + Url: in.URL, + Title: in.Title.Ptr(), + Description: in.Description.Ptr(), + Slug: in.Slug, + Domain: in.Domain, + Assets: dt.Map(in.Assets, serialiseAssetReference), + Threads: dt.Map(in.Threads, serialiseThreadReference), + Posts: dt.Map(in.Replies, serialisePost), + Clusters: dt.Map(in.Clusters, serialiseCluster), + Items: dt.Map(in.Items, serialiseItemWithParents), + } +} diff --git a/internal/openapi/generated.go b/internal/openapi/generated.go index 63c0c234e..d3adf7a12 100644 --- a/internal/openapi/generated.go +++ b/internal/openapi/generated.go @@ -1110,6 +1110,27 @@ type LinkSlug = string // LinkTitle defines model for LinkTitle. type LinkTitle = string +// LinkWithRefs defines model for LinkWithRefs. +type LinkWithRefs struct { + Assets AssetList `json:"assets"` + Clusters ClusterList `json:"clusters"` + Collections CollectionList `json:"collections"` + Description *LinkDescription `json:"description,omitempty"` + Domain LinkDomain `json:"domain"` + + // Items A list of items where each item includes all the information about its + // parent cluster too. For use with APIs that want to provide a list of + // items that may be part of different clusters. + Items ItemList `json:"items"` + Posts PostList `json:"posts"` + Slug LinkSlug `json:"slug"` + Threads ThreadList `json:"threads"` + Title *LinkTitle `json:"title,omitempty"` + + // Url A web address + Url URL `json:"url"` +} + // Metadata Arbitrary metadata for the resource. type Metadata map[string]interface{} @@ -1197,6 +1218,9 @@ type PostInitialProps struct { Url *URL `json:"url,omitempty"` } +// PostList defines model for PostList. +type PostList = []PostProps + // PostMetadata defines model for PostMetadata. type PostMetadata = CommonProperties @@ -1480,8 +1504,8 @@ type Thread struct { Pinned bool `json:"pinned"` // PostCount The number of posts under this thread. - PostCount *int `json:"post_count,omitempty"` - Posts []PostProps `json:"posts"` + PostCount *int `json:"post_count,omitempty"` + Posts PostList `json:"posts"` // Reacts A list of reactions this post has had from people. Reacts ReactList `json:"reacts"` @@ -1678,6 +1702,9 @@ type IconSize string // ItemSlugParam A unique identifier for this resource. type ItemSlugParam = Identifier +// LinkSlugParam defines model for LinkSlugParam. +type LinkSlugParam = string + // OAuthProvider defines model for OAuthProvider. type OAuthProvider = string @@ -1851,6 +1878,9 @@ type ItemUpdateOK = Item // LinkCreateOK A web address with content information such as title, description, etc. type LinkCreateOK = Link +// LinkGetOK defines model for LinkGetOK. +type LinkGetOK = LinkWithRefs + // LinkListOK defines model for LinkListOK. type LinkListOK struct { Links LinkList `json:"links"` @@ -2371,6 +2401,9 @@ type ClientInterface interface { LinkCreate(ctx context.Context, body LinkCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // LinkGet request + LinkGet(ctx context.Context, linkSlug LinkSlugParam, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostSearch request PostSearch(ctx context.Context, params *PostSearchParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3246,6 +3279,18 @@ func (c *Client) LinkCreate(ctx context.Context, body LinkCreateJSONRequestBody, return c.Client.Do(req) } +func (c *Client) LinkGet(ctx context.Context, linkSlug LinkSlugParam, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLinkGetRequest(c.Server, linkSlug) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) PostSearch(ctx context.Context, params *PostSearchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewPostSearchRequest(c.Server, params) if err != nil { @@ -5347,6 +5392,40 @@ func NewLinkCreateRequestWithBody(server string, contentType string, body io.Rea return req, nil } +// NewLinkGetRequest generates requests for LinkGet +func NewLinkGetRequest(server string, linkSlug LinkSlugParam) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "link_slug", runtime.ParamLocationPath, linkSlug) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/links/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewPostSearchRequest generates requests for PostSearch func NewPostSearchRequest(server string, params *PostSearchParams) (*http.Request, error) { var err error @@ -6126,6 +6205,9 @@ type ClientWithResponsesInterface interface { LinkCreateWithResponse(ctx context.Context, body LinkCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*LinkCreateResponse, error) + // LinkGet request + LinkGetWithResponse(ctx context.Context, linkSlug LinkSlugParam, reqEditors ...RequestEditorFn) (*LinkGetResponse, error) + // PostSearch request PostSearchWithResponse(ctx context.Context, params *PostSearchParams, reqEditors ...RequestEditorFn) (*PostSearchResponse, error) @@ -7305,6 +7387,29 @@ func (r LinkCreateResponse) StatusCode() int { return 0 } +type LinkGetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *LinkWithRefs + JSONDefault *APIError +} + +// Status returns HTTPResponse.Status +func (r LinkGetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r LinkGetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type PostSearchResponse struct { Body []byte HTTPResponse *http.Response @@ -8180,6 +8285,15 @@ func (c *ClientWithResponses) LinkCreateWithResponse(ctx context.Context, body L return ParseLinkCreateResponse(rsp) } +// LinkGetWithResponse request returning *LinkGetResponse +func (c *ClientWithResponses) LinkGetWithResponse(ctx context.Context, linkSlug LinkSlugParam, reqEditors ...RequestEditorFn) (*LinkGetResponse, error) { + rsp, err := c.LinkGet(ctx, linkSlug, reqEditors...) + if err != nil { + return nil, err + } + return ParseLinkGetResponse(rsp) +} + // PostSearchWithResponse request returning *PostSearchResponse func (c *ClientWithResponses) PostSearchWithResponse(ctx context.Context, params *PostSearchParams, reqEditors ...RequestEditorFn) (*PostSearchResponse, error) { rsp, err := c.PostSearch(ctx, params, reqEditors...) @@ -9899,6 +10013,39 @@ func ParseLinkCreateResponse(rsp *http.Response) (*LinkCreateResponse, error) { return response, nil } +// ParseLinkGetResponse parses an HTTP response from a LinkGetWithResponse call +func ParseLinkGetResponse(rsp *http.Response) (*LinkGetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &LinkGetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest LinkWithRefs + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest APIError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSONDefault = &dest + + } + + return response, nil +} + // ParsePostSearchResponse parses an HTTP response from a PostSearchWithResponse call func ParsePostSearchResponse(rsp *http.Response) (*PostSearchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -10416,6 +10563,9 @@ type ServerInterface interface { // (POST /v1/links) LinkCreate(ctx echo.Context) error + // (GET /v1/links/{link_slug}) + LinkGet(ctx echo.Context, linkSlug LinkSlugParam) error + // (GET /v1/posts/search) PostSearch(ctx echo.Context, params PostSearchParams) error @@ -11253,6 +11403,22 @@ func (w *ServerInterfaceWrapper) LinkCreate(ctx echo.Context) error { return err } +// LinkGet converts echo context to params. +func (w *ServerInterfaceWrapper) LinkGet(ctx echo.Context) error { + var err error + // ------------- Path parameter "link_slug" ------------- + var linkSlug LinkSlugParam + + err = runtime.BindStyledParameterWithLocation("simple", false, "link_slug", runtime.ParamLocationPath, ctx.Param("link_slug"), &linkSlug) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter link_slug: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.LinkGet(ctx, linkSlug) + return err +} + // PostSearch converts echo context to params. func (w *ServerInterfaceWrapper) PostSearch(ctx echo.Context) error { var err error @@ -11554,6 +11720,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PATCH(baseURL+"/v1/items/:item_slug", wrapper.ItemUpdate) router.GET(baseURL+"/v1/links", wrapper.LinkList) router.POST(baseURL+"/v1/links", wrapper.LinkCreate) + router.GET(baseURL+"/v1/links/:link_slug", wrapper.LinkGet) router.GET(baseURL+"/v1/posts/search", wrapper.PostSearch) router.DELETE(baseURL+"/v1/posts/:post_id", wrapper.PostDelete) router.PATCH(baseURL+"/v1/posts/:post_id", wrapper.PostUpdate) @@ -11665,6 +11832,8 @@ type ItemUpdateOKJSONResponse Item type LinkCreateOKJSONResponse Link +type LinkGetOKJSONResponse LinkWithRefs + type LinkListOKJSONResponse struct { Links LinkList `json:"links"` } @@ -13667,6 +13836,42 @@ func (response LinkCreatedefaultJSONResponse) VisitLinkCreateResponse(w http.Res return json.NewEncoder(w).Encode(response.Body) } +type LinkGetRequestObject struct { + LinkSlug LinkSlugParam `json:"link_slug"` +} + +type LinkGetResponseObject interface { + VisitLinkGetResponse(w http.ResponseWriter) error +} + +type LinkGet200JSONResponse struct{ LinkGetOKJSONResponse } + +func (response LinkGet200JSONResponse) VisitLinkGetResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type LinkGet404Response = NotFoundResponse + +func (response LinkGet404Response) VisitLinkGetResponse(w http.ResponseWriter) error { + w.WriteHeader(404) + return nil +} + +type LinkGetdefaultJSONResponse struct { + Body APIError + StatusCode int +} + +func (response LinkGetdefaultJSONResponse) VisitLinkGetResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(response.StatusCode) + + return json.NewEncoder(w).Encode(response.Body) +} + type PostSearchRequestObject struct { Params PostSearchParams } @@ -14309,6 +14514,9 @@ type StrictServerInterface interface { // (POST /v1/links) LinkCreate(ctx context.Context, request LinkCreateRequestObject) (LinkCreateResponseObject, error) + // (GET /v1/links/{link_slug}) + LinkGet(ctx context.Context, request LinkGetRequestObject) (LinkGetResponseObject, error) + // (GET /v1/posts/search) PostSearch(ctx context.Context, request PostSearchRequestObject) (PostSearchResponseObject, error) @@ -15672,6 +15880,31 @@ func (sh *strictHandler) LinkCreate(ctx echo.Context) error { return nil } +// LinkGet operation middleware +func (sh *strictHandler) LinkGet(ctx echo.Context, linkSlug LinkSlugParam) error { + var request LinkGetRequestObject + + request.LinkSlug = linkSlug + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.LinkGet(ctx.Request().Context(), request.(LinkGetRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "LinkGet") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(LinkGetResponseObject); ok { + return validResponse.VisitLinkGetResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + // PostSearch operation middleware func (sh *strictHandler) PostSearch(ctx echo.Context, params PostSearchParams) error { var request PostSearchRequestObject @@ -16001,194 +16234,197 @@ func (sh *strictHandler) GetVersion(ctx echo.Context) error { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/web/src/api/openapi/links.ts b/web/src/api/openapi/links.ts index f826bb374..50afb88c4 100644 --- a/web/src/api/openapi/links.ts +++ b/web/src/api/openapi/links.ts @@ -14,6 +14,7 @@ import type { InternalServerErrorResponse, LinkCreateBody, LinkCreateOKResponse, + LinkGetOKResponse, LinkListOKResponse, LinkListParams, NotFoundResponse, @@ -93,3 +94,53 @@ export const useLinkList = < ...query, }; }; + +/** + * Get the details for a specific link. Such as where it's been posted, +which resources it's linked to and how many times it's been opened. + + */ +export const linkGet = (linkSlug: string) => { + return fetcher({ + url: `/v1/links/${linkSlug}`, + method: "get", + }); +}; + +export const getLinkGetKey = (linkSlug: string) => + [`/v1/links/${linkSlug}`] as const; + +export type LinkGetQueryResult = NonNullable< + Awaited> +>; +export type LinkGetQueryError = NotFoundResponse | InternalServerErrorResponse; + +export const useLinkGet = < + TError = NotFoundResponse | InternalServerErrorResponse, +>( + linkSlug: string, + options?: { + swr?: SWRConfiguration>, TError> & { + swrKey?: Key; + enabled?: boolean; + }; + }, +) => { + const { swr: swrOptions } = options ?? {}; + + const isEnabled = swrOptions?.enabled !== false && !!linkSlug; + const swrKey = + swrOptions?.swrKey ?? (() => (isEnabled ? getLinkGetKey(linkSlug) : null)); + const swrFn = () => linkGet(linkSlug); + + const query = useSwr>, TError>( + swrKey, + swrFn, + swrOptions, + ); + + return { + swrKey, + ...query, + }; +}; diff --git a/web/src/api/openapi/schemas/index.ts b/web/src/api/openapi/schemas/index.ts index 2a3c1d0fe..d019eb726 100644 --- a/web/src/api/openapi/schemas/index.ts +++ b/web/src/api/openapi/schemas/index.ts @@ -137,12 +137,15 @@ export * from "./linkCreateBody"; export * from "./linkCreateOKResponse"; export * from "./linkDescription"; export * from "./linkDomain"; +export * from "./linkGetOKResponse"; export * from "./linkInitialProps"; export * from "./linkList"; export * from "./linkListOKResponse"; export * from "./linkListParams"; export * from "./linkSlug"; export * from "./linkTitle"; +export * from "./linkWithRefs"; +export * from "./linkWithRefsAllOf"; export * from "./metadata"; export * from "./notFoundResponse"; export * from "./oAuthCallback"; @@ -158,6 +161,7 @@ export * from "./postContent"; export * from "./postCreateBody"; export * from "./postCreateOKResponse"; export * from "./postInitialProps"; +export * from "./postList"; export * from "./postMetadata"; export * from "./postMutableProps"; export * from "./postProps"; diff --git a/web/src/api/openapi/schemas/linkGetOKResponse.ts b/web/src/api/openapi/schemas/linkGetOKResponse.ts new file mode 100644 index 000000000..4c84a7e04 --- /dev/null +++ b/web/src/api/openapi/schemas/linkGetOKResponse.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v6.17.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ +import type { LinkWithRefs } from "./linkWithRefs"; + +/** + * Link data. + */ +export type LinkGetOKResponse = LinkWithRefs; diff --git a/web/src/api/openapi/schemas/linkWithRefs.ts b/web/src/api/openapi/schemas/linkWithRefs.ts new file mode 100644 index 000000000..43218f13a --- /dev/null +++ b/web/src/api/openapi/schemas/linkWithRefs.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v6.17.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ +import type { Link } from "./link"; +import type { LinkWithRefsAllOf } from "./linkWithRefsAllOf"; + +export type LinkWithRefs = Link & LinkWithRefsAllOf; diff --git a/web/src/api/openapi/schemas/linkWithRefsAllOf.ts b/web/src/api/openapi/schemas/linkWithRefsAllOf.ts new file mode 100644 index 000000000..f2f9fa852 --- /dev/null +++ b/web/src/api/openapi/schemas/linkWithRefsAllOf.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v6.17.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ +import type { ClusterList } from "./clusterList"; +import type { CollectionList } from "./collectionList"; +import type { ItemList } from "./itemList"; +import type { PostList } from "./postList"; +import type { ThreadList } from "./threadList"; + +export type LinkWithRefsAllOf = { + threads: ThreadList; + posts: PostList; + clusters: ClusterList; + items: ItemList; + collections: CollectionList; +}; diff --git a/web/src/api/openapi/schemas/postList.ts b/web/src/api/openapi/schemas/postList.ts new file mode 100644 index 000000000..2a0df9580 --- /dev/null +++ b/web/src/api/openapi/schemas/postList.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v6.17.0 🍺 + * Do not edit manually. + * storyden + * Storyden social API for building community driven platforms. + * OpenAPI spec version: 1 + */ +import type { PostProps } from "./postProps"; + +export type PostList = PostProps[]; diff --git a/web/src/api/openapi/schemas/threadAllOf.ts b/web/src/api/openapi/schemas/threadAllOf.ts index 778686976..c99c64d55 100644 --- a/web/src/api/openapi/schemas/threadAllOf.ts +++ b/web/src/api/openapi/schemas/threadAllOf.ts @@ -5,8 +5,8 @@ * Storyden social API for building community driven platforms. * OpenAPI spec version: 1 */ -import type { PostProps } from "./postProps"; +import type { PostList } from "./postList"; export type ThreadAllOf = { - posts: PostProps[]; + posts: PostList; }; diff --git a/web/src/app/(dashboard)/l/[slug]/page.tsx b/web/src/app/(dashboard)/l/[slug]/page.tsx index 319840aeb..b79b83cf3 100644 --- a/web/src/app/(dashboard)/l/[slug]/page.tsx +++ b/web/src/app/(dashboard)/l/[slug]/page.tsx @@ -1,4 +1,4 @@ -import { LinkScreen } from "src/screens/directory/links/LinkScreen/useLinkScreen"; +import { LinkScreen } from "src/screens/directory/links/LinkScreen/LinkScreen"; type Props = { params: { diff --git a/web/src/components/directory/links/LinkView.tsx b/web/src/components/directory/links/LinkView.tsx new file mode 100644 index 000000000..e39572e9c --- /dev/null +++ b/web/src/components/directory/links/LinkView.tsx @@ -0,0 +1,26 @@ +import { LinkWithRefs } from "src/api/openapi/schemas"; +import { Link } from "src/theme/components/Link"; + +import { Flex, styled } from "@/styled-system/jsx"; + +type Props = { + link: LinkWithRefs; +}; + +export function LinkView({ link }: Props) { + return ( + + {link.title} +

{link.description}

+ {link.url} + {link.domain} +
{link.slug}
+ {link.threads.map((v) => ( + <>{v.title} + ))} + {link.assets.map((v) => ( + <>{v.url} + ))} +
+ ); +} diff --git a/web/src/screens/directory/links/LinkScreen/Client.tsx b/web/src/screens/directory/links/LinkScreen/Client.tsx index c54dc8c79..e49a75e11 100644 --- a/web/src/screens/directory/links/LinkScreen/Client.tsx +++ b/web/src/screens/directory/links/LinkScreen/Client.tsx @@ -1,14 +1,14 @@ "use client"; +import { LinkView } from "src/components/directory/links/LinkView"; import { Unready } from "src/components/site/Unready"; -import { LinkResultList } from "./components/LinkResultList/LinkResultList"; -import { Props, useLinkIndexScreen } from "./useLinkScreen"; +import { Props, useLinkScreen } from "./useLinkScreen"; export function Client(props: Props) { - const { ready, data, error } = useLinkIndexScreen(props); + const { ready, data, error } = useLinkScreen(props); if (!ready) return ; - return ; + return ; } diff --git a/web/src/screens/directory/links/LinkScreen/LinkScreen.tsx b/web/src/screens/directory/links/LinkScreen/LinkScreen.tsx index ca6f9b8e6..a614b6c55 100644 --- a/web/src/screens/directory/links/LinkScreen/LinkScreen.tsx +++ b/web/src/screens/directory/links/LinkScreen/LinkScreen.tsx @@ -1,14 +1,13 @@ -import { LinkListOKResponse, LinkListParams } from "src/api/openapi/schemas"; +import { LinkGetOKResponse } from "src/api/openapi/schemas"; import { server } from "src/api/server"; import { Client } from "./Client"; import { Props } from "./useLinkScreen"; -export async function LinkIndexScreen(props: Props) { - const response = await server({ - url: "/v1/links", - params: {} as LinkListParams, +export async function LinkScreen(props: Omit) { + const response = await server({ + url: `/v1/links/${props.slug}`, }); - return ; + return ; } diff --git a/web/src/screens/directory/links/LinkScreen/useLinkScreen.tsx b/web/src/screens/directory/links/LinkScreen/useLinkScreen.tsx index 60c6d59f8..919fe46b1 100644 --- a/web/src/screens/directory/links/LinkScreen/useLinkScreen.tsx +++ b/web/src/screens/directory/links/LinkScreen/useLinkScreen.tsx @@ -1,22 +1,15 @@ -import { useLinkList } from "src/api/openapi/links"; -import { Link } from "src/api/openapi/schemas"; +import { useLinkGet } from "src/api/openapi/links"; +import { LinkWithRefs } from "src/api/openapi/schemas"; export type Props = { slug: string; + link: LinkWithRefs; }; -export function LinkScreen(props: Props) { - const { data, error } = useLink( - { - q: props.query, - page: props.page?.toString(), - }, - { - swr: { - fallbackData: { links: props.links }, - }, - }, - ); +export function useLinkScreen(props: Props) { + const { data, error } = useLinkGet(props.slug, { + swr: { fallbackData: props.link }, + }); if (!data) { return { @@ -27,6 +20,6 @@ export function LinkScreen(props: Props) { return { ready: true as const, - data: data.links, + data: data, }; }