Digitale bierlijst

__init__.py 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. """
  2. Piket server, handles events generated by the client.
  3. """
  4. import datetime
  5. import os
  6. from sqlalchemy.exc import SQLAlchemyError
  7. from flask import Flask, jsonify, abort, request
  8. from flask_sqlalchemy import SQLAlchemy
  9. DATA_HOME = os.environ.get("XDG_DATA_HOME", "~/.local/share")
  10. CONFIG_DIR = os.path.join(DATA_HOME, "piket_server")
  11. DB_PATH = os.path.expanduser(os.path.join(CONFIG_DIR, "database.sqlite3"))
  12. DB_URL = f"sqlite:///{DB_PATH}"
  13. app = Flask("piket_server")
  14. app.config["SQLALCHEMY_DATABASE_URI"] = DB_URL
  15. app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
  16. db = SQLAlchemy(app)
  17. # ---------- Models ----------
  18. class Person(db.Model):
  19. """ Represents a person to be shown on the lists. """
  20. __tablename__ = "people"
  21. person_id = db.Column(db.Integer, primary_key=True)
  22. name = db.Column(db.String, nullable=False)
  23. consumptions = db.relationship("Consumption", backref="person", lazy=True)
  24. def __repr__(self) -> str:
  25. return f"<Person {self.person_id}: {self.name}>"
  26. @property
  27. def as_dict(self) -> dict:
  28. return {
  29. "person_id": self.person_id,
  30. "name": self.name,
  31. "consumptions": {
  32. ct.consumption_type_id: Consumption.query.filter_by(person=self)
  33. .filter_by(consumption_type=ct)
  34. .count()
  35. for ct in ConsumptionType.query.all()
  36. },
  37. }
  38. class Settlement(db.Model):
  39. """ Represents a settlement of the list. """
  40. __tablename__ = "settlements"
  41. settlement_id = db.Column(db.Integer, primary_key=True)
  42. name = db.Column(db.String, nullable=False)
  43. consumptions = db.relationship("Consumption", backref="settlement", lazy=True)
  44. def __repr__(self) -> str:
  45. return f"<Settlement {self.settlement_id}: {self.name}>"
  46. class ConsumptionType(db.Model):
  47. """ Represents a type of consumption to be counted. """
  48. __tablename__ = "consumption_types"
  49. consumption_type_id = db.Column(db.Integer, primary_key=True)
  50. name = db.Column(db.String, nullable=False)
  51. icon = db.Column(db.String)
  52. consumptions = db.relationship("Consumption", backref="consumption_type", lazy=True)
  53. def __repr__(self) -> str:
  54. return f"<ConsumptionType: {self.name}>"
  55. @property
  56. def as_dict(self) -> dict:
  57. return {
  58. "consumption_type_id": self.consumption_type_id,
  59. "name": self.name,
  60. "icon": self.icon,
  61. }
  62. class Consumption(db.Model):
  63. """ Represent one consumption to be counted. """
  64. __tablename__ = "consumptions"
  65. consumption_id = db.Column(db.Integer, primary_key=True)
  66. person_id = db.Column(db.Integer, db.ForeignKey("people.person_id"), nullable=True)
  67. consumption_type_id = db.Column(
  68. db.Integer,
  69. db.ForeignKey("consumption_types.consumption_type_id"),
  70. nullable=False,
  71. )
  72. settlement_id = db.Column(
  73. db.Integer, db.ForeignKey("settlements.settlement_id"), nullable=True
  74. )
  75. created_at = db.Column(
  76. db.DateTime, default=datetime.datetime.utcnow, nullable=False
  77. )
  78. def __repr__(self) -> str:
  79. return f"<Consumption: {self.consumption_type.name} for {self.person.name}>"
  80. @property
  81. def as_dict(self) -> dict:
  82. return {
  83. "person_id": self.person_id,
  84. "consumption_type_id": self.consumption_type_id,
  85. "settlement_id": self.settlement_id,
  86. "created_at": self.created_at.isoformat(),
  87. }
  88. # ---------- Models ----------
  89. @app.route("/ping")
  90. def ping() -> None:
  91. """ Return a status ping. """
  92. return "Pong"
  93. PRESET_NAMES = [
  94. "Maarten",
  95. "Knoepie Draggelsturf",
  96. "Teddy Veenlijk",
  97. "Chris Kraslot",
  98. "Knibbe Tjakkomans",
  99. "Foek Lammenschaap",
  100. ]
  101. PEOPLE = {
  102. index: {"id": index, "name": name, "count": 0}
  103. for index, name in enumerate(PRESET_NAMES)
  104. }
  105. NEXT_ID = len(PEOPLE)
  106. @app.route("/people", methods=["GET"])
  107. def get_people():
  108. """ Return a list of currently known people. """
  109. people = Person.query.order_by(Person.name).all()
  110. result = [person.as_dict for person in people]
  111. return jsonify(people=result)
  112. @app.route("/people/<int:person_id>", methods=["GET"])
  113. def get_person(person_id: int):
  114. person = Person.query.get_or_404(person_id)
  115. return jsonify(person=person.as_dict)
  116. @app.route("/people", methods=["POST"])
  117. def add_person():
  118. """
  119. Add a new person.
  120. Required parameters:
  121. - name (str)
  122. """
  123. json = request.get_json()
  124. if not json:
  125. return jsonify({"error": "Could not parse JSON."}), 400
  126. data = json.get("person") or {}
  127. person = Person(name=data.get("name"))
  128. try:
  129. db.session.add(person)
  130. db.session.commit()
  131. except SQLAlchemyError:
  132. return jsonify({"error": "Invalid arguments for Person."})
  133. return jsonify(person=person.as_dict)
  134. @app.route("/people/<int:person_id>/add_consumption", methods=["POST"])
  135. def add_consumption(person_id: int):
  136. person = Person.query.get_or_404(person_id)
  137. consumption = Consumption(person=person, consumption_type_id=1)
  138. try:
  139. db.session.add(consumption)
  140. db.session.commit()
  141. except SQLAlchemyError:
  142. return (
  143. jsonify(
  144. {"error": "Invalid Consumption parameters.", "person": person.as_dict}
  145. ),
  146. 400,
  147. )
  148. return jsonify(person=person.as_dict, consumption=consumption.as_dict)