Skip to content

Commit

Permalink
Merge pull request #71 from sgtm-club/dev/moul/relationships
Browse files Browse the repository at this point in the history
  • Loading branch information
moul authored Nov 12, 2020
2 parents af93f01 + f8de419 commit f8e5a2e
Show file tree
Hide file tree
Showing 8 changed files with 875 additions and 351 deletions.
57 changes: 53 additions & 4 deletions api/sgtm.proto
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ message Me {
*/

message User {
/// model base

int64 id = 1 [(go.field) = {name: 'ID', tags: 'gorm:"primary_key"'}];
int64 created_at = 2 [(go.field) = {tags: 'gorm:"autocreatetime:nano"'}];
int64 updated_at = 3 [(go.field) = {tags: 'gorm:"autoupdatetime:nano"'}];
int64 deleted_at = 4;

/// fields

string email = 10 [(go.field) = {tags: 'gorm:"size:255;not null;index:,unique"'}];
string slug = 11 [(go.field) = {tags: 'gorm:"size:32;not null;default:\'\'"'}];
string firstname = 12 [(go.field) = {tags: 'gorm:"size:255;not null;default:\'\'"'}];
Expand All @@ -114,10 +118,14 @@ message User {
string role = 29;
int64 processing_version = 30;
string processing_error = 31;

repeated Post recent_posts = 50 [(go.field) = {tags: 'gorm:"foreignkey:AuthorID;PRELOAD:false"'}];
// timezone
// location

/// relationships

repeated Post recent_posts = 50 [(go.field) = {tags: 'gorm:"foreignkey:AuthorID;PRELOAD:false"'}];
repeated Relationship relationships_as_source = 51 [(go.field) = {tags: 'gorm:"foreignKey:SourceUserID"'}];
repeated Relationship relationships_as_target = 52 [(go.field) = {tags: 'gorm:"foreignKey:TargetUserID"'}];
}

message Post {
Expand Down Expand Up @@ -166,25 +174,33 @@ message Post {
string tags = 51; // comma separated list of tags
string lyrics = 52;

// soundcloud post
/// soundcloud post

string soundcloud_secret_token = 80 [(go.field) = {name: 'SoundCloudSecretToken'}];
uint64 soundcloud_id = 81 [(go.field) = {name: 'SoundCloudID'}];
SoundCloudKind soundcloud_kind = 83 [(go.field) = {name: 'SoundCloudKind'}];

// ipfs post
/// ipfs post

string ipfs_cid = 90 [(go.field) = {name: 'IPFSCID'}];
string mime_type = 91 [(go.field) = {name: 'MIMEType'}];
int64 size_bytes = 92;
string file_extension = 93;
string attachment_filename = 94;

/// tracking activities

int64 target_user_id = 101 [(go.field) = {name: 'TargetUserID'}];
User target_user = 102;
int64 target_post_id = 103 [(go.field) = {name: 'TargetPostID'}];
Post target_post = 104;
string target_metadata = 105;

/// relationships

repeated Relationship relationships_as_source = 110 [(go.field) = {tags: 'gorm:"foreignKey:SourcePostID"'}];
repeated Relationship relationships_as_target = 111 [(go.field) = {tags: 'gorm:"foreignKey:TargetPostID"'}];

enum SoundCloudKind {
UnknownSoundCloudKind = 0;
SoundCloudTrack = 1;
Expand Down Expand Up @@ -212,6 +228,39 @@ message Post {
}
}

message Relationship {
/// model base

int64 id = 1 [(go.field) = {name: 'ID', tags: 'gorm:"primary_key"'}];
int64 created_at = 2 [(go.field) = {tags: 'gorm:"autocreatetime:nano"'}];
int64 updated_at = 3 [(go.field) = {tags: 'gorm:"autoupdatetime:nano"'}];
int64 deleted_at = 4;

/// relationship fields

Kind kind = 10;
int64 source_post_id = 11 [(go.field) = {name: 'SourcePostID'}];
Post source_post = 12;
int64 target_post_id = 13 [(go.field) = {name: 'TargetPostID'}];
Post target_post = 14;
int64 source_user_id = 15 [(go.field) = {name: 'SourceUserID'}];
User source_user = 16;
int64 target_user_id = 17 [(go.field) = {name: 'TargetUserID'}];
User target_user = 18;
string source_raw = 19;
string target_raw = 20;
string metadata = 21;

enum Kind {
UnknownKind = 0;
FeaturingUserKind = 1;
RemixOfTrackKind = 2;
NewVersionOfTrackKind = 3;
InspiredByTrackKind = 4;
RemixOfUserKind = 5;
}
}

/// Common enums

enum Visibility {
Expand Down
2 changes: 1 addition & 1 deletion gen.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/sgtm/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func DBInit(db *gorm.DB, sfn *snowflake.Node) (*gorm.DB, error) {
err = db.AutoMigrate(
&sgtmpb.User{},
&sgtmpb.Post{},
&sgtmpb.Relationship{},
)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion pkg/sgtm/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (svc *Service) httpServer() (*http.Server, error) {
r.Post("/post/{post_slug}", svc.postPage(srcBox))
r.Get("/post/{post_slug}/edit", svc.postEditPage(srcBox))
r.Post("/post/{post_slug}/edit", svc.postEditPage(srcBox))
r.Get("/post/{post_slug}/sync", svc.postSyncPage(srcBox))
r.Get("/post/{post_slug}/maintenance", svc.postMaintenancePage(srcBox))
r.Get("/post/{post_slug}/download", svc.postDownloadPage(srcBox))
// FIXME: r.Use(ModeratorOnly) + r.Get("/moderator")
// FIXME: r.Use(AdminOnly) + r.Get("/admin")
Expand Down
8 changes: 8 additions & 0 deletions pkg/sgtm/page_base.tmpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,14 @@ <h4 class="text-white">@{{.User.Slug}}</h4>
{{end}}
{{end}}

{{define "user_link_with_pict_and_name"}}
{{if .}}
<a href="{{.CanonicalURL}}"><img src="{{.Avatar}}" width="20" height="20" /> {{.DisplayName}}</a>
{{else}}
<b>Anonymous</b>
{{end}}
{{end}}

{{define "navbar_brand"}}
<a href="/" class="navbar-brand d-flex align-items-center">
{{ if .Opts.DevMode }}
Expand Down
127 changes: 109 additions & 18 deletions pkg/sgtm/page_post.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ package sgtm
import (
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"time"

"github.com/go-chi/chi"
packr "github.com/gobuffalo/packr/v2"
"go.uber.org/zap"
"gorm.io/gorm"
"moul.io/godev"
"moul.io/sgtm/pkg/sgtmpb"
)

var (
featRegex = regexp.MustCompile(`(?im)(feat.|feat|featuring|features)\s*[:= ]\s*@([^\s,]+)\s*`)
)

func (svc *Service) postPage(box *packr.Box) func(w http.ResponseWriter, r *http.Request) {
tmpl := loadTemplates(box, "base.tmpl.html", "post.tmpl.html")

Expand All @@ -27,7 +33,14 @@ func (svc *Service) postPage(box *packr.Box) func(w http.ResponseWriter, r *http
// custom
data.PageKind = "post"
postSlug := chi.URLParam(r, "post_slug")
query := svc.rodb().Preload("Author")
query := svc.rodb().
Preload("Author").
Preload("RelationshipsAsSource").
Preload("RelationshipsAsSource.TargetPost").
Preload("RelationshipsAsSource.TargetUser").
Preload("RelationshipsAsTarget").
Preload("RelationshipsAsTarget.SourcePost").
Preload("RelationshipsAsTarget.SourceUser")
id, err := strconv.ParseInt(postSlug, 10, 64)
if err == nil {
query = query.Where(sgtmpb.Post{ID: id, Kind: sgtmpb.Post_TrackKind})
Expand Down Expand Up @@ -118,9 +131,22 @@ func (svc *Service) postPage(box *packr.Box) func(w http.ResponseWriter, r *http
}
}

func (svc *Service) postSyncPage(box *packr.Box) func(w http.ResponseWriter, r *http.Request) {
func (svc *Service) postMaintenancePage(box *packr.Box) func(w http.ResponseWriter, r *http.Request) {
tmpl := loadTemplates(box, "base.tmpl.html", "dummy.tmpl.html")
return func(w http.ResponseWriter, r *http.Request) {
var (
shouldExtractBpm = r.URL.Query().Get("extract_bpm") == "1"
shouldDetectRelationships = r.URL.Query().Get("detect_relationships") == "1"
shouldResyncSoundCloud = r.URL.Query().Get("resync_soundcloud") == "1"
shouldDL = shouldExtractBpm
shouldDoSomething = shouldExtractBpm || shouldDetectRelationships || shouldResyncSoundCloud
)
if !shouldDoSomething {
svc.error404Page(box)(w, r)
return
}

// common init
started := time.Now()
data, err := svc.newTemplateData(w, r)
if err != nil {
Expand All @@ -133,7 +159,10 @@ func (svc *Service) postSyncPage(box *packr.Box) func(w http.ResponseWriter, r *
return
}
postSlug := chi.URLParam(r, "post_slug")
query := svc.rodb().Preload("Author")
query := svc.rodb().
Preload("Author").
Preload("RelationshipsAsSource").
Preload("RelationshipsAsTarget")
id, err := strconv.ParseInt(postSlug, 10, 64)
if err == nil {
query = query.Where(sgtmpb.Post{ID: id, Kind: sgtmpb.Post_TrackKind})
Expand All @@ -150,22 +179,79 @@ func (svc *Service) postSyncPage(box *packr.Box) func(w http.ResponseWriter, r *
return
}

// FIXME: do the sync here
dl, err := DownloadPost(&post, false)
if err != nil {
svc.errRenderHTML(w, r, err, http.StatusUnprocessableEntity)
return
// dl file
var dl *Download
if shouldDL {
var err error
dl, err = DownloadPost(&post, false)
if err != nil {
svc.errRenderHTML(w, r, err, http.StatusUnprocessableEntity)
return
}
svc.logger.Debug("file downloaded", zap.String("path", dl.Path))
}
svc.logger.Debug("file downloaded", zap.String("path", dl.Path))
bpm, err := ExtractBPM(dl.Path)
if err != nil {
svc.errRenderHTML(w, r, err, http.StatusUnprocessableEntity)

// resync soundcloud
if shouldResyncSoundCloud {
svc.error404Page(box)(w, r)
return
}
svc.logger.Debug("BPM extracted", zap.Float64("bpm", bpm))
if err := svc.rwdb().Model(&post).Update("bpm", bpm).Error; err != nil {
svc.errRenderHTML(w, r, err, http.StatusUnprocessableEntity)
return

// extract bpm
if shouldExtractBpm {
bpm, err := ExtractBPM(dl.Path)
if err != nil {
svc.errRenderHTML(w, r, err, http.StatusUnprocessableEntity)
return
}
svc.logger.Debug("BPM extracted", zap.Float64("bpm", bpm))
if err := svc.rwdb().Model(&post).Update("bpm", bpm).Error; err != nil {
svc.errRenderHTML(w, r, err, http.StatusUnprocessableEntity)
return
}
}

if shouldDetectRelationships {
// FIXME: support more relationship kinds

err := svc.rwdb().Transaction(func(tx *gorm.DB) error {
// FIXME: avoid delete/recreate associations if they didn't changed

body := post.Title + "\n\n" + post.SafeDescription()

if err := tx.Model(&post).Association("RelationshipsAsSource").Clear(); err != nil {
return err
}
if err := tx.Model(&post).Association("RelationshipsAsTarget").Clear(); err != nil {
return err
}

for _, match := range featRegex.FindAllStringSubmatch(body, -1) {
target := strings.ToLower(strings.TrimSpace(match[len(match)-1]))
var user sgtmpb.User
err := svc.rodb().
Where("LOWER(slug) = ?", target).
First(&user).
Error
if err != nil {
svc.logger.Debug("cannot find the featured artist in DB", zap.Error(err))
continue
}

if err := tx.Model(&post).Association("RelationshipsAsSource").Append(&sgtmpb.Relationship{
SourcePostID: post.ID,
TargetUserID: user.ID,
Kind: sgtmpb.Relationship_FeaturingUserKind,
}); err != nil {
return err
}
}
return nil
})
if err != nil {
svc.errRenderHTML(w, r, err, http.StatusUnprocessableEntity)
return
}
}

switch r.URL.Query().Get("return") {
Expand Down Expand Up @@ -211,7 +297,10 @@ func (svc *Service) postEditPage(box *packr.Box) func(w http.ResponseWriter, r *
// fetch post from db
{
postSlug := chi.URLParam(r, "post_slug")
query := svc.rodb().Preload("Author")
query := svc.rodb().
Preload("Author").
Preload("RelationshipsAsSource").
Preload("RelationshipsAsTarget")
id, err := strconv.ParseInt(postSlug, 10, 64)
if err == nil {
query = query.Where(sgtmpb.Post{ID: id, Kind: sgtmpb.Post_TrackKind})
Expand Down Expand Up @@ -277,7 +366,9 @@ func (svc *Service) postEditPage(box *packr.Box) func(w http.ResponseWriter, r *
func (svc *Service) postDownloadPage(box *packr.Box) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
postSlug := chi.URLParam(r, "post_slug")
query := svc.rodb().Preload("Author")
query := svc.rodb().
Preload("Author")

id, err := strconv.ParseInt(postSlug, 10, 64)
if err == nil {
query = query.Where(sgtmpb.Post{ID: id, Kind: sgtmpb.Post_TrackKind})
Expand Down
14 changes: 13 additions & 1 deletion pkg/sgtm/page_post.tmpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ <h1>{{.Post.Post.Title}}</h1>
<div>📆 Added <span data-toggle="tooltip" data-placement="right" title="{{.Post.Post.SortDate | fromUnixNano | prettyDate}}">{{.Post.Post.SortDate | fromUnixNano | prettyAgo}}</span></div>
<!--<div><span class="fa fa-soundcloud"></span> {{.Post.Post.URL}}</div>-->

<!-- RELATIONSHIPS -->
{{ range $rel := .Post.Post.RelationshipsAsSource }}
{{$kind := .Kind.String}}
{{with eq $kind "FeaturingUserKind"}}{{":handshake:" | emojify}} feat. {{template "user_link_with_pict_and_name" $rel.TargetUser}}{{end}}
{{end}}
<!-- FIXME: .Post.Post.RelationshipsAsTarget -->



{{if or (len .Post.Comments) (.User)}}
<div class="card mt-3 p-1">
Expand Down Expand Up @@ -125,7 +133,11 @@ <h1>{{.Post.Post.Title}}</h1>
<div class="card mb-3 bg-danger text-white">
<div class="card-header"><span class="fa fa-user-lock"></span> Admin</div>
<div class="p-2">
<div><a href="{{.Post.Post.CanonicalURL}}/sync" class="text-white"><span class="fa fa-sync"></span> Sync <span class="fab fa-soundcloud"></span></a></div>
<div><a href="{{.Post.Post.CanonicalURL}}/maintenance?extract_bpm=1" class="text-white"><span class="fa fa-sync"></span> Extract BPM</a></div>
<div><a href="{{.Post.Post.CanonicalURL}}/maintenance?detect_relationships=1" class="text-white"><span class="fa fa-sync"></span> Detect Relationships</a></div>
{{ if .Post.Post.IsSoundCloud }}
<div><a href="{{.Post.Post.CanonicalURL}}/maintenance?resync_soundcloud=1" class="text-white"><span class="fa fa-sync"></span> Resync SoundCloud</a></div>
{{ end }}
{{if not (eq .Post.Post.Author.ID .User.ID)}}
<div><a href="{{.Post.Post.CanonicalURL}}/edit" class="text-white"><span class="fa fa-edit"></span> Edit</a></div>
<div><a href="{{.Post.Post.URL}}" class="text-white"><span class="fab fa-soundcloud"></span> See original</a></div>
Expand Down
Loading

0 comments on commit f8e5a2e

Please sign in to comment.