diff --git a/internal/database/migrations/1693961486_follows.sql b/internal/database/migrations/1693961486_follows.sql index b495ca5..d7dd964 100644 --- a/internal/database/migrations/1693961486_follows.sql +++ b/internal/database/migrations/1693961486_follows.sql @@ -4,9 +4,14 @@ create table account_follows ( account_id text not null references accounts (id) on delete cascade, - blog_id text not null references blogs (id) on delete cascade + blog_id text not null references blogs (id) on delete cascade, + + primary key (account_id, blog_id) ); +create index account_follows_account_id_idx on account_follows (account_id); + -- +migrate Down +drop index account_follows_account_id_idx; drop table account_follows; diff --git a/internal/database/sql/timeline.go b/internal/database/sql/timeline.go index d2d825d..461c099 100644 --- a/internal/database/sql/timeline.go +++ b/internal/database/sql/timeline.go @@ -19,22 +19,26 @@ func NewTimelineStore(q Querier) *TimelineStore { } type TimelinePost struct { - database.Post - database.Blog + Post database.Post `db:"p"` + Blog database.Blog `db:"b"` + Account database.Account `db:"a"` } func (s *TimelineStore) Home(ctx context.Context, accountID ulid.ULID, limit int, before, after *ulid.ULID) ([]TimelinePost, error) { - q := sqlf.Sprintf("SELECT p.*, b.account_id, b.name, b.domain FROM posts p JOIN blogs b ON b.id = p.blog_id") + q := sqlf.Sprintf(`SELECT p.id as "p.id", p.blog_id as "p.blog_id", p.content as "p.content", p.source as "p.source", p.visibility as "p.visibility", + b.id as "b.id", b.name as "b.name", b.domain as "b.domain", b.bio as "b.bio", b.account_id as "b.account_id", + a.id as "a.id", a.username as "a.username", a.domain as "a.domain" + FROM posts p JOIN blogs b ON b.id = p.blog_id JOIN accounts a on a.id = b.account_id`) q = sqlf.Sprintf("%v WHERE (blog_id IN (%s) OR blog_id IN (%s))", q, sqlf.Sprintf("SELECT id FROM blogs WHERE account_id = %s", accountID), sqlf.Sprintf("SELECT blog_id FROM account_follows WHERE account_id = %s", accountID)) if before != nil { - q = sqlf.Sprintf("%v AND id < %s", q, *before) + q = sqlf.Sprintf("%v AND p.id < %s", q, *before) } if after != nil { - q = sqlf.Sprintf("%v AND id > %s", q, *after) + q = sqlf.Sprintf("%v AND p.id > %s", q, *after) } q = sqlf.Sprintf("%v AND (visibility != %s OR (b.account_id = %s OR %s IN (%s)))", q, database.DirectVisibility, accountID, accountID, @@ -43,7 +47,7 @@ func (s *TimelineStore) Home(ctx context.Context, accountID ulid.ULID, limit int if limit <= 0 || limit > 100 { limit = 100 } - q = sqlf.Sprintf("%v ORDER BY id DESC LIMIT %d", q, limit) + q = sqlf.Sprintf("%v ORDER BY p.id DESC LIMIT %d", q, limit) return Select[TimelinePost](ctx, s.q, q) } diff --git a/web/api/blog.go b/web/api/blog.go index e2ee41e..523b791 100644 --- a/web/api/blog.go +++ b/web/api/blog.go @@ -12,13 +12,7 @@ type Blog struct { Domain *string `json:"domain"` Bio string `json:"bio"` - Account blogPartialAccount `json:"account"` -} - -type blogPartialAccount struct { - ID ulid.ULID `json:"id"` - Username string `json:"username"` - Domain *string `json:"domain"` + Account Account `json:"account"` } func DBBlogToBlog(b database.Blog, a database.Account) Blog { @@ -27,10 +21,6 @@ func DBBlogToBlog(b database.Blog, a database.Account) Blog { Name: b.Name, Domain: b.Domain, Bio: b.Bio, - Account: blogPartialAccount{ - ID: a.ID, - Username: a.Username, - Domain: a.Domain, - }, + Account: DBAccountToAccount(a), } } diff --git a/web/api/post.go b/web/api/post.go index e2d8a86..3fc8fac 100644 --- a/web/api/post.go +++ b/web/api/post.go @@ -11,25 +11,15 @@ type Post struct { Source *string `json:"source"` Visibility database.PostVisibility `json:"visibility"` - Blog postPartialBlog `json:"blog"` + Blog Blog `json:"blog"` } -type postPartialBlog struct { - ID ulid.ULID `json:"id"` - Name string `json:"name"` - Domain *string `json:"domain"` -} - -func DBPostToPost(p database.Post, b database.Blog) Post { +func DBPostToPost(p database.Post, b database.Blog, a database.Account) Post { return Post{ ID: p.ID, Content: p.Content, Source: p.Source, Visibility: p.Visibility, - Blog: postPartialBlog{ - ID: p.BlogID, - Name: b.Name, - Domain: b.Domain, - }, + Blog: DBBlogToBlog(b, a), } } diff --git a/web/api/posts/create_post.go b/web/api/posts/create_post.go index dd66b97..2a5b13e 100644 --- a/web/api/posts/create_post.go +++ b/web/api/posts/create_post.go @@ -75,6 +75,12 @@ func (app *App) Create(w http.ResponseWriter, r *http.Request) (api.Post, error) return api.Post{}, err } + acct, err := app.Account(conn).ByID(ctx, blog.AccountID) + if err != nil { + log.Err(err).Msg("fetching account") + return api.Post{}, err + } + if blog.AccountID != token.UserID { return api.Post{}, api.Error{Code: api.ErrNotYourObject} } @@ -87,5 +93,5 @@ func (app *App) Create(w http.ResponseWriter, r *http.Request) (api.Post, error) } // TODO: federate post + push to websockets - return api.DBPostToPost(post, blog), nil + return api.DBPostToPost(post, blog, acct), nil } diff --git a/web/api/posts/get_post.go b/web/api/posts/get_post.go index 06f8c09..bd748d3 100644 --- a/web/api/posts/get_post.go +++ b/web/api/posts/get_post.go @@ -40,5 +40,11 @@ func (app *App) GetID(w http.ResponseWriter, r *http.Request) (api.Post, error) return api.Post{}, err } - return api.DBPostToPost(post, blog), nil + acct, err := app.Account(conn).ByID(ctx, blog.AccountID) + if err != nil { + log.Err(err).Str("id", blog.AccountID.String()).Msg("fetching account from database") + return api.Post{}, err + } + + return api.DBPostToPost(post, blog, acct), nil } diff --git a/web/api/timelines/home_timeline.go b/web/api/timelines/home_timeline.go index 0f69bd0..5a5bb26 100644 --- a/web/api/timelines/home_timeline.go +++ b/web/api/timelines/home_timeline.go @@ -36,11 +36,12 @@ func (app *App) Home(w http.ResponseWriter, r *http.Request) (timelineResponse, posts, err := app.Timeline().Home(ctx, token.UserID, limit, before, after) if err != nil { log.Err(err).Msg("getting posts from database") + return timelineResponse{}, err } resp := timelineResponse{Posts: make([]api.Post, len(posts))} for i := range posts { - resp.Posts[i] = api.DBPostToPost(posts[i].Post, posts[i].Blog) + resp.Posts[i] = api.DBPostToPost(posts[i].Post, posts[i].Blog, posts[i].Account) } return resp, nil diff --git a/web/app/middleware.go b/web/app/middleware.go index 19126b9..f026be4 100644 --- a/web/app/middleware.go +++ b/web/app/middleware.go @@ -61,9 +61,11 @@ func (app *App) APIAuth(scope database.TokenScope, anonAccess bool) func(next ht render.JSON(w, r, api.Error{ Code: api.ErrInvalidToken, Message: api.ErrCodeMessage(api.ErrInvalidToken), + Details: "No token supplied", }) return } + header = cookie.Value } token, err := app.ParseToken(r.Context(), header) @@ -72,6 +74,7 @@ func (app *App) APIAuth(scope database.TokenScope, anonAccess bool) func(next ht render.JSON(w, r, api.Error{ Code: api.ErrInvalidToken, Message: api.ErrCodeMessage(api.ErrInvalidToken), + Details: "Could not parse token", }) return } @@ -81,6 +84,7 @@ func (app *App) APIAuth(scope database.TokenScope, anonAccess bool) func(next ht render.JSON(w, r, api.Error{ Code: api.ErrInvalidToken, Message: api.ErrCodeMessage(api.ErrInvalidToken), + Details: "Token is expired", }) return } diff --git a/web/frontend/app.html b/web/frontend/app.html index 39cb513..d2f6fa9 100644 --- a/web/frontend/app.html +++ b/web/frontend/app.html @@ -1,5 +1,5 @@ - +
@@ -7,7 +7,7 @@ {{.Vue.RenderTags}} - + diff --git a/web/frontend/frontend.go b/web/frontend/frontend.go index 2f856a6..c4741d2 100644 --- a/web/frontend/frontend.go +++ b/web/frontend/frontend.go @@ -40,8 +40,8 @@ func New(app *app.App) *Frontend { glue, err := vueglue.NewVueGlue(&vueglue.ViteConfig{ Environment: "development", AssetsPath: "frontend", - EntryPoint: "src/main.ts", - Platform: "svelte", + EntryPoint: "src/main.tsx", + Platform: "vue", FS: os.DirFS("frontend"), }) if err != nil { @@ -55,8 +55,8 @@ func New(app *app.App) *Frontend { Environment: "production", URLPrefix: "/assets/", AssetsPath: "dist", - EntryPoint: "src/main.ts", - Platform: "svelte", + EntryPoint: "src/main.tsx", + Platform: "vue", FS: frontend.Embed, }) if err != nil {