<commit_before>package models

import (
	"database/sql"
	"errors"
	"fmt"
	sq "github.com/Masterminds/squirrel"
	"github.com/guregu/null"
	"net/http"
	"strings"
)

// Returned by a function returning only one listing (usually by ID)
type Listing struct {
	KeyID                int         `json:"keyId"`
	CreationDate         null.Time   `json:"creationDate"`
	LastModificationDate null.Time   `json:"lastModificationDate"`
	Title                string      `json:"title"`
	Description          null.String `json:"description"`
	UserID               int         `json:"userId"`
	Username             null.String `json:"username"`
	Price                null.Int    `json:"price"`
	Status               null.String `json:"status"`
	ExpirationDate       null.Time   `json:"expirationDate"`
	Thumbnail            null.String `json:"thumbnail"`
	Photos               []string    `json:"photos"`
	IsStarred            bool        `json:"isStarred"`
}

type listingQuery struct {
	Query            string
	OnlyStarred      bool
	OnlyMine         bool
	TruncationLength int
	Limit            uint64
	UserID           int
}

func NewListingQuery() *listingQuery {
	q := new(listingQuery)
	q.TruncationLength = defaultTruncationLength
	q.Limit = defaultNumResults
	return q
}

type IsStarred struct {
	IsStarred bool `json:"isStarred"`
}

// Returns the SQL query that returns true if a particular listing is starred
// by the user with key_id id. This method exists because dealing with nested
// SQL queries in squirrel is an ugly pain in the ass.
func isStarredBy(id int) string {
	// "But Perry!" you say,
	// "concatenating strings and putting it directly in an SQL query is bad!"
	// you say.
	// "You're exactly correct, of course. Unfortunately we need a nested sql
	// query here and I couldn't find any documentation for nested queries
	// using squirrel. (Or any other documentation on squirrel other than the
	// godocs). On the bright side, we're not actually opening ourselves to an
	// injection attack since id is guaranteed to be an int."
	// "But isn't this still annoying and ugly?"
	// "Yeah."
	return fmt.Sprint("exists( SELECT 1 FROM starred_listings",
		" WHERE starred_listings.listing_id=listings.key_id",
		" AND starred_listings.user_id=", id,
		" AND starred_listings.is_active)")
}

// Returns the most recent count listings, based on original date created.
// If queryStr is nonempty, filters that every returned item must have every word in either title or description
// On error, returns an error and the HTTP code associated with that error.
func ReadListings(db *sql.DB, query *listingQuery) ([]*Listing, error, int) {
	// Create listings statement
	stmt := psql.
		Select("listings.key_id", "listings.creation_date",
			"listings.last_modification_date", "title",
			fmt.Sprintf("left(description, %d)", query.TruncationLength), "user_id",
			"users.net_id", "price", "status", "expiration_date", "thumbnails.url",
			isStarredBy(query.UserID)).
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id").
		LeftJoin("thumbnails ON listings.thumbnail_id = thumbnails.key_id")

	for _, word := range strings.Fields(query.Query) {
		stmt = stmt.Where("(lower(listings.title) LIKE lower(?) OR lower(listings.description) LIKE lower(?))", fmt.Sprint("%", word, "%"), fmt.Sprint("%", word, "%"))
	}

	if query.UserID == 0 && (query.OnlyStarred || query.OnlyMine) {
		return nil, errors.New("Unauthenticated user attempted to view profile data"), http.StatusUnauthorized
	}

	if query.OnlyStarred {
		stmt = stmt.Where(isStarredBy(query.UserID))
	}

	if query.OnlyMine {
		stmt = stmt.Where(sq.Eq{"user_id": query.UserID})
	}

	stmt = stmt.OrderBy("listings.creation_date DESC")
	if query.Limit <= maxNumResults {
		stmt = stmt.Limit(query.Limit)
	} else {
		stmt = stmt.Limit(maxNumResults)
	}

	// Query db
	rows, err := stmt.RunWith(db).Query()
	if err != nil {
		return nil, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing structs
	listings := make([]*Listing, 0)
	for rows.Next() {
		l := new(Listing)
		err := rows.Scan(&l.KeyID, &l.CreationDate, &l.LastModificationDate,
			&l.Title, &l.Description, &l.UserID, &l.Username, &l.Price, &l.Status,
			&l.ExpirationDate, &l.Thumbnail, &l.IsStarred)
		if err != nil {
			return nil, err, http.StatusInternalServerError
		}
		listings = append(listings, l)
	}

	if err = rows.Err(); err != nil {
		return nil, err, http.StatusInternalServerError
	}

	return listings, nil, http.StatusOK
}

// Returns the most recent count listings, based on original date created. On error
// returns an error and the HTTP code associated with that error.
func ReadListing(db *sql.DB, id string) (Listing, error, int) {
	var listing Listing

	// Create listing query
	query := psql.
		Select("listings.key_id", "listings.creation_date", "listings.last_modification_date",
			"title", "description", "user_id", "users.net_id", "price", "status", "expiration_date",
			"thumbnails.url").
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id").
		LeftJoin("thumbnails ON listings.thumbnail_id = thumbnails.key_id").
		Where(sq.Eq{"listings.key_id": id})

	// Query db for listing
	rows, err := query.RunWith(db).Query()
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing struct
	rows.Next()
	err = rows.Scan(&listing.KeyID, &listing.CreationDate,
		&listing.LastModificationDate, &listing.Title, &listing.Description,
		&listing.UserID, &listing.Username, &listing.Price, &listing.Status,
		&listing.ExpirationDate, &listing.Thumbnail)
	if err == sql.ErrNoRows {
		return listing, err, http.StatusNotFound
	} else if err != nil {
		return listing, err, http.StatusInternalServerError
	}

	return listing, nil, http.StatusOK
}

// Inserts the given listing (belonging to userId) into the database. Returns
// listing with its new KeyID added.
func CreateListing(db *sql.DB, listing Listing, userId int) (Listing, error, int) {
	listing.UserID = userId

	// Insert listing
	stmt := psql.Insert("listings").<commit_msg>Include photos in listings<commit_after>package models

import (
	"database/sql"
	"errors"
	"fmt"
	sq "github.com/Masterminds/squirrel"
	"github.com/guregu/null"
	"net/http"
	"strings"
	"github.com/lib/pq"
)

// Returned by a function returning only one listing (usually by ID)
type Listing struct {
	KeyID                int         `json:"keyId"`
	CreationDate         null.Time   `json:"creationDate"`
	LastModificationDate null.Time   `json:"lastModificationDate"`
	Title                string      `json:"title"`
	Description          null.String `json:"description"`
	UserID               int         `json:"userId"`
	Username             null.String `json:"username"`
	Price                null.Int    `json:"price"`
	Status               null.String `json:"status"`
	ExpirationDate       null.Time   `json:"expirationDate"`
	Thumbnail            null.String `json:"thumbnail"`
	Photos               pq.StringArray    `json:"photos"`
	IsStarred            bool        `json:"isStarred"`
}

type listingQuery struct {
	Query            string
	OnlyStarred      bool
	OnlyMine         bool
	TruncationLength int
	Limit            uint64
	UserID           int
}

func NewListingQuery() *listingQuery {
	q := new(listingQuery)
	q.TruncationLength = defaultTruncationLength
	q.Limit = defaultNumResults
	return q
}

type IsStarred struct {
	IsStarred bool `json:"isStarred"`
}

// Returns the SQL query that returns true if a particular listing is starred
// by the user with key_id id. This method exists because dealing with nested
// SQL queries in squirrel is an ugly pain in the ass.
func isStarredBy(id int) string {
	// "But Perry!" you say,
	// "concatenating strings and putting it directly in an SQL query is bad!"
	// you say.
	// "You're exactly correct, of course. Unfortunately we need a nested sql
	// query here and I couldn't find any documentation for nested queries
	// using squirrel. (Or any other documentation on squirrel other than the
	// godocs). On the bright side, we're not actually opening ourselves to an
	// injection attack since id is guaranteed to be an int."
	// "But isn't this still annoying and ugly?"
	// "Yeah."
	return fmt.Sprint("exists( SELECT 1 FROM starred_listings",
		" WHERE starred_listings.listing_id=listings.key_id",
		" AND starred_listings.user_id=", id,
		" AND starred_listings.is_active)")
}

// Returns the most recent count listings, based on original date created.
// If queryStr is nonempty, filters that every returned item must have every word in either title or description
// On error, returns an error and the HTTP code associated with that error.
func ReadListings(db *sql.DB, query *listingQuery) ([]*Listing, error, int) {
	// Create listings statement
	stmt := psql.
		Select("listings.key_id", "listings.creation_date",
			"listings.last_modification_date", "title",
			fmt.Sprintf("left(description, %d)", query.TruncationLength), "user_id",
			"users.net_id", "price", "status", "expiration_date", "thumbnails.url",
			isStarredBy(query.UserID), "photos").
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id").
		LeftJoin("thumbnails ON listings.thumbnail_id = thumbnails.key_id")

	for _, word := range strings.Fields(query.Query) {
		stmt = stmt.Where("(lower(listings.title) LIKE lower(?) OR lower(listings.description) LIKE lower(?))", fmt.Sprint("%", word, "%"), fmt.Sprint("%", word, "%"))
	}

	if query.UserID == 0 && (query.OnlyStarred || query.OnlyMine) {
		return nil, errors.New("Unauthenticated user attempted to view profile data"), http.StatusUnauthorized
	}

	if query.OnlyStarred {
		stmt = stmt.Where(isStarredBy(query.UserID))
	}

	if query.OnlyMine {
		stmt = stmt.Where(sq.Eq{"user_id": query.UserID})
	}

	stmt = stmt.OrderBy("listings.creation_date DESC")
	if query.Limit <= maxNumResults {
		stmt = stmt.Limit(query.Limit)
	} else {
		stmt = stmt.Limit(maxNumResults)
	}

	// Query db
	rows, err := stmt.RunWith(db).Query()
	if err != nil {
		return nil, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing structs
	listings := make([]*Listing, 0)
	for rows.Next() {
		l := new(Listing)
		err := rows.Scan(&l.KeyID, &l.CreationDate, &l.LastModificationDate,
			&l.Title, &l.Description, &l.UserID, &l.Username, &l.Price, &l.Status,
			&l.ExpirationDate, &l.Thumbnail, &l.IsStarred, &l.Photos)
		if err != nil {
			return nil, err, http.StatusInternalServerError
		}
		listings = append(listings, l)

		// If no photos, initialize to empty array for frontend convenience
		if l.Photos == nil {
			l.Photos = []string{}
		}
	}

	if err = rows.Err(); err != nil {
		return nil, err, http.StatusInternalServerError
	}

	return listings, nil, http.StatusOK
}

// Returns the most recent count listings, based on original date created. On error
// returns an error and the HTTP code associated with that error.
func ReadListing(db *sql.DB, id string) (Listing, error, int) {
	var listing Listing

	// Create listing query
	query := psql.
		Select("listings.key_id", "listings.creation_date", "listings.last_modification_date",
			"title", "description", "user_id", "users.net_id", "price", "status", "expiration_date",
			"thumbnails.url", "photos").
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id").
		LeftJoin("thumbnails ON listings.thumbnail_id = thumbnails.key_id").
		Where(sq.Eq{"listings.key_id": id})

	// Query db for listing
	rows, err := query.RunWith(db).Query()
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing struct
	rows.Next()
	err = rows.Scan(&listing.KeyID, &listing.CreationDate,
		&listing.LastModificationDate, &listing.Title, &listing.Description,
		&listing.UserID, &listing.Username, &listing.Price, &listing.Status,
		&listing.ExpirationDate, &listing.Thumbnail, &listing.Photos)
	if err == sql.ErrNoRows {
		return listing, err, http.StatusNotFound
	} else if err != nil {
		return listing, err, http.StatusInternalServerError
	}

	// If no photos, initialize to empty array for frontend convenience
	if listing.Photos == nil {
		listing.Photos = []string{}
	}

	return listing, nil, http.StatusOK
}

// Inserts the given listing (belonging to userId) into the database. Returns
// listing with its new KeyID added.
func CreateListing(db *sql.DB, listing Listing, userId int) (Listing, error, int) {
	listing.UserID = userId

	// Insert listing
	stmt := psql.Insert("listings").
{'fraction_non_alphanumeric': 0.1235813366960908, 'fraction_numerical': 0.0007881462799495586, 'mean_word_length': 3.6857459379615953, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7188916', 'n_tokens_mistral': 4252, 'n_tokens_neox': 3994, 'n_words': 1173}