Skip to content

Commit

Permalink
start work on link pages
Browse files Browse the repository at this point in the history
  • Loading branch information
Southclaws committed Nov 27, 2023
1 parent ec153d6 commit 7acec39
Show file tree
Hide file tree
Showing 19 changed files with 809 additions and 244 deletions.
48 changes: 45 additions & 3 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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" }
Expand Down
42 changes: 42 additions & 0 deletions app/resources/link_graph/db.go
Original file line number Diff line number Diff line change
@@ -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
}
93 changes: 93 additions & 0 deletions app/resources/link_graph/link.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions app/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -44,6 +45,7 @@ func Build() fx.Option {
item.New,
item_search.New,
link.New,
link_graph.New,
),
)
}
39 changes: 16 additions & 23 deletions app/services/url/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package url
import (
"context"
"net/http"
"net/url"

"github.com/PuerkitoBio/goquery"
"github.com/Southclaws/dt"
Expand Down Expand Up @@ -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 {
Expand Down
30 changes: 30 additions & 0 deletions app/transports/openapi/bindings/links.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,26 @@ 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"
)

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,
}
}

Expand Down Expand Up @@ -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),
}
}
Loading

0 comments on commit 7acec39

Please sign in to comment.