Skip to content

Indie-go is a full stack app for all your favorite indie games. Search through pre seeded games, created your own game listing and review games on the website!

Notifications You must be signed in to change notification settings

will-short/Indie-Go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Indie-Go By William Short

Since this was hosted on heroku this is currently down. Working on making a next.js version and hosting it on vercel! Below you can see information from the site when it was still up and running

Table of contents

Indie-Go overview

Indie-Go is a fullstack web-app using React, React-Redux, Python/Flask and PostgreSQL

This app is a combination of Etsy and Steam specifically for indie games.

Users are able to:

  • Search through 400 pre seeded games from Steams's API
  • Create, update and delete their own game listing
  • Create, update and delete reviews on game listings
  • Add, remove games to the user's cart
  • Checkout with the games in the user's cart

Homepage

homepage

Search

search

Gamepage

gamepage

Profilepage

profilepage

Architecture

Dataflow

DataFlow

Backend

Database (PostgreSQL)

The database for this app was set up to communicate with the server to store data for persistance between sessions and to serve back that data for games, listings and user cart details

Database Scheme

SQLAlchemy was used to create models to easily store and harvest data from the database.

Game listing model:

# in /app/models/listing.py
class Listing(db.Model):
    __tablename__ = 'listings'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    video_url = db.Column(db.String(255), nullable=True)
    image_urls = db.Column(db.Text(), nullable=False)
    description = db.Column(db.String(500), nullable=True)
    price = db.Column(db.Numeric(6, 2), nullable=True)
    owner_id = db.Column(db.Integer, db.ForeignKey(
        'users.id'), nullable=False)
    created_at = db.Column(db.DateTime(), nullable=False,
                           server_default=func.now())
    updated_at = db.Column(
        db.DateTime(), onupdate=func.now(), default=func.now())

    users = db.relationship('User', back_populates='listings')
    reviews = db.relationship(
        'Review', back_populates='listings', cascade="all, delete")
    tags = db.relationship(
        'Tag', back_populates='listings', cascade="all, delete")
    cart_owners = db.relationship(
        "User",
        secondary='cart_listings',
        back_populates="listings",
        overlaps="cart_listings"
    )

    def owner(self):
        return self.users.to_dict()

    def listingId(self):
        return self.id

    def listingInfo(self):
        return {
            'name': self.name,
            'video_url': self.video_url,
            'image_urls': json.loads(self.image_urls.replace("'", '"')),
            'description': self.description,
            "price": str(self.price),
            'owner_id': self.owner_id,
        }

    def to_dict(self):
        reviews = [review.to_dict() for review in self.reviews]
        return {
            'id': self.id,
            'name': self.name,
            'video_url': self.video_url,
            'image_urls': json.loads(self.image_urls.replace("'", '"')),
            'description': self.description,
            "price": str(self.price),
            'owner_id': self.owner_id,
            'owner': self.users.info(),
            'tags': self.tags[0].to_list() if self.tags else [],
            'reviews': reviews,
            'created_at': self.created_at.strftime('%m/%d/%Y %H:%M:%S'),
            'updated_at': self.updated_at.strftime('%m/%d/%Y %H:%M:%S')
        }

Server (Python/Flask)

The server for this app was coded using python with Flask to create routes responsible for dataflow between the frontend and the database.

Listing POST route (images uploaded to and hosted on AWS):

# in app/api/listing_routes
@listing_routes.route('/', methods=['POST'])
@login_required
def postListing():
    form = ListingForm()
    form['csrf_token'].data = request.cookies['csrf_token']
    video = form.data["video"]
    videoURL = None
    if video:
        video.filename = get_unique_filename(video.filename)
        videoupload = upload_file_to_s3(video)
        videoURL = videoupload["url"]

    uploads = [form.data["image1"], form.data["image2"],
               form.data["image3"], form.data["image4"], form.data["image5"]]
    imageURLs = []
    for upload in uploads:
        if upload:
            upload.filename = get_unique_filename(upload.filename)
            imageupload = upload_file_to_s3(upload)
            imageURLs.append(imageupload["url"])
    listing = Listing(
        name=form.data["name"],
        description=form.data["description"],
        video_url=videoURL,
        image_urls=json.dumps(imageURLs),
        price=form.data["price"],
        owner_id=current_user.id
    )
    db.session.add(listing)
    db.session.commit()
    tagsList = json.loads(form.data["tags"])
    tags = Tag(
        **tagsList,
        listing_id=listing.listingId()
    )
    db.session.add(tags)
    db.session.commit()
    return listing.to_dict()

Frontend

React (React)

The front end of Indie-Go is all based in react. React is one of the most popular JS frameworks for full stack aplications. Using React Components with Redux state Indie-Go serves all the data from the backend to be viewed by the user.

User info component: image

// in react-app/src/components/User/index.js
function User({ user }) {
  const session = useSelector((state) => state.session);

  if (!user) {
    return null;
  }
  let listings = user?.listings;
  let tags = new Set(listings?.flatMap((listing) => listing.tags));
  return (
    <div className={style.container}>
      <img src={user?.image_url} alt="" />
      <span>
        <strong>{user?.username}</strong>
      </span>
      <span>
        Games listed: <strong>{listings?.length}</strong>, Reviews posted:
        <strong>{user?.reviews?.length}</strong>
      </span>
      <span>tags:</span>
      <div className={style.tags}>
        {[...tags].map((tag, i) => (
          <span key={i} className={"tags " + tag}>
            {tag}
          </span>
        ))}
      </div>
      {session?.user?.id === +user?.id && (
        <Link className="primary-link" to={`/users/${user.id}/listings/new/1`}>
          New Listing
        </Link>
      )}
    </div>
  );
}
export default User;

Redux Store (React-Redux)

Redux is used to keep a site wide state for the current logged in user and all game listings. On application start Redux stores all listings, while this causes initial load time to be longer it allows for a fast experience with game listings after initial load.

Part of the Redux state tree (1 and 2 are game listings): image

Redux uses Thunks to communicate to the backend and then change state with an Action based on the response

Thunk for POST listing:

// in react-app/src/store/listings.js
export const postListing =
  (video, images, name, description, price, tags) => async (dispatch) => {
    const formData = new FormData();
    if (video) formData.append("video", video);
    images.map((image, i) =>
      image ? formData.append(`image${i + 1}`, image) : null
    );
    formData.append("name", name);
    formData.append("description", description);
    formData.append("tags", JSON.stringify(tags));
    if (price) formData.append("price", price);
    const res = await fetch("/api/listings/", {
      method: "POST",
      body: formData,
    });

    const data = await res.json();
    dispatch(post(data));
  };

Action dispatched with data from the response from server:

// in react-app/src/store/listings.js
const post = (listing) => ({
  type: POST,
  listing,
});

About

Indie-go is a full stack app for all your favorite indie games. Search through pre seeded games, created your own game listing and review games on the website!

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages