Always update snippet tags on every Discord edit

This commit is contained in:
Ben Visness 2021-12-08 23:00:44 -06:00
parent 5344e9d4bc
commit 4d63d02533
6 changed files with 110 additions and 70 deletions

View File

@ -7,7 +7,7 @@ fi
. cinera.conf . cinera.conf
if [ ! -d $CINERA_HMML_PATH ]; then if [ ! -d $CINERA_HMML_PATH ]; then
git clone --config core.sshCommand="ssh -i ~/.ssh/gitlab-hmml" git@gitssh.handmade.network:Annotation-Pushers/cinera_handmade.network.git $CINERA_HMML_PATH git clone --config core.sshCommand="ssh -i ~/.ssh/gitlab-hmml" git@git.handmade.network:Annotation-Pushers/cinera_handmade.network.git $CINERA_HMML_PATH
fi fi
if [ ! -d $CINERA_HMML_PATH ]; then if [ ! -d $CINERA_HMML_PATH ]; then

View File

@ -7,7 +7,7 @@ fi
. cinera.conf . cinera.conf
if [ ! -d $CINERA_REPO_PATH ]; then if [ ! -d $CINERA_REPO_PATH ]; then
git clone --config core.sshCommand="ssh -i ~/.ssh/gitlab-annotation-system" git@gitssh.handmade.network:Annotation-Pushers/Annotation-System.git $CINERA_REPO_PATH git clone --config core.sshCommand="ssh -i ~/.ssh/gitlab-annotation-system" git@git.handmade.network:Annotation-Pushers/Annotation-System.git $CINERA_REPO_PATH
fi fi
if [ ! -d $CINERA_REPO_PATH ]; then if [ ! -d $CINERA_REPO_PATH ]; then

View File

@ -176,7 +176,7 @@ if [ $checkpoint -lt 82 ]; then
do_as hmn <<'SCRIPT' do_as hmn <<'SCRIPT'
set -euxo pipefail set -euxo pipefail
if ! ssh -T -i ~/.ssh/gitlab-hmn git@gitssh.handmade.network; then if ! ssh -T -i ~/.ssh/gitlab-hmn git@git.handmade.network; then
set +x set +x
echo "Copy the following key:" echo "Copy the following key:"
@ -192,7 +192,7 @@ if [ $checkpoint -lt 82 ]; then
SCRIPT SCRIPT
do_as annotations <<'SCRIPT' do_as annotations <<'SCRIPT'
if ! ssh -T -i ~/.ssh/gitlab-annotation-system git@gitssh.handmade.network; then if ! ssh -T -i ~/.ssh/gitlab-annotation-system git@git.handmade.network; then
set +x set +x
echo "Copy the following key:" echo "Copy the following key:"
@ -206,7 +206,7 @@ SCRIPT
exit 1 exit 1
fi fi
if ! ssh -T -i ~/.ssh/gitlab-hmml git@gitssh.handmade.network; then if ! ssh -T -i ~/.ssh/gitlab-hmml git@git.handmade.network; then
set +x set +x
echo "Copy the following key:" echo "Copy the following key:"
@ -230,7 +230,7 @@ if [ $checkpoint -lt 90 ]; then
set -euxo pipefail set -euxo pipefail
cd ~ cd ~
git clone git@gitssh.handmade.network:hmn/hmn.git git clone git@git.handmade.network:hmn/hmn.git
SCRIPT SCRIPT
savecheckpoint 90 savecheckpoint 90

View File

@ -200,7 +200,7 @@ func handleHistoryMessage(ctx context.Context, dbConn *pgxpool.Pool, msg *Messag
} }
if createSnippets { if createSnippets {
if doSnippet, err := AllowedToCreateMessageSnippet(ctx, tx, newMsg.UserID); doSnippet && err == nil { if doSnippet, err := AllowedToCreateMessageSnippet(ctx, tx, newMsg.UserID); doSnippet && err == nil {
_, err := CreateMessageSnippet(ctx, tx, msg.ID) _, err := CreateMessageSnippet(ctx, tx, newMsg.UserID, msg.ID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -26,7 +26,6 @@ var reDiscordMessageLink = regexp.MustCompile(`https?://.+?(\s|$)`)
var errNotEnoughInfo = errors.New("Discord didn't send enough info in this event for us to do this") var errNotEnoughInfo = errors.New("Discord didn't send enough info in this event for us to do this")
// TODO: Turn this ad-hoc isJam parameter into a tag or something
func (bot *botInstance) processShowcaseMsg(ctx context.Context, msg *Message) error { func (bot *botInstance) processShowcaseMsg(ctx context.Context, msg *Message) error {
switch msg.Type { switch msg.Type {
case MessageTypeDefault, MessageTypeReply, MessageTypeApplicationCommand: case MessageTypeDefault, MessageTypeReply, MessageTypeApplicationCommand:
@ -59,67 +58,10 @@ func (bot *botInstance) processShowcaseMsg(ctx context.Context, msg *Message) er
return err return err
} }
if doSnippet, err := AllowedToCreateMessageSnippet(ctx, tx, newMsg.UserID); doSnippet && err == nil { if doSnippet, err := AllowedToCreateMessageSnippet(ctx, tx, newMsg.UserID); doSnippet && err == nil {
snippet, err := CreateMessageSnippet(ctx, tx, msg.ID) _, err := CreateMessageSnippet(ctx, tx, newMsg.UserID, msg.ID)
if err != nil { if err != nil {
return oops.New(err, "failed to create snippet in gateway") return oops.New(err, "failed to create snippet in gateway")
} }
u, err := FetchDiscordUser(ctx, bot.dbConn, newMsg.UserID)
if err != nil {
return oops.New(err, "failed to look up HMN user information from Discord user")
// we shouldn't see a "not found" here because of the AllowedToBlahBlahBlah check.
}
projects, err := hmndata.FetchProjects(ctx, bot.dbConn, &u.HMNUser, hmndata.ProjectsQuery{
OwnerIDs: []int{u.HMNUser.ID},
})
if err != nil {
return oops.New(err, "failed to look up user projects")
}
projectIDs := make([]int, len(projects))
for i, p := range projects {
projectIDs[i] = p.Project.ID
}
// Try to associate tags in the message with project tags in HMN.
// Match only tags for projects in which the current user is a collaborator.
messageTags := getDiscordTags(msg.Content)
type tagsRow struct {
Tag models.Tag `db:"tags"`
}
itUserTags, err := db.Query(ctx, tx, tagsRow{},
`
SELECT $columns
FROM
tags
JOIN handmade_project AS project ON project.tag = tags.id
JOIN handmade_user_projects AS user_project ON user_project.project_id = project.id
WHERE
project.id = ANY ($1)
`,
projectIDs,
)
if err != nil {
return oops.New(err, "failed to fetch tags for user projects")
}
iUserTags := itUserTags.ToSlice()
var tagIDs []int
for _, itag := range iUserTags {
tag := itag.(*tagsRow).Tag
for _, messageTag := range messageTags {
if tag.Text == messageTag {
tagIDs = append(tagIDs, tag.ID)
}
}
}
for _, tagID := range tagIDs {
_, err = tx.Exec(ctx, `INSERT INTO snippet_tags (snippet_id, tag_id) VALUES ($1, $2)`, snippet.ID, tagID)
if err != nil {
return oops.New(err, "failed to add tag to snippet")
}
}
} else if err != nil { } else if err != nil {
return oops.New(err, "failed to check snippet permissions in gateway") return oops.New(err, "failed to check snippet permissions in gateway")
} }
@ -612,7 +554,14 @@ any content saved, nothing will happen.
Does not check user preferences around snippets. Does not check user preferences around snippets.
*/ */
func CreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, msgID string) (*models.Snippet, error) { func CreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, userID, msgID string) (snippet *models.Snippet, err error) {
defer func() {
err := updateSnippetTags(ctx, tx, userID, snippet)
if err != nil {
logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to update tags for Discord snippet")
}
}()
// Check for existing snippet, maybe return it // Check for existing snippet, maybe return it
type existingSnippetResult struct { type existingSnippetResult struct {
Message models.DiscordMessage `db:"msg"` Message models.DiscordMessage `db:"msg"`
@ -644,13 +593,14 @@ func CreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, msgID string) (*m
contentMarkdown := existing.MessageContent.LastContent contentMarkdown := existing.MessageContent.LastContent
contentHTML := parsing.ParseMarkdown(contentMarkdown, parsing.DiscordMarkdown) contentHTML := parsing.ParseMarkdown(contentMarkdown, parsing.DiscordMarkdown)
_, err := tx.Exec(ctx, iSnippet, err := db.QueryOne(ctx, tx, models.Snippet{},
` `
UPDATE handmade_snippet UPDATE handmade_snippet
SET SET
description = $1, description = $1,
_description_html = $2 _description_html = $2
WHERE id = $3 WHERE id = $3
RETURNING $columns
`, `,
contentMarkdown, contentMarkdown,
contentHTML, contentHTML,
@ -659,8 +609,10 @@ func CreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, msgID string) (*m
if err != nil { if err != nil {
logging.ExtractLogger(ctx).Warn().Err(err).Msg("failed to update content of snippet on message edit") logging.ExtractLogger(ctx).Warn().Err(err).Msg("failed to update content of snippet on message edit")
} }
return iSnippet.(*models.Snippet), nil
} else {
return existing.Snippet, nil
} }
return existing.Snippet, nil
} }
if existing.Message.SnippetCreated { if existing.Message.SnippetCreated {
@ -716,6 +668,94 @@ func CreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, msgID string) (*m
return isnippet.(*models.Snippet), nil return isnippet.(*models.Snippet), nil
} }
/*
Associates any Discord tags with website tags. Idempotent; will clear
out any existing tags and then add new ones.
*/
func updateSnippetTags(ctx context.Context, dbConn db.ConnOrTx, userID string, snippet *models.Snippet) error {
tx, err := dbConn.Begin(ctx)
if err != nil {
return oops.New(err, "failed to start transaction")
}
defer tx.Rollback(ctx)
u, err := FetchDiscordUser(ctx, tx, userID)
if err != nil {
return oops.New(err, "failed to look up HMN user information from Discord user")
// we shouldn't see a "not found" here because of the AllowedToBlahBlahBlah check earlier in the process
}
projects, err := hmndata.FetchProjects(ctx, tx, &u.HMNUser, hmndata.ProjectsQuery{
OwnerIDs: []int{u.HMNUser.ID},
})
if err != nil {
return oops.New(err, "failed to look up user projects")
}
projectIDs := make([]int, len(projects))
for i, p := range projects {
projectIDs[i] = p.Project.ID
}
// Delete any existing tags for this snippet
_, err = tx.Exec(ctx,
`
DELETE FROM snippet_tags
WHERE snippet_id = $1
`,
snippet.ID,
)
if err != nil {
return oops.New(err, "failed to delete existing snippet tags")
}
// Try to associate tags in the message with project tags in HMN.
// Match only tags for projects in which the current user is a collaborator.
messageTags := getDiscordTags(snippet.Description)
type tagsRow struct {
Tag models.Tag `db:"tags"`
}
itUserTags, err := db.Query(ctx, tx, tagsRow{},
`
SELECT $columns
FROM
tags
JOIN handmade_project AS project ON project.tag = tags.id
JOIN handmade_user_projects AS user_project ON user_project.project_id = project.id
WHERE
project.id = ANY ($1)
`,
projectIDs,
)
if err != nil {
return oops.New(err, "failed to fetch tags for user projects")
}
iUserTags := itUserTags.ToSlice()
var tagIDs []int
for _, itag := range iUserTags {
tag := itag.(*tagsRow).Tag
for _, messageTag := range messageTags {
if tag.Text == messageTag {
tagIDs = append(tagIDs, tag.ID)
}
}
}
for _, tagID := range tagIDs {
_, err = tx.Exec(ctx, `INSERT INTO snippet_tags (snippet_id, tag_id) VALUES ($1, $2)`, snippet.ID, tagID)
if err != nil {
return oops.New(err, "failed to add tag to snippet")
}
}
err = tx.Commit(ctx)
if err != nil {
return oops.New(err, "failed to commit transaction")
}
return nil
}
// NOTE(ben): This is maybe redundant with the regexes we use for markdown. But // NOTE(ben): This is maybe redundant with the regexes we use for markdown. But
// do we actually want to reuse those, or should we keep them separate? // do we actually want to reuse those, or should we keep them separate?
var RESnippetableUrl = regexp.MustCompile(`^https?://(youtu\.be|(www\.)?youtube\.com/watch)`) var RESnippetableUrl = regexp.MustCompile(`^https?://(youtu\.be|(www\.)?youtube\.com/watch)`)

View File

@ -173,7 +173,7 @@ func DiscordShowcaseBacklog(c *RequestContext) ResponseData {
for _, imsgId := range msgIds { for _, imsgId := range msgIds {
msgId := imsgId.(*messageIdQuery) msgId := imsgId.(*messageIdQuery)
_, err := discord.CreateMessageSnippet(c.Context(), c.Conn, msgId.MessageID) _, err := discord.CreateMessageSnippet(c.Context(), c.Conn, duser.UserID, msgId.MessageID)
if err != nil { if err != nil {
c.Logger.Warn().Err(err).Msg("failed to create snippet from showcase backlog") c.Logger.Warn().Err(err).Msg("failed to create snippet from showcase backlog")
continue continue