Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:

- name: Pass the diff and file content to Python validation and send request
run: |
python validate_pixel_update.py changes.txt changed_file_content.txt ${{ github.event.pull_request.user.login }} https://amplace.co/api/update_pixel
python validate_pixel_update.py changes.txt changed_file_content.txt ${{ github.event.pull_request.user.login }} https://amplacebackend.amfoss.in/api/update_pixel

auto_merge_pr:
needs: audit
Expand Down Expand Up @@ -114,4 +114,4 @@ jobs:
if: failure()
run: |
echo "Previous steps failed. Closing PR #$PR_NUMBER"
gh pr close $PR_NUMBER --delete-branch
gh pr close $PR_NUMBER --delete-branch
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
frontend/.DS_Store
frontend/node_modules
frontend/*.log
frontend/explorations
frontend/TODOs.md
frontend/RELEASE_NOTE*.md
frontend/packages/server-renderer/basic.js
frontend/packages/server-renderer/build.dev.js
frontend/packages/server-renderer/build.prod.js
frontend/packages/server-renderer/server-plugin.js
frontend/packages/server-renderer/client-plugin.js
frontend/packages/template-compiler/build.js
frontend/packages/template-compiler/browser.js
frontend/.vscode
frontend/dist
frontend/temp
frontend/types/v3-generated.d.ts

backend/.idea/
backend/.venv*/
backend/venv*/
backend/__pycache__/
backend/dist/
backend/.coverage*
backend/htmlcov/
backend/.tox/
backend/docs/_build/
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# am/place

am/place is an interactive game inspired by [r/place](https://en.wikipedia.org/wiki/R/place) built by amFOSS members for Hacktoberfest 2024. The way it works is that players submit PR's to this repository modifying the `pixel_update.json` file, the modifications to this file will reflect on pixels on the cavas.

`pixel_update.json`:
```json
[
{
"x": "69",
"y": "42",
"rgb": "#8008ff"
},
{
"x": "70",
"y": "7",
"rgb": "#222222"
},
{
"x": "71",
"y": "8",
"rgb": "#333333"
},
{
"x": "72",
"y": "9",
"rgb": "#444444"
},
{
"x": "68",
"y": "5",
"rgb": "#555555"
}
]
```

Each entry in this json list corresponds to a singular pixel on the canvas. There are a few rules to be followed when making PR's:
1. Only `pixel_update.json` should have modifications, you can verify this by running `git diff` before commiting.
2. The above prescribed format must be strictly followed.
3. You're only allowed to modify 5 pixels at a time.

Failure to follow any of the above rules will result in your PR getting disqualified, we have setup a Github action which accepts and rejects PR's automagically. You can try out this game at [amplace.amfoss.in](https://amplace.amfoss.in)

## 2024 Canvas:
![image](https://github.com/user-attachments/assets/e3cb0ab1-8a1f-41dc-b629-3b173ea829fc)


## Credits:
Backend Author: [@Rihaan B H](https://github.com/RihaanBH-1810)

Frontend Author: [@JATAYU000](https://github.com/JATAYU000)

Misc Fixes: [@Hridesh MG](https://github.com/hrideshmg)
3 changes: 3 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
.venv/
__pycache__/
21 changes: 21 additions & 0 deletions backend/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Rihaan B H

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
44 changes: 44 additions & 0 deletions backend/Models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from dataclasses import dataclass
from datetime import datetime

from sqlalchemy import (
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
SmallInteger,
String,
Table,
create_engine,
)
from sqlalchemy.orm import declarative_base, relationship, sessionmaker

from config import config, curr_env

engine = create_engine(config["SQL_URI"])
Session = sessionmaker(bind=engine)
Base = declarative_base()


@dataclass
class Pixel(Base):
__tablename__ = "pixel"
pid = Column(Integer, primary_key=True, autoincrement=True)
x: int = Column(SmallInteger, default=None)
y: int = Column(SmallInteger, default=None)
color_hex: str = Column(String(30), default="#ffffff")
user_id: str = Column(String(50), ForeignKey("user.username"), nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user = relationship("User", back_populates="pixels")

@dataclass
class User(Base):
__tablename__ = "user"
username: str = Column(String(50), primary_key=True)
count: int = Column(Integer, default=0)

pixels = relationship("Pixel", back_populates="user")

Base.metadata.create_all(engine)

45 changes: 45 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## delete pixel
DELETE http://localhost:5000/api/delete_pixel
Content-Type: application/json

{ "X":9,
"Y":15
}

## Test get user details
GET http://localhost:5000/api/get_user_details

## Test updating pixels
POST http://localhost:5000/api/update_pixel
Content-Type: application/json

{ "user" : "test_user",
"pixel_list": [
{
"X": 10,
"Y": 15,
"hex-code": "#ff5733"
},
{
"X": 12,
"Y": 18,
"hex-code": "#33c5ff"
},
{
"X": 10,
"Y": 15,
"hex-code": "#123456"
},
{
"X": 9,
"Y": 15,
"hex-code": "#123456"
}
]
}

## Test getting pixel details
GET http://localhost:5000/api/get_pixel

---
Author: [@Rihaan B H](https://github.com/RihaanBH-1810)
163 changes: 163 additions & 0 deletions backend/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
from flask import Flask, jsonify, request
from Models import Session, Pixel, User
from config import config
from datetime import datetime, timedelta
from flask_cors import CORS, cross_origin
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'


app.secret_key = config["APP_SECRET_KEY"]

def check_cooldown(pixel):
session = Session()
try:
flag = True
existing_pixel = session.query(Pixel).filter_by(x=pixel["X"], y=pixel["Y"]).order_by(Pixel.updated_at.desc()).first()

if existing_pixel:
last_updated_time = existing_pixel.updated_at
if last_updated_time > datetime.utcnow() - timedelta(minutes=2):
flag = False

finally:
session.close()
return flag

@app.route("/api/update_pixel", methods=['POST'])
@cross_origin()
def update_pixel():
if request.method == "POST":
try:
cooldown_pixels = []
data = request.json
session = Session()
pixel_list = data["pixel_list"]
username = data["user"]

for pixel_data in pixel_list:

user = session.query(User).filter_by(username=username).first()
if not user:
user = User(username=username)
session.add(user)
session.commit()

existing_pixel = session.query(Pixel).filter_by(x=pixel_data["X"], y=pixel_data["Y"]).first()

if existing_pixel:
if check_cooldown(pixel_data):
existing_pixel.color_hex = pixel_data["hex-code"]
existing_pixel.updated_at = datetime.utcnow()
existing_pixel.user = user
user.count += 1
session.add(user)
else:
cooldown_pixels.append(pixel_data)

else:
new_pixel = Pixel(
user_id=user.username,
x=pixel_data["X"],
y=pixel_data["Y"],
color_hex=pixel_data["hex-code"],
updated_at=datetime.utcnow()
)
user.count += 1

session.add(user)
session.add(new_pixel)
session.commit()
if len(cooldown_pixels) == 0:
return jsonify({"success": True, "message" : "All pixels updated none blocked by a cooldown"}), 200
elif len(cooldown_pixels) == len(pixel_list):
return jsonify({"success" : True, "message": "All the pixels you tried to update have a cooldown try again in 2 min"}), 200
else:
return jsonify({"success" : True, "message" : f" the following pixels that you tried to update have a cooldown {cooldown_pixels}"}), 200

except Exception as e:
session.rollback()
return jsonify({"success": False, "message": str(e)}), 404
finally:
session.close()

@app.route("/api/get_pixel", methods=["GET"])
def get_pixel_details():
if request.method == "GET":
try:
session = Session()
pixels = session.query(Pixel).all()

pixel_list = [
{
"user": pixel.user_id,
"X": pixel.x,
"Y": pixel.y,
"hex-code": pixel.color_hex,
"updated_at": pixel.updated_at.strftime("%Y-%m-%d %H:%M:%S")
}
for pixel in pixels
]

return jsonify({"success": True, "pixels": pixel_list}), 200

except Exception as e:
return jsonify({"success": False, "message": str(e)}), 404

finally:
session.close()

@app.route("/api/get_user_details", methods=["GET"])
def get_user_details():
if request.method == "GET":
try:
session = Session()
users = session.query(User).all()

users_data_list = [
{
"user" : user.username,
"score" : user.count
}
for user in users
]
return jsonify({"success": True, "user_data": users_data_list}), 200

except Exception as e:
return jsonify({"success": False, "message": str(e)}), 404

finally:
session.close()

@app.route("/api/delete_pixel", methods=['DELETE'])
def delete_pixel():
if request.method == "DELETE":
try:
data = request.json
session = Session()
x = data.get("X")
y = data.get("Y")
if x is None or y is None:
return jsonify({"success": False, "message": "X and Y coordinates are required"}), 400

pixel = session.query(Pixel).filter_by(x=x, y=y).first()

if not pixel:
return jsonify({"success": False, "message": "Pixel not found"}), 404

session.delete(pixel)
session.commit()

return jsonify({"success": True, "message": "Pixel deleted successfully"}), 200

except Exception as e:
session.rollback()
return jsonify({"success": False, "message": str(e)}), 500

finally:
session.close()


if __name__ == "__main__":
app.run(debug=True)
19 changes: 19 additions & 0 deletions backend/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os
from dotenv import load_dotenv

load_dotenv()

curr_env = os.environ["MODE"] if "MODE" in os.environ else "development"
DB_URL = "" if curr_env == "production" else "localhost"
DB_USERNAME = os.getenv("DB_USERNAME")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_NAME = os.getenv("DB_NAME")
TIMEZONE = os.getenv("TIMEZONE")

APP_SECRET_KEY = os.getenv("APP_SECRET_KEY")

config = {
"SQL_URI": f"mariadb+pymysql://{DB_USERNAME}:{DB_PASSWORD}@{DB_URL}:3306/{DB_NAME}",
"TIMEZONE": TIMEZONE,
"APP_SECRET_KEY" : APP_SECRET_KEY
}
Loading
Loading