Get forum post creation working

This commit is contained in:
Ben Visness 2021-07-04 17:48:08 -05:00
parent de0b7a08fb
commit c1785d79a4
5 changed files with 206 additions and 46 deletions

View File

@ -0,0 +1,45 @@
package migrations
import (
"context"
"time"
"git.handmade.network/hmn/hmn/src/migration/types"
"git.handmade.network/hmn/hmn/src/oops"
"github.com/jackc/pgx/v4"
)
func init() {
registerMigration(DropThreadFields{})
}
type DropThreadFields struct{}
func (m DropThreadFields) Version() types.MigrationVersion {
return types.MigrationVersion(time.Date(2021, 7, 4, 21, 36, 58, 0, time.UTC))
}
func (m DropThreadFields) Name() string {
return "DropThreadFields"
}
func (m DropThreadFields) Description() string {
return "Drop unnecessary thread fields"
}
func (m DropThreadFields) Up(ctx context.Context, tx pgx.Tx) error {
_, err := tx.Exec(ctx, `
ALTER TABLE handmade_thread
DROP hits,
DROP reply_count;
`)
if err != nil {
return oops.New(err, "failed to drop thread fields")
}
return nil
}
func (m DropThreadFields) Down(ctx context.Context, tx pgx.Tx) error {
panic("Implement me")
}

View File

@ -0,0 +1,69 @@
package migrations
import (
"context"
"time"
"git.handmade.network/hmn/hmn/src/migration/types"
"git.handmade.network/hmn/hmn/src/oops"
"github.com/jackc/pgx/v4"
)
func init() {
registerMigration(FixPostConstraints{})
}
type FixPostConstraints struct{}
func (m FixPostConstraints) Version() types.MigrationVersion {
return types.MigrationVersion(time.Date(2021, 7, 4, 22, 2, 28, 0, time.UTC))
}
func (m FixPostConstraints) Name() string {
return "FixPostConstraints"
}
func (m FixPostConstraints) Description() string {
return "Update post-related constraints to make insertion sane"
}
func (m FixPostConstraints) Up(ctx context.Context, tx pgx.Tx) error {
_, err := tx.Exec(ctx, `
ALTER TABLE handmade_thread
ALTER locked SET DEFAULT FALSE,
ALTER first_id SET NOT NULL,
ALTER last_id SET NOT NULL;
`)
if err != nil {
return oops.New(err, "failed to update thread constraints")
}
_, err = tx.Exec(ctx, `
ALTER TABLE handmade_post
ALTER deleted SET DEFAULT FALSE,
ALTER readonly SET DEFAULT FALSE,
ALTER CONSTRAINT handmade_post_current_id_fkey DEFERRABLE INITIALLY DEFERRED;
`)
if err != nil {
return oops.New(err, "failed to update project constraints")
}
_, err = tx.Exec(ctx, `
CREATE SEQUENCE handmade_postversion_id_seq
START WITH 40000 -- this is well out of the way of existing IDs
OWNED BY handmade_postversion.id;
ALTER TABLE handmade_postversion
ALTER id SET DEFAULT nextval('handmade_postversion_id_seq'),
ALTER CONSTRAINT handmade_postversion_post_id_fkey DEFERRABLE INITIALLY DEFERRED;
`)
if err != nil {
return oops.New(err, "failed to update postversion constraints")
}
return nil
}
func (m FixPostConstraints) Down(ctx context.Context, tx pgx.Tx) error {
panic("Implement me")
}

View File

@ -5,12 +5,10 @@ type Thread struct {
CategoryID int `db:"category_id"` CategoryID int `db:"category_id"`
Title string `db:"title"` Title string `db:"title"`
Hits int `db:"hits"` Sticky bool `db:"sticky"`
ReplyCount int `db:"reply_count"` Locked bool `db:"locked"`
Sticky bool `db:"sticky"` Deleted bool `db:"deleted"`
Locked bool `db:"locked"`
Deleted bool `db:"deleted"`
FirstID *int `db:"first_id"` FirstID *int `db:"first_id"`
LastID *int `db:"last_id"` LastID *int `db:"last_id"`

View File

@ -46,33 +46,29 @@
<div class="flex flex-row-reverse justify-start mt2"> <div class="flex flex-row-reverse justify-start mt2">
<input type="submit" class="button ml2" name="submit" value="{{ .SubmitLabel }}" /> <input type="submit" class="button ml2" name="submit" value="{{ .SubmitLabel }}" />
<input type="submit" class="button ml2" name="preview" value="{{ .PreviewLabel }}" />
</div> </div>
<div class="post post-preview mv3 mathjax"> <div class="post post-preview mv3 mathjax">
<div id="preview" class="body contents"></div> <div id="preview" class="body contents"></div>
</div> </div>
{{/* {{ if .IsEditing }}
{% if are_editing %}
<span class="editreason"> <span class="editreason">
<label for="editreason">Edit reason:</label> <label for="editreason">Edit reason:</label>
<input name="editreason" maxlength="255" type="text" id="editreason" value="{% if preview_edit_reason|length > 0 %}{{preview_edit_reason}}{% endif %}"/> <input name="editreason" maxlength="255" type="text" id="editreason" />
</span> </span>
{% endif %} {{ end }}
{{/* TODO: Sticky threads
{% if user.is_staff and post and post.depth == 0 %} {% if user.is_staff and post and post.depth == 0 %}
<div class="checkbox sticky"> <div class="checkbox sticky">
<input type="checkbox" name="sticky" id="sticky" {% if thread.sticky %}checked{% endif%} /> <input type="checkbox" name="sticky" id="sticky" {% if thread.sticky %}checked{% endif%} />
<label for="sticky">Sticky thread</label> <label for="sticky">Sticky thread</label>
</div> </div>
{% endif %} {% endif %}
*/}}
{{/*
{% if are_previewing %}
{% include "edit_preview.html" %}
{% endif %}
{% if context_reply_to %} {% if context_reply_to %}
<h4>The post you're replying to:</h4> <h4>The post you're replying to:</h4>

View File

@ -3,6 +3,7 @@ package website
import ( import (
"errors" "errors"
"math" "math"
"net"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -518,18 +519,14 @@ func ForumPostRedirect(c *RequestContext) ResponseData {
type editorData struct { type editorData struct {
templates.BaseData templates.BaseData
SubmitUrl string SubmitUrl string
PostTitle string PostTitle string
PostBody string PostBody string
SubmitLabel string SubmitLabel string
PreviewLabel string IsEditing bool // false if new post, true if updating existing one
} }
func ForumNewThread(c *RequestContext) ResponseData { func ForumNewThread(c *RequestContext) ResponseData {
if c.Req.Method == http.MethodPost {
// TODO: Get preview data
}
baseData := getBaseData(c) baseData := getBaseData(c)
baseData.Title = "Create New Thread" baseData.Title = "Create New Thread"
baseData.MathjaxEnabled = true baseData.MathjaxEnabled = true
@ -547,19 +544,10 @@ func ForumNewThread(c *RequestContext) ResponseData {
var res ResponseData var res ResponseData
err := res.WriteTemplate("editor.html", editorData{ err := res.WriteTemplate("editor.html", editorData{
BaseData: baseData, BaseData: baseData,
SubmitUrl: hmnurl.BuildForumNewThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), true), SubmitUrl: hmnurl.BuildForumNewThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), true),
SubmitLabel: "Post New Thread", SubmitLabel: "Post New Thread",
PreviewLabel: "Preview",
}, c.Perf) }, c.Perf)
// err := res.WriteTemplate("forum_thread.html", forumThreadData{
// BaseData: baseData,
// Thread: templates.ThreadToTemplate(&thread),
// Posts: posts,
// CategoryUrl: hmnurl.BuildForumCategory(c.CurrentProject.Slug, currentSubforumSlugs, 1),
// ReplyUrl: hmnurl.BuildForumPostReply(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, *thread.FirstID),
// Pagination: pagination,
// }, c.Perf)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -572,6 +560,7 @@ func ForumNewThreadSubmit(c *RequestContext) ResponseData {
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer tx.Rollback(c.Context())
c.Perf.StartBlock("SQL", "Fetch category tree") c.Perf.StartBlock("SQL", "Fetch category tree")
categoryTree := models.GetFullCategoryTree(c.Context(), c.Conn) categoryTree := models.GetFullCategoryTree(c.Context(), c.Conn)
@ -593,41 +582,104 @@ func ForumNewThreadSubmit(c *RequestContext) ResponseData {
} }
parsed := parsing.ParsePostInput(unparsed, false) parsed := parsing.ParsePostInput(unparsed, false)
now := time.Now()
ip := net.ParseIP(c.Req.RemoteAddr)
// Create thread // Create thread
var threadId int var threadId int
err = tx.QueryRow(c.Context(), err = tx.QueryRow(c.Context(),
` `
INSERT INTO handmade_thread (title, sticky, locked, category_id) INSERT INTO handmade_thread (title, sticky, category_id, first_id, last_id)
VALUES ($1, $2, $3, $4, $5)
RETURNING id RETURNING id
`, `,
title, title,
sticky, sticky,
false,
currentCatId, currentCatId,
-1,
-1,
).Scan(&threadId) ).Scan(&threadId)
if err != nil { if err != nil {
panic(oops.New(err, "failed to create thread")) panic(oops.New(err, "failed to create thread"))
} }
// Create post version // Create post
_, err = tx.Exec(c.Context(), var postId int
err = tx.QueryRow(c.Context(),
` `
INSERT INTO handmade_postversion (post_id, text_raw, text_parsed) INSERT INTO handmade_post (postdate, category_id, thread_id, preview, current_id, author_id, category_kind, project_id)
VALUES ($1, $2, $3) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id
`, `,
// TODO: post id now,
currentCatId,
threadId,
"lol", // TODO: Actual previews
-1,
c.CurrentUser.ID,
models.CatKindForum,
c.CurrentProject.ID,
).Scan(&postId)
if err != nil {
panic(oops.New(err, "failed to create post"))
}
// Create post version
var versionId int
err = tx.QueryRow(c.Context(),
`
INSERT INTO handmade_postversion (post_id, text_raw, text_parsed, ip, date)
VALUES ($1, $2, $3, $4, $5)
RETURNING id
`,
postId,
unparsed, unparsed,
parsed, parsed,
ip,
now,
).Scan(&versionId)
if err != nil {
panic(oops.New(err, "failed to create post version"))
}
// Update post with version id
_, err = tx.Exec(c.Context(),
`
UPDATE handmade_post
SET current_id = $1
WHERE id = $2
`,
versionId,
postId,
) )
if err != nil {
panic(oops.New(err, "failed to set current post version"))
}
// Update thread with post id
_, err = tx.Exec(c.Context(),
`
UPDATE handmade_thread
SET
first_id = $1,
last_id = $1
WHERE id = $2
`,
postId,
threadId,
)
if err != nil {
panic(oops.New(err, "failed to set thread post ids"))
}
err = tx.Commit(c.Context()) err = tx.Commit(c.Context())
if err != nil { if err != nil {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to create new forum thread")) return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to create new forum thread"))
} }
// TODO: Redirect to newly created thread newThreadUrl := hmnurl.BuildForumThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), threadId, title, 1)
return c.Redirect(hmnurl.BuildForumNewThread(models.HMNProjectSlug, nil, false), http.StatusSeeOther) return c.Redirect(newThreadUrl, http.StatusSeeOther)
} }
func validateSubforums(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, catPath string) (int, bool) { func validateSubforums(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, catPath string) (int, bool) {