github.com/bitcubate/cryptojournal@v1.2.5-0.20171102134152-f578b3d788ab/src/stories/story.go (about)

     1  // Package stories represents the story resource
     2  package stories
     3  
     4  import (
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/fragmenta/model/file"
    10  
    11  	"github.com/bitcubate/cryptojournal/src/lib/resource"
    12  	"github.com/bitcubate/cryptojournal/src/lib/status"
    13  )
    14  
    15  // Story handles saving and retreiving stories from the database
    16  type Story struct {
    17  	// resource.Base defines behaviour and fields shared between all resources
    18  	resource.Base
    19  
    20  	// status.ResourceStatus defines a status field and associated behaviour
    21  	status.ResourceStatus
    22  
    23  	CommentCount int64
    24  	Name         string
    25  	Summary      string
    26  	URL          string
    27  	Points       int64
    28  	Rank         int64
    29  	UserID       int64
    30  
    31  	// UserName denormalises the user name - use join instead?
    32  	UserName string
    33  }
    34  
    35  // NegativePoints returns a negative point score or 0 if points is above 0
    36  func (s *Story) NegativePoints() int64 {
    37  	if s.Points > 0 {
    38  		return 0
    39  	}
    40  	return -s.Points
    41  }
    42  
    43  // Domain returns the domain of the story URL
    44  func (s *Story) Domain() string {
    45  	parts := strings.Split(s.URL, "/")
    46  	if len(parts) > 2 {
    47  		return strings.Replace(parts[2], "www.", "", 1)
    48  	}
    49  
    50  	if len(s.URL) > 0 {
    51  		return s.URL
    52  	}
    53  
    54  	return "CJ"
    55  }
    56  
    57  // ShowAsk returns true if this is a Show: or Ask: story
    58  func (s *Story) ShowAsk() bool {
    59  	return strings.HasPrefix(s.Name, "Show:") || strings.HasPrefix(s.Name, "Ask:")
    60  }
    61  
    62  // DestinationURL returns the URL of the story
    63  // if no url is set, it uses the CanonicalURL
    64  func (s *Story) DestinationURL() string {
    65  	// If downvoted, don't publish urls
    66  	if s.Points < 0 {
    67  		return ""
    68  	}
    69  	// If we have an external url, return it
    70  	if s.URL != "" {
    71  		return s.URL
    72  	}
    73  	// If we have an empty url, return the story url instead
    74  	return s.CanonicalURL()
    75  }
    76  
    77  // PrimaryURL returns the URL to use for this story in lists
    78  // Videos and Show Ask stories link to the story
    79  // for other links for now it is the destination
    80  func (s *Story) PrimaryURL() string {
    81  	// If video or show or empty, return story url
    82  	if s.YouTube() || s.ShowAsk() || s.URL == "" {
    83  		return s.CanonicalURL()
    84  	}
    85  
    86  	return s.DestinationURL()
    87  }
    88  
    89  // CanonicalURL is the canonical URL of the story on this site
    90  // including a slug for seo
    91  func (s *Story) CanonicalURL() string {
    92  	return fmt.Sprintf("/stories/%d-%s", s.ID, file.SanitizeName(s.Name))
    93  }
    94  
    95  // Code returns true if this is a link to a git repository
    96  // At present we only check for github urls, we should at least check for bitbucket
    97  func (s *Story) Code() bool {
    98  	if strings.Contains(s.URL, "https://github.com") {
    99  		if strings.Contains(s.URL, "/commit/") || strings.Contains(s.URL, "/releases/") || strings.HasSuffix(s.URL, ".md") {
   100  			return false
   101  		}
   102  		return true
   103  	}
   104  	return false
   105  }
   106  
   107  // GodocURL returns the godoc.org URL for this story, or empty string if none
   108  func (s *Story) GodocURL() string {
   109  	if s.Code() {
   110  		return strings.Replace(s.URL, "https://github.com", "https://godoc.org/github.com", 1)
   111  	}
   112  	return ""
   113  }
   114  
   115  // VetURL returns a URL for goreportcard.com, for code repos
   116  func (s *Story) VetURL() string {
   117  	if s.Code() {
   118  		return strings.Replace(s.URL, "https://github.com/", "http://goreportcard.com/report/", 1)
   119  	}
   120  	return ""
   121  }
   122  
   123  // YouTube returns true if this is a youtube video
   124  func (s *Story) YouTube() bool {
   125  	return strings.Contains(s.URL, "youtube.com/watch?v=")
   126  }
   127  
   128  // YouTubeURL returns the youtube URL
   129  func (s *Story) YouTubeURL() string {
   130  	url := strings.Replace(s.URL, "https://s.youtube.com", "https://www.youtube.com", 1)
   131  	// https://www.youtube.com/watch?v=sZx3oZt7LVg ->
   132  	// https://www.youtube.com/embed/sZx3oZt7LVg
   133  	url = strings.Replace(url, "watch?v=", "embed/", 1)
   134  	return url
   135  }
   136  
   137  // CommentCountDisplay returns the comment count or ellipsis if count is 0
   138  func (s *Story) CommentCountDisplay() string {
   139  	if s.CommentCount > 0 {
   140  		return fmt.Sprintf("%d", s.CommentCount)
   141  	}
   142  	return "…"
   143  }
   144  
   145  // NameDisplay returns a title string without hashtags (assumed to be at the end),
   146  // by truncating the title at the first #
   147  func (s *Story) NameDisplay() string {
   148  	if strings.Contains(s.Name, "#") {
   149  		return s.Name[0:strings.Index(s.Name, "#")]
   150  	}
   151  	return s.Name
   152  }
   153  
   154  // Tags are defined as words beginning with # in the title
   155  // TODO: for speed and clarity we could extract at submit time instead and store in db
   156  func (s *Story) Tags() []string {
   157  	var tags []string
   158  	if strings.Contains(s.Name, "#") {
   159  		// split on " #"
   160  		parts := strings.Split(s.Name, " #")
   161  		tags = parts[1:]
   162  	}
   163  	return tags
   164  }
   165  
   166  // Editable returns true if this story is editable.
   167  // Stories are editable if less than 1 hours old
   168  func (s *Story) Editable() bool {
   169  	return time.Now().Sub(s.CreatedAt) < time.Hour*1
   170  }
   171  
   172  // OwnedBy returns true if this user id owns this story.
   173  func (s *Story) OwnedBy(uid int64) bool {
   174  	return uid == s.UserID
   175  }