Browse Source

init

master
adcs 1 year ago
commit
74cd5120d2
100 changed files with 4097 additions and 0 deletions
  1. 11
    0
      Bl0g/Dockerfile
  2. 385
    0
      Bl0g/app.py
  3. 12
    0
      Bl0g/docker-compose.yml
  4. 3
    0
      Bl0g/requirements.txt
  5. 110
    0
      Bl0g/templates/base.html
  6. 26
    0
      Bl0g/templates/contact.html
  7. 18
    0
      Bl0g/templates/contact_list.html
  8. 23
    0
      Bl0g/templates/contact_view.html
  9. 18
    0
      Bl0g/templates/contact_view_form.html
  10. 39
    0
      Bl0g/templates/index.html
  11. 19
    0
      Bl0g/templates/login.html
  12. 17
    0
      Bl0g/templates/register.html
  13. 15
    0
      Bl0g/templates/user.html
  14. 10
    0
      GUEST_BOOK/app/CMakeLists.txt
  15. 49
    0
      GUEST_BOOK/app/Dockerfile
  16. 9
    0
      GUEST_BOOK/app/app/CMakeLists.txt
  17. 8
    0
      GUEST_BOOK/app/app/src/main.cpp
  18. 13
    0
      GUEST_BOOK/app/conan_config/profiles/Guestbook24
  19. 98
    0
      GUEST_BOOK/app/conan_config/settings.yml
  20. 12
    0
      GUEST_BOOK/app/conanfile.txt
  21. 7
    0
      GUEST_BOOK/app/data/drop.sql
  22. 36
    0
      GUEST_BOOK/app/data/init.sql
  23. 25
    0
      GUEST_BOOK/app/lib/CMakeLists.txt
  24. 19
    0
      GUEST_BOOK/app/lib/include/Application.h
  25. 10
    0
      GUEST_BOOK/app/lib/include/Command.h
  26. 17
    0
      GUEST_BOOK/app/lib/include/Commands.h
  27. 6
    0
      GUEST_BOOK/app/lib/include/Context.h
  28. 38
    0
      GUEST_BOOK/app/lib/include/DBHelper.h
  29. 258
    0
      GUEST_BOOK/app/lib/include/Database.h
  30. 174
    0
      GUEST_BOOK/app/lib/include/DbModel.h
  31. 7
    0
      GUEST_BOOK/app/lib/include/EditCommand.h
  32. 9
    0
      GUEST_BOOK/app/lib/include/Lib.h
  33. 7
    0
      GUEST_BOOK/app/lib/include/LoadEntriesCommand.h
  34. 7
    0
      GUEST_BOOK/app/lib/include/LoadMyEntriesCommand.h
  35. 5
    0
      GUEST_BOOK/app/lib/include/Logger.h
  36. 9
    0
      GUEST_BOOK/app/lib/include/LoginCommand.h
  37. 7
    0
      GUEST_BOOK/app/lib/include/PostCommand.h
  38. 7
    0
      GUEST_BOOK/app/lib/include/QuoteCommand.h
  39. 9
    0
      GUEST_BOOK/app/lib/include/RegisterCommand.h
  40. 28
    0
      GUEST_BOOK/app/lib/include/Util.h
  41. 9
    0
      GUEST_BOOK/app/lib/include/ViewCommand.h
  42. 54
    0
      GUEST_BOOK/app/lib/src/Application.cpp
  43. 19
    0
      GUEST_BOOK/app/lib/src/Commands.cpp
  44. 35
    0
      GUEST_BOOK/app/lib/src/Database.cpp
  45. 5
    0
      GUEST_BOOK/app/lib/src/DbModel.cpp
  46. 27
    0
      GUEST_BOOK/app/lib/src/EditCommand.cpp
  47. 46
    0
      GUEST_BOOK/app/lib/src/LoadEntriesCommand.cpp
  48. 91
    0
      GUEST_BOOK/app/lib/src/LoadMyEntriesCommand.cpp
  49. 4
    0
      GUEST_BOOK/app/lib/src/Logger.cpp
  50. 24
    0
      GUEST_BOOK/app/lib/src/LoginCommand.cpp
  51. 28
    0
      GUEST_BOOK/app/lib/src/PostCommand.cpp
  52. 38
    0
      GUEST_BOOK/app/lib/src/QuoteCommand.cpp
  53. 18
    0
      GUEST_BOOK/app/lib/src/RegisterCommand.cpp
  54. 36
    0
      GUEST_BOOK/app/lib/src/Util.cpp
  55. 47
    0
      GUEST_BOOK/app/lib/src/ViewCommand.cpp
  56. 3
    0
      GUEST_BOOK/app/run.sh
  57. 19
    0
      GUEST_BOOK/app/test/CMakeLists.txt
  58. 2
    0
      GUEST_BOOK/app/test/src/main.cpp
  59. 694
    0
      GUEST_BOOK/app/test/src/test.cpp
  60. 16
    0
      GUEST_BOOK/docker-compose.yaml
  61. 10
    0
      Notepad/.dockerignore
  62. 12
    0
      Notepad/.gitignore
  63. 20
    0
      Notepad/Dockerfile
  64. 6
    0
      Notepad/README.md
  65. 0
    0
      Notepad/app/__init__.py
  66. 66
    0
      Notepad/app/app.py
  67. 73
    0
      Notepad/app/auth.py
  68. 16
    0
      Notepad/app/config.py
  69. 59
    0
      Notepad/app/encrypt.py
  70. 10
    0
      Notepad/app/forms/forms.py
  71. 35
    0
      Notepad/app/models.py
  72. 144
    0
      Notepad/app/routes.py
  73. 0
    0
      Notepad/app/static/files/notes/.gitkeep
  74. BIN
      Notepad/app/static/images/logo.png
  75. 9
    0
      Notepad/app/static/styles.css
  76. 5
    0
      Notepad/app/templates/403.html
  77. 5
    0
      Notepad/app/templates/404.html
  78. 5
    0
      Notepad/app/templates/500.html
  79. 117
    0
      Notepad/app/templates/base.html
  80. 25
    0
      Notepad/app/templates/index.html
  81. 31
    0
      Notepad/app/templates/login.html
  82. 20
    0
      Notepad/app/templates/showEncryptedNotes.html
  83. 28
    0
      Notepad/app/templates/show_notes.html
  84. 31
    0
      Notepad/app/templates/signup.html
  85. 33
    0
      Notepad/app/templates/uploadNote.html
  86. 44
    0
      Notepad/docker-compose.yml
  87. 22
    0
      Notepad/init_secret.py
  88. 54
    0
      Notepad/requirements.txt
  89. 14
    0
      Notepad/waitdb.sh
  90. 3
    0
      SuperSecureMySpace/apiserver/.dockerignore
  91. 3
    0
      SuperSecureMySpace/apiserver/.gitignore
  92. 10
    0
      SuperSecureMySpace/apiserver/Dockerfile
  93. 42
    0
      SuperSecureMySpace/apiserver/package.json
  94. 51
    0
      SuperSecureMySpace/apiserver/pug/blog.pug
  95. 40
    0
      SuperSecureMySpace/apiserver/pug/frame/api.pug
  96. 19
    0
      SuperSecureMySpace/apiserver/pug/frame/foot.pug
  97. 53
    0
      SuperSecureMySpace/apiserver/pug/frame/head.pug
  98. 7
    0
      SuperSecureMySpace/apiserver/pug/frame/layout.pug
  99. 180
    0
      SuperSecureMySpace/apiserver/pug/frame/style.pug
  100. 0
    0
      SuperSecureMySpace/apiserver/pug/login.pug

+ 11
- 0
Bl0g/Dockerfile View File

@@ -0,0 +1,11 @@
1
+FROM python:3.10
2
+
3
+COPY requirements.txt /
4
+
5
+RUN pip3 install --no-cache-dir -r /requirements.txt
6
+
7
+COPY . /app
8
+VOLUME ["/data"]
9
+WORKDIR /data
10
+
11
+CMD ["python3", "/app/app.py"]

+ 385
- 0
Bl0g/app.py View File

@@ -0,0 +1,385 @@
1
+import collections
2
+import datetime
3
+import hashlib
4
+import hmac
5
+import os
6
+import secrets
7
+from pathlib import Path
8
+from typing import Dict, Optional
9
+from uuid import uuid4
10
+
11
+import bcrypt
12
+from flask import (
13
+    Flask,
14
+    Response,
15
+    abort,
16
+    flash,
17
+    make_response,
18
+    redirect,
19
+    render_template,
20
+    request,
21
+    url_for,
22
+)
23
+from flask_sqlalchemy import SQLAlchemy
24
+from sqlalchemy.exc import IntegrityError
25
+from werkzeug.urls import url_decode
26
+
27
+storage_dir = Path.cwd()
28
+database_path = storage_dir / "database.db"
29
+sign_secret_path = storage_dir / "sign.key"
30
+flask_secret_path = storage_dir / "flask-secret.key"
31
+
32
+app = Flask(__name__)
33
+if flask_secret_path.exists():
34
+    app.config["SECRET_KEY"] = flask_secret_path.read_text()
35
+else:
36
+    temp = secrets.token_hex()
37
+    flask_secret_path.write_text(temp)
38
+    app.config["SECRET_KEY"] = temp
39
+
40
+app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{database_path}"
41
+app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
42
+db = SQLAlchemy(app)
43
+
44
+UTC = datetime.timezone.utc
45
+
46
+def utcnow() -> datetime.datetime:
47
+    """Get the current time/date as a UTC timezone-aware datetime object."""
48
+    return datetime.datetime.now(UTC)
49
+
50
+
51
+@app.template_filter('as_utc')
52
+def as_utc(dattim: datetime.datetime) -> datetime.datetime:
53
+    if dattim.tzinfo == UTC:
54
+        return dattim
55
+    if dattim.tzinfo is None:
56
+        dattim = dattim.replace(tzinfo=UTC)
57
+    return dattim.astimezone(UTC)
58
+
59
+
60
+class User(db.Model):
61
+    id = db.Column(db.Integer, primary_key=True)
62
+    username = db.Column(db.String(), unique=True, nullable=False, index=True)
63
+    password_digest = db.Column(db.String(), nullable=False)
64
+    last_login = db.Column(db.DateTime(timezone=True), nullable=False)
65
+    created_at = db.Column(db.DateTime(timezone=True), nullable=False)
66
+    session_cookie = db.Column(db.String(), nullable=True, index=True)
67
+
68
+    comments = db.relationship("Comment", back_populates="author", lazy=True)
69
+
70
+
71
+class ContactRequest(db.Model):
72
+    uuid = db.Column(db.String(), primary_key=True, nullable=False)
73
+    title = db.Column(db.String(), nullable=False)
74
+    content = db.Column(db.String(), nullable=False)
75
+    created_at = db.Column(db.DateTime(timezone=True), nullable=False)
76
+
77
+
78
+class Post(db.Model):
79
+    id = db.Column(db.Integer, primary_key=True)
80
+    title = db.Column(db.String(), nullable=False)
81
+    content = db.Column(db.String(), nullable=False)
82
+    date_posted = db.Column(db.Date(), nullable=False)
83
+
84
+    comments = db.relationship("Comment", back_populates="post", lazy=True)
85
+
86
+
87
+class Comment(db.Model):
88
+    id = db.Column(db.Integer, primary_key=True)
89
+    post_id = db.Column(db.Integer, db.ForeignKey("post.id"), nullable=False)
90
+    author_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
91
+    content = db.Column(db.String(), nullable=False)
92
+    created_at = db.Column(db.DateTime(timezone=True), nullable=False)
93
+
94
+    post = db.relationship("Post", back_populates="comments", lazy=True)
95
+    author = db.relationship("User", back_populates="comments", lazy=True)
96
+
97
+    __table_args__ = (db.Index("comment_post_author_index", post_id, author_id),)
98
+
99
+
100
+def set_session_cookie(response: Response, user: User):
101
+    λ = hashlib.new("sha256")
102
+    λ.update(user.username.encode())
103
+    now = utcnow()
104
+    λ.update(str(int(now.timestamp())).encode())
105
+    cookie = λ.hexdigest()
106
+
107
+    user.session_cookie = cookie
108
+    user.last_login = now
109
+    response.set_cookie("sessionID", cookie)
110
+    db.session.commit()
111
+
112
+
113
+def get_user() -> Optional[User]:
114
+    sess_id = request.cookies.get("sessionID")
115
+    return db.session.query(User).filter(User.session_cookie == sess_id).first()
116
+
117
+
118
+@app.context_processor
119
+def inject_current_user():
120
+    # Inject current_user variable into all templates
121
+    return {"current_user": get_user()}
122
+
123
+
124
+@app.route("/")
125
+def index():
126
+    posts = db.session.query(Post).order_by(Post.id).all()
127
+
128
+    post_comment_usernames: Dict[int, str] = collections.defaultdict(list)
129
+    for post_id, username in (
130
+        db.session.query(Post.id, User.username)
131
+        .filter(Comment.author_id == User.id)
132
+        .filter(Comment.post_id == Post.id)
133
+        .distinct(User.username)
134
+    ):
135
+        post_comment_usernames[post_id].append(username)
136
+
137
+    current_user = get_user()
138
+
139
+    post_my_comments: Dict[int, str] = collections.defaultdict(list)
140
+    if current_user:
141
+        for post_id, content in (
142
+            db.session.query(Post.id, Comment.content)
143
+            .filter(Comment.post_id == Post.id)
144
+            .filter(Comment.author_id == current_user.id)
145
+        ):
146
+            post_my_comments[post_id].append(content)
147
+
148
+    return render_template(
149
+        "index.html",
150
+        posts=posts,
151
+        post_comment_usernames=post_comment_usernames,
152
+        post_my_comments=post_my_comments,
153
+    )
154
+
155
+
156
+@app.route("/register", methods=["GET", "POST"])
157
+def register():
158
+    if request.method == "POST":
159
+        username = request.form["username"]
160
+        password = request.form["password"]
161
+
162
+        if username.isspace() or not username:
163
+            flash("username can't be empty")
164
+            return render_template("register.html")
165
+
166
+        if not username.isalnum():
167
+            flash("usernames can only consist of alphanumeric symbols")
168
+            return render_template("register.html")
169
+
170
+        if len(username) > 50:
171
+            flash("usernames can't be longer than 50 characters")
172
+            return render_template("register.html")
173
+
174
+        password_digest = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
175
+        now = utcnow()
176
+        user = User(username=username, password_digest=password_digest, last_login=now, created_at=now)
177
+        try:
178
+            db.session.add(user)
179
+            db.session.commit()
180
+        except IntegrityError:
181
+            db.session.rollback()
182
+            flash("username already in use", "error")
183
+            return render_template("register.html"), 409
184
+        else:
185
+            response = redirect("/")
186
+            set_session_cookie(response, user)
187
+            return response
188
+
189
+    return render_template("register.html")
190
+
191
+
192
+@app.route("/login", methods=["GET", "POST"])
193
+def login():
194
+    if request.method == "POST":
195
+        username = request.form["username"]
196
+        password = request.form["password"]
197
+
198
+        user = db.session.query(User).filter(User.username == username).first()
199
+        if user is not None and bcrypt.checkpw(password.encode(), user.password_digest):
200
+            response = redirect("/")
201
+            set_session_cookie(response, user)
202
+            return response
203
+
204
+        flash("wrong username or password")
205
+
206
+    return render_template("login.html")
207
+
208
+
209
+@app.route("/logout", methods=["POST"])
210
+def logout():
211
+    resp = make_response(redirect("/"))
212
+    resp.set_cookie("sessionID", "", expires=0)
213
+    return resp
214
+
215
+
216
+@app.route("/user/<string:username>", methods=["GET"])
217
+def user(username: str):
218
+    user = db.session.query(User).filter(User.username == username).first()
219
+    if user is None:
220
+        abort(404)
221
+
222
+    flash("This page is still WIP!")
223
+    return render_template("user.html", user=user)
224
+
225
+
226
+@app.route("/comment", methods=["POST"])
227
+def comment():
228
+    user = get_user()
229
+    if user is None:
230
+        flash("You need to login to comment.")
231
+        return redirect("/"), 401
232
+
233
+    content = request.form["content"]
234
+    post_id = request.form["post_id"]
235
+    post = db.session.query(Post).filter(Post.id == int(post_id)).first()
236
+    if post is None:
237
+        abort(404)
238
+
239
+    db.session.add(
240
+        Comment(
241
+            post=post,
242
+            author=user,
243
+            content=content,
244
+            created_at=utcnow(),
245
+        )
246
+    )
247
+    db.session.commit()
248
+    return redirect("/")
249
+
250
+
251
+j = 16
252
+if sign_secret_path.exists():
253
+    sign_secret = sign_secret_path.read_bytes()
254
+else:
255
+    sign_secret = secrets.token_bytes(j)
256
+    sign_secret_path.write_bytes(sign_secret)
257
+assert len(sign_secret) == j
258
+
259
+
260
+def sign(payload: bytes) -> bytes:
261
+    ϳ = len(payload)
262
+    base = sign_secret + j.to_bytes(4, byteorder="big") + payload
263
+    h = hashlib.new("md5")
264
+    h.update(base)
265
+    return h.digest()
266
+
267
+
268
+@app.route("/contact", methods=["GET", "POST"])
269
+def contact():
270
+    if request.method == "POST":
271
+        req = ContactRequest(
272
+            uuid=str(uuid4()),
273
+            title=request.form["title"],
274
+            content=request.form["content"],
275
+            created_at=datetime.datetime.now(datetime.timezone.utc)
276
+        )
277
+        db.session.add(req)
278
+        db.session.commit()
279
+
280
+        sig = sign(f"uuid={req.uuid}".encode())
281
+        return redirect(url_for("contact_view", sig=sig.hex(), uuid=req.uuid))
282
+
283
+    return render_template("contact.html")
284
+
285
+
286
+@app.route("/contact_list")
287
+def contact_list():
288
+    min_created = utcnow() - datetime.timedelta(minutes=30)
289
+    recent_requests = (
290
+        db.session.query(ContactRequest)
291
+        .filter(ContactRequest.created_at >= min_created)
292
+        .order_by(ContactRequest.created_at.desc())
293
+        .all()
294
+    )
295
+    return render_template("contact_list.html", recent_requests=recent_requests)
296
+
297
+
298
+@app.route("/contact_view")
299
+def contact_view():
300
+    # verify signature
301
+    uuid = None
302
+    sig = None
303
+    sigbase = []
304
+    ud = url_decode(request.query_string, charset=None)
305
+    for k, v in ud.items(True):
306
+        if k == b"sig":
307
+            try:
308
+                sig = bytes.fromhex(v.decode())
309
+            except ValueError:
310
+                sig = b""
311
+            continue
312
+        if k == b"uuid":
313
+            uuid = v
314
+        sigbase.append(k + b"=" + v)
315
+
316
+    if uuid is None:
317
+        abort(400)
318
+
319
+    if sig is None:
320
+        return render_template("contact_view_form.html", uuid=uuid.decode())
321
+
322
+    sigcheck = sign(b"&".join(sigbase))
323
+    if not hmac.compare_digest(sig, sigcheck):
324
+        flash("Invalid access key")
325
+        return render_template("contact_view_form.html", uuid=uuid.decode()), 403
326
+
327
+    creq = (
328
+        db.session.query(ContactRequest)
329
+        .filter(ContactRequest.uuid == uuid.decode())
330
+        .first()
331
+    )
332
+    if creq is None:
333
+        abort(404)
334
+
335
+    created_at = as_utc(creq.created_at)
336
+    now = utcnow()
337
+    if now - created_at >= datetime.timedelta(minutes=15):
338
+        response = "Thanks for the feedback!"
339
+    else:
340
+        response = ""
341
+
342
+    return render_template(
343
+        "contact_view.html",
344
+        contact_request=creq,
345
+        response=response,
346
+        sig=sig.hex(),
347
+    )
348
+
349
+
350
+def main():
351
+    db.create_all()
352
+
353
+    if db.session.query(Post).count() == 0:
354
+        db.session.add(
355
+            Post(
356
+                id=0,
357
+                title="My First Blog!",
358
+                content="I programmed it myself! You can even comment (but i haven't implemented approving them yet)!",
359
+                date_posted=datetime.date(2022, 1, 1),
360
+            )
361
+        )
362
+        db.session.add(
363
+            Post(
364
+                id=1,
365
+                title="Suggestions",
366
+                content="Added a feature to submit suggestions for the blog!",
367
+                date_posted=datetime.date(2022, 1, 2),
368
+            )
369
+        )
370
+        db.session.add(
371
+            Post(
372
+                id=2,
373
+                title="Too Early",
374
+                content="I may have made this site public too early...",
375
+                date_posted=datetime.date(2022, 1, 4),
376
+            )
377
+        )
378
+        db.session.commit()
379
+
380
+    port = int(os.environ.get("APP_PORT", 51131))
381
+    app.run(debug=False, host="0.0.0.0", port=port)
382
+
383
+
384
+if __name__ == "__main__":
385
+    main()

+ 12
- 0
Bl0g/docker-compose.yml View File

@@ -0,0 +1,12 @@
1
+version: '2'
2
+services:
3
+    service:
4
+        build: .
5
+        restart: always
6
+        volumes:
7
+          - data:/data:rw
8
+        ports:
9
+          - 51131:51131
10
+
11
+volumes:
12
+  data:

+ 3
- 0
Bl0g/requirements.txt View File

@@ -0,0 +1,3 @@
1
+Flask==2.0.2
2
+bcrypt==3.2.0
3
+Flask-SQLAlchemy==2.5.1

+ 110
- 0
Bl0g/templates/base.html View File

@@ -0,0 +1,110 @@
1
+<!DOCTYPE html>
2
+<html lang="en" class="h-100">
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1">
6
+    <title>{% block title %}{% endblock %}</title>
7
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
8
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
9
+    <style>
10
+      main > .container {
11
+        padding: 60px 15px 0;
12
+      }
13
+      body {
14
+        font-family: monospace;
15
+        font-size: 20px;
16
+      }
17
+      .post {
18
+        background-color: lightblue;
19
+        margin-bottom: 15px;
20
+        padding: 10px;
21
+      }
22
+      nav li {
23
+        display: inline;
24
+      }
25
+      nav ul {
26
+        padding: 0;
27
+        list-style-type: none;
28
+      }
29
+      .date {
30
+        font-size: 14px;
31
+      }
32
+      .unapproved {
33
+        font-size: 14px;
34
+      }
35
+      .comment {
36
+        font-size: 16px;
37
+      }
38
+      h2 {
39
+        margin-top: 5px;
40
+      }
41
+      .footer {
42
+        font-size: 14px;
43
+      }
44
+      a {
45
+        color: darkgreen;
46
+        text-decoration: none;
47
+      }
48
+      a:hover {
49
+        text-decoration: underline;
50
+      }
51
+      .content {
52
+        padding: 3px;
53
+        background-color: rgb(139, 203, 224);
54
+      }
55
+    </style>
56
+  </head>
57
+  <body class="d-flex flex-column h-100">
58
+    <header>
59
+      <nav class="navbar navbar-expand-md navbar-light fixed-top bg-light">
60
+        <div class="container-fluid">
61
+          <a class="navbar-brand" href="/">My first Blog</a>
62
+          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
63
+            <span class="navbar-toggler-icon"></span>
64
+          </button>
65
+          <div class="collapse navbar-collapse" id="navbarCollapse">
66
+            <ul class="navbar-nav me-auto mb-2 mb-md-0">
67
+              <li class="nav-item">
68
+                <a class="nav-link active" aria-current="page" href="/">Home</a>
69
+              </li>
70
+              <li class="nav-item">
71
+                <a class="nav-link" href="/contact">Contact</a>
72
+              </li>
73
+            </ul>
74
+            <div class="d-flex">
75
+              {% if current_user %}
76
+                <form method="POST" action="/logout">
77
+                  <button class="btn btn-outline-success" type="submit">Logout</a>
78
+                </form>
79
+              {% else %}
80
+                <a class="btn btn-outline-success me-1" href="/register">Register</a>
81
+                <a class="btn btn-success" href="/login">Login</a>
82
+              {% endif %}
83
+            </div>
84
+          </div>
85
+        </div>
86
+      </nav>
87
+    </header>
88
+      
89
+    <main class="flex-shrink-0">
90
+      <div class="container">
91
+        {% with messages = get_flashed_messages() %}
92
+          {% for message in messages %}
93
+            <div class="alert alert-warning" role="alert">
94
+              {{ message }}
95
+            </div>              
96
+          {% endfor %}
97
+        {% endwith %}
98
+
99
+        {% block body %}
100
+        {% endblock %}
101
+      </div>
102
+    </main>
103
+      
104
+    <footer class="footer mt-auto py-3 bg-light">
105
+      <div class="container">
106
+        <span class="text-muted">© 2021 ADCS</span>
107
+      </div>
108
+    </footer>
109
+  </body>
110
+</html>

+ 26
- 0
Bl0g/templates/contact.html View File

@@ -0,0 +1,26 @@
1
+{% extends "base.html" %}
2
+{% block title %}Contact{% endblock %}
3
+
4
+{% block body %}
5
+
6
+<h1 class="mt-5">Contact</h1>
7
+<form method="POST">
8
+    <div class="mb-3">
9
+      <label for="title" class="form-label">Title</label>
10
+      <input type="text" class="form-control" id="title" name="title">
11
+    </div>
12
+    <div class="mb-3">
13
+      <label for="content" class="form-label">Content</label>
14
+      <textarea class="form-control" id="content" name="content" row="5"></textarea>
15
+    </div>
16
+
17
+    <button type="submit" class="btn btn-primary">Submit</button>
18
+
19
+    <div class="form-text">Note: only the site's author and you will be able to see the message after it's been sent.</div>
20
+</form>
21
+
22
+<hr>
23
+
24
+<a href="{{ url_for('contact_list') }}">View recent contact requests</a>
25
+
26
+{% endblock %}

+ 18
- 0
Bl0g/templates/contact_list.html View File

@@ -0,0 +1,18 @@
1
+{% extends "base.html" %}
2
+{% block title %}Contact List{% endblock %}
3
+
4
+{% block body %}
5
+
6
+<h1 class="mt-5">Recent Contact Requests</h1>
7
+
8
+<ul>
9
+    {% for creq in recent_requests %}
10
+        <li>
11
+            <a href="{{ url_for('contact_view', uuid=creq.uuid) }}">
12
+                {{ creq.uuid }}
13
+            </a> at {{ creq.created_at.isoformat() }}
14
+        </li>
15
+    {% endfor %}
16
+</ul>
17
+
18
+{% endblock %}

+ 23
- 0
Bl0g/templates/contact_view.html View File

@@ -0,0 +1,23 @@
1
+{% extends "base.html" %}
2
+{% block title %}View Contact Request{% endblock %}
3
+
4
+{% block body %}
5
+
6
+<h1 class="mt-5">See status for <span id="req-title">{{ contact_request.title }}</span></h1>
7
+<p id="req-content">
8
+    {{ contact_request.content }}
9
+</p>
10
+
11
+<hr></hr>
12
+
13
+{% if not response %}
14
+    Status: Not answered yet
15
+{% else %}
16
+    Response: {{ response }}
17
+{% endif %}
18
+
19
+<hr class="mt-5"></hr>
20
+
21
+Your Access Key: {{ sig }}
22
+
23
+{% endblock %}

+ 18
- 0
Bl0g/templates/contact_view_form.html View File

@@ -0,0 +1,18 @@
1
+{% extends "base.html" %}
2
+{% block title %}Access Contact Request{% endblock %}
3
+
4
+{% block body %}
5
+
6
+<h1 class="mt-5">Access contact request {{ uuid }}</h1>
7
+
8
+<form method="get">
9
+    <input value="{{ uuid }}" name="uuid" hidden>
10
+    <div class="mb-2">
11
+        <label for="sig" class="form-label">Access Key</label>
12
+        <input type="text" class="form-control" id="sig" name="sig">
13
+    </div>
14
+
15
+    <button type="submit" class="btn btn-primary">Open</button>
16
+</form>
17
+
18
+{% endblock %}

+ 39
- 0
Bl0g/templates/index.html View File

@@ -0,0 +1,39 @@
1
+{% extends "base.html" %}
2
+{% block title %}Home{% endblock %}
3
+{% block body %}
4
+
5
+{% for post in posts %}
6
+    <div class="post" data-post-id="{{ post.id }}">
7
+        <h2>{{ post.title }}</h2>
8
+        <p class="date">Date: {{ post.date_posted }}</p>
9
+        <p class="content">{{ post.content }}</p>
10
+        {% set usernames = post_comment_usernames[post.id] %}
11
+        {% if post.comments %}
12
+            <p class="unapproved">Unapproved comments from:
13
+                {% for username in usernames %}
14
+                    <a class="unapproved-user" href={{ url_for("user", username=username) }}>{{ username }}</a>
15
+                {% endfor %}
16
+            </p>
17
+        {% endif %}
18
+        {% if current_user %}
19
+            {% set my_comments = post_my_comments[post.id] %}
20
+            {% if my_comments %}
21
+                <p class="comment">
22
+                    Your comment:
23
+                    {% for content in my_comments %}
24
+                        <br><small class="user">{{ content }}</small>
25
+                    {% endfor %}
26
+                </p>
27
+            {% endif %}
28
+            <form method="POST" action="/comment">
29
+                <textarea name="content" rows=3 cols=40 type="longtext"></textarea>
30
+                <input name="post_id" value="{{ post.id }}" hidden><br/>
31
+                <button type="submit">Submit</button>
32
+            </form>
33
+        {% else %}
34
+            <small>Register to comment on this post.</small>
35
+        {% endif %}
36
+    </div>
37
+{% endfor %}
38
+
39
+{% endblock %}

+ 19
- 0
Bl0g/templates/login.html View File

@@ -0,0 +1,19 @@
1
+{% extends "base.html" %}
2
+{% block title %}Login{% endblock %}
3
+{% block body %}
4
+
5
+<h1 class="mt-5">Login</h1>
6
+<form method="POST">
7
+    <div class="mb-3">
8
+      <label for="username" class="form-label">Username</label>
9
+      <input type="text" class="form-control" id="username" name="username">
10
+    </div>
11
+    <div class="mb-3">
12
+      <label for="password" class="form-label">Password</label>
13
+      <input type="password" class="form-control" id="password" name="password">
14
+    </div>
15
+
16
+    <button type="submit" class="btn btn-primary">Login</button>
17
+</form>
18
+
19
+{% endblock %}

+ 17
- 0
Bl0g/templates/register.html View File

@@ -0,0 +1,17 @@
1
+{% extends "base.html" %}
2
+{% block title %}Register{% endblock %}
3
+{% block body %}
4
+<h1 class="mt-5">Register</h1>
5
+<form method="POST">
6
+    <div class="mb-3">
7
+      <label for="username" class="form-label">Username</label>
8
+      <input type="text" class="form-control" id="username" name="username">
9
+    </div>
10
+    <div class="mb-3">
11
+      <label for="password" class="form-label">Password</label>
12
+      <input type="password" class="form-control" id="password" name="password">
13
+    </div>
14
+
15
+    <button type="submit" class="btn btn-primary">Register</button>
16
+</form>
17
+{% endblock %}

+ 15
- 0
Bl0g/templates/user.html View File

@@ -0,0 +1,15 @@
1
+{% extends "base.html" %}
2
+{% block title %}User{% endblock %}
3
+{% block body %}
4
+<h1 class="mt-3">{{ user.username }}</h1>
5
+<p>
6
+    Last login: 
7
+    <span id="last-login">{{ (user.last_login | as_utc).isoformat() }}</span>
8
+</p>
9
+<p>
10
+    Registered:
11
+    <span id="registered">{{ (user.created_at | as_utc).isoformat() }}</span>
12
+</p>
13
+<p>Number of Approved Comments: 0</p>
14
+
15
+{% endblock %}

+ 10
- 0
GUEST_BOOK/app/CMakeLists.txt View File

@@ -0,0 +1,10 @@
1
+cmake_minimum_required(VERSION 3.15)
2
+project(root)
3
+set(CMAKE_CXX_STANDARD 20)
4
+
5
+include(${CMAKE_BINARY_DIR}/conan_paths.cmake)
6
+enable_testing()
7
+
8
+add_subdirectory(app)
9
+add_subdirectory(lib)
10
+

+ 49
- 0
GUEST_BOOK/app/Dockerfile View File

@@ -0,0 +1,49 @@
1
+# build stage
2
+FROM conanio/clang13-ubuntu16.04 as build-stage
3
+
4
+USER 0
5
+
6
+RUN mkdir -p /opt/build
7
+RUN chown -R conan /opt
8
+
9
+USER conan
10
+
11
+WORKDIR /opt
12
+
13
+COPY conan_config conan_config
14
+RUN conan config install conan_config
15
+RUN conan config install
16
+
17
+COPY conanfile.txt  ./
18
+WORKDIR /opt/build
19
+RUN conan install .. --build=missing --profile Guestbook24
20
+
21
+WORKDIR /opt/
22
+COPY CMakeLists.txt ./
23
+
24
+COPY app app
25
+COPY lib lib
26
+COPY data data
27
+
28
+WORKDIR /opt/build
29
+RUN cmake ..
30
+RUN cmake --build . -j 8
31
+# RUN ctest .
32
+
33
+# run stage
34
+FROM ubuntu:21.10 as production-stage
35
+
36
+RUN apt update
37
+RUN apt-get install -y socat libunwind-12
38
+RUN useradd -ms /bin/bash ctf
39
+RUN ln -s /lib/llvm-12/lib/libunwind.so.1 /lib/libllvm-unwind.so.1
40
+
41
+WORKDIR /home/ctf
42
+
43
+COPY --from=build-stage /opt/build/app/Application Application
44
+COPY data data
45
+COPY run.sh run.sh
46
+
47
+RUN chmod +x ./run.sh
48
+
49
+CMD socat tcp-listen:4444,reuseaddr,fork exec:./run.sh,rawer,pty,echo=0,su=ctf

+ 9
- 0
GUEST_BOOK/app/app/CMakeLists.txt View File

@@ -0,0 +1,9 @@
1
+project(Application)
2
+
3
+set(CMAKE_CXX_STANDARD 20)
4
+
5
+add_executable(Application src/main.cpp)
6
+target_link_libraries(Application PRIVATE ApplicationLib)
7
+
8
+file(COPY ${CMAKE_SOURCE_DIR}/data
9
+        DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/)

+ 8
- 0
GUEST_BOOK/app/app/src/main.cpp View File

@@ -0,0 +1,8 @@
1
+#include "Lib.h"
2
+#include <iostream>
3
+
4
+
5
+int main() {
6
+    Application app{std::cin, std::cout};
7
+    app.Run();
8
+}

+ 13
- 0
GUEST_BOOK/app/conan_config/profiles/Guestbook24 View File

@@ -0,0 +1,13 @@
1
+[settings]
2
+os=Linux
3
+os_build=Linux
4
+arch=x86_64
5
+arch_build=x86_64
6
+compiler=clang
7
+compiler.version=13
8
+compiler.libcxx=libstdc++11
9
+build_type=Release
10
+[options]
11
+[conf]
12
+[build_requires]
13
+[env]

+ 98
- 0
GUEST_BOOK/app/conan_config/settings.yml View File

@@ -0,0 +1,98 @@
1
+
2
+# Only for cross building, 'os_build/arch_build' is the system that runs Conan
3
+os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS, AIX]
4
+arch_build: [x86, x86_64, ppc32be, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x, sh4le]
5
+
6
+# Only for building cross compilation tools, 'os_target/arch_target' is the system for
7
+# which the tools generate code
8
+os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, AIX, Arduino, Neutrino]
9
+arch_target: [x86, x86_64, ppc32be, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x, asm.js, wasm, sh4le]
10
+
11
+# Rest of the settings are "host" settings:
12
+# - For native building/cross building: Where the library/program will run.
13
+# - For building cross compilation tools: Where the cross compiler will run.
14
+os:
15
+    Windows:
16
+        subsystem: [None, cygwin, msys, msys2, wsl]
17
+    WindowsStore:
18
+        version: ["8.1", "10.0"]
19
+    WindowsCE:
20
+        platform: ANY
21
+        version: ["5.0", "6.0", "7.0", "8.0"]
22
+    Linux:
23
+    Macos:
24
+        version: [None, "10.6", "10.7", "10.8", "10.9", "10.10", "10.11", "10.12", "10.13", "10.14", "10.15", "11.0"]
25
+    Android:
26
+        api_level: ANY
27
+    iOS:
28
+        version: ["7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1", "12.2", "12.3", "12.4", "13.0", "13.1", "13.2", "13.3", "13.4", "13.5", "13.6"]
29
+    watchOS:
30
+        version: ["4.0", "4.1", "4.2", "4.3", "5.0", "5.1", "5.2", "5.3", "6.0", "6.1"]
31
+    tvOS:
32
+        version: ["11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1", "12.2", "12.3", "12.4", "13.0"]
33
+    FreeBSD:
34
+    SunOS:
35
+    AIX:
36
+    Arduino:
37
+        board: ANY
38
+    Emscripten:
39
+    Neutrino:
40
+        version: ["6.4", "6.5", "6.6", "7.0", "7.1"]
41
+arch: [x86, x86_64, ppc32be, ppc32, ppc64le, ppc64, armv4, armv4i, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x, asm.js, wasm, sh4le]
42
+compiler:
43
+    sun-cc:
44
+        version: ["5.10", "5.11", "5.12", "5.13", "5.14", "5.15"]
45
+        threads: [None, posix]
46
+        libcxx: [libCstd, libstdcxx, libstlport, libstdc++]
47
+    gcc: &gcc
48
+        version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
49
+                  "5", "5.1", "5.2", "5.3", "5.4", "5.5",
50
+                  "6", "6.1", "6.2", "6.3", "6.4", "6.5",
51
+                  "7", "7.1", "7.2", "7.3", "7.4", "7.5",
52
+                  "8", "8.1", "8.2", "8.3", "8.4",
53
+                  "9", "9.1", "9.2", "9.3",
54
+                  "10", "10.1"]
55
+        libcxx: [libstdc++, libstdc++11]
56
+        threads: [None, posix, win32] #  Windows MinGW
57
+        exception: [None, dwarf2, sjlj, seh] # Windows MinGW
58
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
59
+    Visual Studio: &visual_studio
60
+        runtime: [MD, MT, MTd, MDd]
61
+        version: ["8", "9", "10", "11", "12", "14", "15", "16"]
62
+        toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp,
63
+                  v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
64
+                  LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
65
+                  LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2, v142,
66
+                  llvm, ClangCL]
67
+        cppstd: [None, 14, 17, 20]
68
+    clang:
69
+        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
70
+                  "5.0", "6.0", "7.0", "7.1",
71
+                  "8", "9", "10", "11","12","13"]
72
+        libcxx: [None, libstdc++, libstdc++11, libc++, c++_shared, c++_static]
73
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
74
+        runtime: [None, MD, MT, MTd, MDd]
75
+    apple-clang: &apple_clang
76
+        version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", "9.1", "10.0", "11.0", "12.0"]
77
+        libcxx: [libstdc++, libc++]
78
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
79
+    intel:
80
+        version: ["11", "12", "13", "14", "15", "16", "17", "18", "19", "19.1"]
81
+        base:
82
+            gcc:
83
+                <<: *gcc
84
+                threads: [None]
85
+                exception: [None]
86
+            Visual Studio:
87
+                <<: *visual_studio
88
+            apple-clang:
89
+                <<: *apple_clang
90
+    qcc:
91
+        version: ["4.4", "5.4", "8.3"]
92
+        libcxx: [cxx, gpp, cpp, cpp-ne, accp, acpp-ne, ecpp, ecpp-ne]
93
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17]
94
+
95
+build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
96
+
97
+
98
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]  # Deprecated, use compiler.cppstd

+ 12
- 0
GUEST_BOOK/app/conanfile.txt View File

@@ -0,0 +1,12 @@
1
+[requires]
2
+spdlog/1.8.1
3
+catch2/2.13.6
4
+abseil/20211102.0
5
+libpqxx/7.5.2
6
+cryptopp/8.5.0
7
+
8
+[generators]
9
+cmake_find_package
10
+cmake_paths
11
+
12
+

+ 7
- 0
GUEST_BOOK/app/data/drop.sql View File

@@ -0,0 +1,7 @@
1
+drop table if exists users;
2
+drop table if exists user_messages;
3
+drop table if exists message;
4
+drop table if exists quote;
5
+drop SEQUENCE if exists UsersId;
6
+drop SEQUENCE if exists MessageId;
7
+drop SEQUENCE if exists QuoteId;

+ 36
- 0
GUEST_BOOK/app/data/init.sql View File

@@ -0,0 +1,36 @@
1
+create sequence if not exists usersid;
2
+create sequence if not exists messageid;
3
+create sequence if not exists quoteid;
4
+
5
+create table if not exists users
6
+(
7
+    id                          integer not null default nextval('usersid'),
8
+    username                    bytea   not null unique,
9
+    password                    bytea   not null,
10
+    firstname                   bytea   not null default '',
11
+    lastname                    bytea   not null default '',
12
+    top_secret_user_information bytea   not null default ''
13
+);
14
+
15
+create table if not exists user_messages
16
+(
17
+    username   bytea not null,
18
+    message_id bytea not null
19
+);
20
+
21
+create table if not exists message
22
+(
23
+    db_id   integer not null default nextval('messageid'),
24
+    id      bytea   not null,
25
+    title   bytea   not null,
26
+    message bytea            default '',
27
+    public  boolean          default true
28
+);
29
+
30
+create table if not exists quote
31
+(
32
+    db_id      integer not null        default nextval('quoteid'),
33
+    id         bytea   not null,
34
+    message_id bytea   not null,
35
+    quote      bytea   not null unique default ''
36
+);

+ 25
- 0
GUEST_BOOK/app/lib/CMakeLists.txt View File

@@ -0,0 +1,25 @@
1
+project(ApplicationLib VERSION 0.0.1 DESCRIPTION "")
2
+
3
+set(CMAKE_CXX_STANDARD 20)
4
+
5
+add_library(ApplicationLib STATIC src/Application.cpp src/Logger.cpp src/Commands.cpp src/LoadMyEntriesCommand.cpp src/RegisterCommand.cpp src/PostCommand.cpp src/LoadEntriesCommand.cpp src/QuoteCommand.cpp src/LoginCommand.cpp src/EditCommand.cpp src/PostCommand.cpp src/ViewCommand.cpp src/Database.cpp src/Util.cpp src/DbModel.cpp)
6
+
7
+find_package(spdlog REQUIRED)
8
+find_package(absl REQUIRED)
9
+find_package(libpqxx REQUIRED)
10
+find_package(cryptopp REQUIRED)
11
+target_link_libraries(ApplicationLib PUBLIC spdlog::spdlog)
12
+target_link_libraries(ApplicationLib PUBLIC absl::absl)
13
+target_link_libraries(ApplicationLib PUBLIC libpqxx::libpqxx)
14
+target_link_libraries(ApplicationLib PUBLIC cryptopp::cryptopp)
15
+
16
+
17
+set_target_properties(ApplicationLib PROPERTIES VERSION ${PROJECT_VERSION})
18
+set_target_properties(ApplicationLib PROPERTIES SOVERSION 0)
19
+set_target_properties(ApplicationLib PROPERTIES PUBLIC_HEADER include/Lib.h)
20
+
21
+# https://github.com/pabloariasal/modern-cmake-sample/blob/master/libjsonutils/CMakeLists.txt
22
+target_include_directories(ApplicationLib
23
+        PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
24
+        PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
25
+)

+ 19
- 0
GUEST_BOOK/app/lib/include/Application.h View File

@@ -0,0 +1,19 @@
1
+#pragma once
2
+
3
+#include <absl/strings/str_split.h>
4
+#include <functional>
5
+#include <istream>
6
+#include <ostream>
7
+#include <vector>
8
+
9
+class Application {
10
+private:
11
+    std::basic_istream<char> &istream;
12
+    std::basic_ostream<char> &ostream;
13
+
14
+public:
15
+    explicit Application(std::basic_istream<char> &istream, std::basic_ostream<char> &ostream) : istream{istream}, ostream{ostream} {}
16
+
17
+    void Run() noexcept;
18
+    Application& Drop() noexcept;
19
+};

+ 10
- 0
GUEST_BOOK/app/lib/include/Command.h View File

@@ -0,0 +1,10 @@
1
+#pragma once
2
+#include "Context.h"
3
+#include <string>
4
+
5
+class Command {
6
+public:
7
+    Command() = default;
8
+    virtual ~Command() = default;
9
+    virtual void Execute(Context&, std::basic_ostream<char>&, std::string) const = 0;
10
+};

+ 17
- 0
GUEST_BOOK/app/lib/include/Commands.h View File

@@ -0,0 +1,17 @@
1
+#pragma once
2
+#include <Command.h>
3
+#include <functional>
4
+#include <memory>
5
+#include <string>
6
+
7
+
8
+class Commands {
9
+    std::unordered_map<std::string, std::function<std::shared_ptr<Command>()>> commands;
10
+
11
+public:
12
+    void Execute(Context& c, std::basic_ostream<char>& s, const std::string &line);
13
+
14
+    static Commands *Get();
15
+
16
+    void Register(const std::string &string, const std::function<std::shared_ptr<Command>()> &);
17
+};

+ 6
- 0
GUEST_BOOK/app/lib/include/Context.h View File

@@ -0,0 +1,6 @@
1
+#pragma once
2
+#include <string>
3
+class Context {
4
+public:
5
+    std::string user{};
6
+};

+ 38
- 0
GUEST_BOOK/app/lib/include/DBHelper.h View File

@@ -0,0 +1,38 @@
1
+#pragma once
2
+#include <pqxx/connection>
3
+#include <string>
4
+#include <unordered_map>
5
+#include <vector>
6
+#include <variant>
7
+
8
+
9
+
10
+using FieldType = std::variant<std::string,bool>;
11
+
12
+template<typename T>
13
+concept Insertable = requires(T a) {
14
+    { a.GetTableName() } -> std::convertible_to<std::string>;
15
+    { T::GetTableName() } -> std::convertible_to<std::string>;
16
+    { a.GetFields() } -> std::convertible_to<std::unordered_map<std::string, FieldType>>;
17
+};
18
+
19
+template<typename T>
20
+concept Queryable = requires(T a, pqxx::result result) {
21
+    { a.GetTableName() } -> std::convertible_to<std::string>;
22
+    { T::GetTableName() } -> std::convertible_to<std::string>;
23
+    { a.GetFields() } -> std::convertible_to<std::unordered_map<std::string, FieldType>>;
24
+    { T::FromResult(result) } -> std::convertible_to<std::vector<T>>;
25
+};
26
+
27
+#define TableName(X) \
28
+    static std::string GetTableName() { return #X; }
29
+
30
+
31
+#define Fields(...)                                            \
32
+    std::unordered_map<std::string, FieldType> GetFields() { \
33
+        return {__VA_ARGS__};                                  \
34
+    }
35
+
36
+
37
+#define Field(X, Y) \
38
+    { #X, Y }

+ 258
- 0
GUEST_BOOK/app/lib/include/Database.h View File

@@ -0,0 +1,258 @@
1
+#pragma once
2
+
3
+#include "DBHelper.h"
4
+#include "Logger.h"
5
+#include <absl/strings/str_cat.h>
6
+#include <absl/strings/str_join.h>
7
+#include <functional>
8
+#include <iostream>
9
+#include <libpq-fe.h>
10
+#include <memory>
11
+#include <pqxx/pqxx>
12
+#include <string>
13
+#include <utility>
14
+
15
+class Condition {
16
+private:
17
+    using value_t = std::variant<pqxx::binarystring, std::vector<pqxx::binarystring>, bool>;
18
+    explicit Condition(std::string condition, std::string field, value_t value) : condition{std::move(condition)}, field{std::move(field)}, value{std::move(value)} {};
19
+    std::string condition;
20
+    std::string field;
21
+    value_t value;
22
+
23
+public:
24
+    static Condition Eq(const std::string &field, const std::string &value) {
25
+        return Condition{"{} = ${}", field, pqxx::binarystring(value)};
26
+    }
27
+
28
+    static Condition In(const std::string &field, const std::vector<std::string> &values) {
29
+        std::vector<pqxx::binarystring> vector;
30
+        std::transform(values.begin(), values.end(), std::back_inserter(vector), [](auto &v) {
31
+            return pqxx::binarystring(v);
32
+        });
33
+
34
+        return Condition{"{} = ANY(${})", field, vector};
35
+    }
36
+
37
+    std::string ToString(int i) {
38
+        return fmt::format(condition, field, i + 1);
39
+    }
40
+
41
+    value_t &GetParam() {
42
+        return value;
43
+    }
44
+
45
+    const std::string &GetField() {
46
+        return field;
47
+    }
48
+};
49
+
50
+class Updates {
51
+private:
52
+    explicit Updates(std::string field, std::string value) : field{std::move(field)}, value{std::move(value)} {};
53
+    std::string field;
54
+    std::string value;
55
+
56
+public:
57
+    static Updates Set(const std::string &field, const std::string &value) {
58
+        return Updates{field, value};
59
+    }
60
+
61
+    std::string ToString(int i) {
62
+        return fmt::format("{} = ${}", field, i + 1);
63
+    }
64
+
65
+    std::string GetParam() {
66
+        return value;
67
+    }
68
+
69
+    const std::string &GetField() {
70
+        return field;
71
+    }
72
+};
73
+
74
+
75
+class Database {
76
+private:
77
+    explicit Database(pqxx::connection connection);
78
+    static Database Create();
79
+    pqxx::connection connection;
80
+
81
+public:
82
+    static Database &Get();
83
+    template<Insertable T>
84
+    void Insert(T insertable) {
85
+        auto fields = insertable.GetFields();
86
+
87
+        pqxx::work w(connection);
88
+
89
+        std::string names = "";
90
+        std::string valuesPlaceholder = "";
91
+        pqxx::params values;
92
+        int i = 0;
93
+        for (auto &[fieldName, value] : fields) {
94
+            absl::StrAppend(&names, fieldName, ",");
95
+            absl::StrAppend(&valuesPlaceholder, "$", i + 1, ",");
96
+
97
+            std::visit([&values, &w](auto a) {
98
+                using U = std::decay_t<decltype(a)>;
99
+                if constexpr (std::is_same<U, std::string>()) {
100
+                    values.append(w.esc_raw(a));
101
+                } else if constexpr (std::is_same<U, bool>()) {
102
+                    values.append(std::to_string(a));
103
+                } else {
104
+                    values.append(a);
105
+                }
106
+            },
107
+                       value);
108
+
109
+            i++;
110
+        }
111
+        names.resize(names.size() - 1);
112
+        valuesPlaceholder.resize(valuesPlaceholder.size() - 1);
113
+
114
+        auto command = fmt::format("INSERT INTO {}({}) VALUES({})", insertable.GetTableName(), names, valuesPlaceholder);
115
+        console->info("{}", command);
116
+
117
+
118
+        //statement template
119
+        connection.prepare("", command);
120
+        //invocation as in varible binding
121
+        pqxx::result r = w.exec_prepared("", values);
122
+
123
+        w.commit();
124
+    }
125
+
126
+
127
+    template<Queryable T>
128
+    std::vector<T> Query(std::vector<Condition> conditions) {
129
+        pqxx::work w(connection);
130
+        auto command = fmt::format("SELECT * FROM {}", T::GetTableName());
131
+        pqxx::params values;
132
+        if (conditions.size() > 0) {
133
+            absl::StrAppend(&command, " WHERE ", conditions.at(0).ToString(0));
134
+            auto &param = conditions.at(0).GetParam();
135
+            std::visit([&values, &w](auto a) {
136
+                using U = std::decay_t<decltype(a)>;
137
+                if constexpr (std::is_same<U, pqxx::binarystring>()) {
138
+                    values.append(w.esc_raw(a.data(), a.size()));
139
+                } else {
140
+                    values.append(a);
141
+                }
142
+            },
143
+                       param);
144
+        }
145
+
146
+        for (int i = 1; i < conditions.size(); ++i) {
147
+            absl::StrAppend(&command, " AND ", conditions.at(i).ToString(i));
148
+            auto &param = conditions.at(i).GetParam();
149
+
150
+            std::visit([&values, &w](auto a) {
151
+                using U = std::decay_t<decltype(a)>;
152
+                if constexpr (std::is_same<U, pqxx::binarystring>()) {
153
+                    values.append(w.esc_raw(a.data(), a.size()));
154
+                } else {
155
+                    values.append(a);
156
+                }
157
+            },
158
+                       param);
159
+        }
160
+        console->info("{}", command);
161
+
162
+
163
+        connection.prepare("", command);
164
+        pqxx::result r = w.exec_prepared("", values);
165
+
166
+        w.commit();
167
+
168
+        return T::FromResult(r);
169
+    }
170
+
171
+
172
+    pqxx::result QueryResult(const std::string &command, const std::vector<std::string> &params) {
173
+        pqxx::work w(connection);
174
+
175
+        pqxx::params values;
176
+        for (auto &param : params) {
177
+            values.append(param);
178
+            //            std::visit([&values, &w](auto a) {
179
+            //                using U = std::decay_t<decltype(a)>;
180
+            //                if constexpr (std::is_same<U, pqxx::binarystring>()) {
181
+            //                    values.append(w.esc_raw(a.data(), a.size()));
182
+            //                } else {
183
+            //                    values.append(a);
184
+            //                }
185
+            //            },
186
+            //                       param);
187
+        }
188
+        console->info("{}", command);
189
+
190
+        connection.prepare("", command);
191
+        pqxx::result r = w.exec_prepared("", values);
192
+
193
+        w.commit();
194
+
195
+        return r;
196
+    }
197
+
198
+
199
+    std::vector<std::unordered_map<std::string, std::string>> Query(const std::string &command, const std::vector<std::string> &params) {
200
+        auto r = QueryResult(command, params);
201
+        std::vector<std::unordered_map<std::string, std::string>> v;
202
+        for (auto const &row : r) {
203
+            auto &map = v.emplace_back();
204
+            for (int i = 0; i < row.size(); i++) {
205
+
206
+                if(r.column_type(i) == 17) {
207
+                    map[r.column_name(i)] = row.at(i).as<pqxx::binarystring>().str();
208
+                } else {
209
+                    map[r.column_name(i)] = row.at(i).as<std::string>();
210
+                }
211
+
212
+            }
213
+        }
214
+        return v;
215
+    }
216
+
217
+    template<Queryable T>
218
+    std::vector<T> Update(std::vector<Condition> conditions, std::vector<Updates> updates) {
219
+        auto command = fmt::format("update {}", T::GetTableName());
220
+
221
+        pqxx::params values;
222
+
223
+
224
+        if (updates.size() > 0) {
225
+            absl::StrAppend(&command, " SET ", updates.at(0).ToString(+0));
226
+            values.append(updates.at(0).GetParam());
227
+        }
228
+        for (int i = 1; i < updates.size(); ++i) {
229
+            absl::StrAppend(&command, ", ", updates.at(i).ToString(i));
230
+            values.append(updates.at(i).GetParam());
231
+        }
232
+
233
+        if (conditions.size() > 0) {
234
+            absl::StrAppend(&command, " WHERE ", conditions.at(0).ToString(updates.size() + 0));
235
+            values.append(conditions.at(0).GetParam());
236
+        }
237
+        for (int i = 1; i < conditions.size(); ++i) {
238
+            absl::StrAppend(&command, " AND ", conditions.at(i).ToString(updates.size() + i));
239
+            values.append(conditions.at(i).GetParam());
240
+        }
241
+
242
+        console->info("{}", command);
243
+
244
+
245
+        pqxx::work w(connection);
246
+
247
+        connection.prepare("", command);
248
+        pqxx::result r = w.exec_prepared("", values);
249
+
250
+        w.commit();
251
+
252
+        return T::FromResult(r);
253
+    }
254
+
255
+
256
+    void Init();
257
+    void Drop();
258
+};

+ 174
- 0
GUEST_BOOK/app/lib/include/DbModel.h View File

@@ -0,0 +1,174 @@
1
+#pragma once
2
+#include "DBHelper.h"
3
+#include "Util.h"
4
+#include <pqxx/result>
5
+#include <string>
6
+#include <utility>
7
+
8
+struct User {
9
+private:
10
+    User() = default;
11
+
12
+public:
13
+    std::string username;
14
+    std::string password;
15
+    std::string firstname;
16
+    std::string lastname;
17
+    std::string top_secret_user_information;
18
+
19
+    User(std::string username, const std::string &password) : username(std::move(username)), password(Hash(password)) {}
20
+
21
+    TableName(Users);
22
+    Fields(Field(username, username),
23
+           Field(password, password),
24
+           Field(firstname, firstname),
25
+           Field(lastname, lastname),
26
+           Field(top_secret_user_information, top_secret_user_information), );
27
+
28
+    static std::vector<User> FromResult(const pqxx::result &result) {
29
+        std::vector<User> v;
30
+        for (auto const &row : result) {
31
+            User &user = v.emplace_back<User>({});
32
+            for (int i = 0; i < row.size(); i++) {
33
+                auto field = row.at(i);
34
+                if (std::string_view(result.column_name(i)) == "username") {
35
+                    user.username = field.as<pqxx::binarystring>().str();
36
+                }
37
+                if (std::string_view(result.column_name(i)) == "password") {
38
+                    user.password = field.as<pqxx::binarystring>().str();
39
+                }
40
+                if (std::string_view(result.column_name(i)) == "firstname") {
41
+                    user.firstname = field.as<pqxx::binarystring>().str();
42
+                }
43
+                if (std::string_view(result.column_name(i)) == "lastname") {
44
+                    user.lastname = field.as<pqxx::binarystring>().str();
45
+                }
46
+                if (std::string_view(result.column_name(i)) == "top_secret_user_information") {
47
+                    user.top_secret_user_information = field.as<pqxx::binarystring>().str();
48
+                }
49
+            }
50
+        }
51
+        return v;
52
+    }
53
+};
54
+
55
+struct Message {
56
+private:
57
+    Message() = default;
58
+
59
+public:
60
+    //    int db_id{};
61
+    std::string id;
62
+    std::string text;
63
+    std::string title;
64
+    bool pub{};
65
+
66
+    Message(/*int dbId,*/ std::string ids, std::string title, std::string text, bool pub);
67
+
68
+
69
+    TableName(Message);
70
+    Fields(
71
+            /*Field(db_id, std::to_string(db_id)),*/
72
+            Field(id, id),
73
+            Field(message, text),
74
+            Field(title, title),
75
+            Field(public, pub));
76
+
77
+    static std::vector<Message> FromResult(const pqxx::result &result) {
78
+        std::vector<Message> v;
79
+        for (auto const &row : result) {
80
+            Message &msg = v.emplace_back<Message>({});
81
+            for (int i = 0; i < row.size(); i++) {
82
+                auto field = row.at(i);
83
+                /*                if (std::string_view(result.column_name(i)) == "db_id") {
84
+                    msg.db_id = field.as<int>();
85
+                }*/
86
+                if (std::string_view(result.column_name(i)) == "id") {
87
+                    msg.id = field.as<pqxx::binarystring>().str();
88
+                }
89
+                if (std::string_view(result.column_name(i)) == "message") {
90
+                    msg.text = field.as<pqxx::binarystring>().str();
91
+                }
92
+                if (std::string_view(result.column_name(i)) == "title") {
93
+                    msg.title = field.as<pqxx::binarystring>().str();
94
+                }
95
+                if (std::string_view(result.column_name(i)) == "public") {
96
+                    msg.pub = field.as<bool>();
97
+                }
98
+            }
99
+        }
100
+        return v;
101
+    }
102
+};
103
+
104
+
105
+struct Quote {
106
+private:
107
+    Quote() = default;
108
+
109
+public:
110
+    std::string id;
111
+    std::string messageId;
112
+    std::string text;
113
+
114
+    Quote(std::string id, std::string messageId, std::string text) : id(std::move(id)), messageId(std::move(messageId)), text(std::move(text)) {}
115
+
116
+    TableName(Quote);
117
+    Fields(
118
+            Field(id, id),
119
+            Field(message_id, messageId),
120
+            Field(quote, text));
121
+
122
+    static std::vector<Quote> FromResult(const pqxx::result &result) {
123
+        std::vector<Quote> v;
124
+        for (auto const &row : result) {
125
+            Quote &msg = v.emplace_back<Quote>({});
126
+            for (int i = 0; i < row.size(); i++) {
127
+                auto field = row.at(i);
128
+                if (std::string_view(result.column_name(i)) == "id") {
129
+                    msg.id = field.as<pqxx::binarystring>().str();
130
+                }
131
+                if (std::string_view(result.column_name(i)) == "message_id") {
132
+                    msg.messageId = field.as<pqxx::binarystring>().str();
133
+                }
134
+                if (std::string_view(result.column_name(i)) == "quote") {
135
+                    msg.text = field.as<pqxx::binarystring>().str();
136
+                }
137
+            }
138
+        }
139
+        return v;
140
+    }
141
+};
142
+
143
+
144
+struct UserMessage {
145
+private:
146
+    UserMessage() = default;
147
+
148
+public:
149
+    std::string username;
150
+    std::string messageId;
151
+
152
+    UserMessage(std::string username, std::string messageId) : username(std::move(username)), messageId(std::move(messageId)) {}
153
+
154
+    TableName(user_messages)
155
+            Fields(Field(message_id, messageId),
156
+                   Field(username, username));
157
+
158
+    static std::vector<UserMessage> FromResult(const pqxx::result &result) {
159
+        std::vector<UserMessage> v;
160
+        for (auto const &row : result) {
161
+            UserMessage &msg = v.emplace_back<UserMessage>({});
162
+            for (int i = 0; i < row.size(); i++) {
163
+                auto field = row.at(i);
164
+                if (std::string_view(result.column_name(i)) == "message_id") {
165
+                    msg.messageId = field.as<pqxx::binarystring>().str();
166
+                }
167
+                if (std::string_view(result.column_name(i)) == "user_id") {
168
+                    msg.username = field.as<pqxx::binarystring>().str();
169
+                }
170
+            }
171
+        }
172
+        return v;
173
+    }
174
+};

+ 7
- 0
GUEST_BOOK/app/lib/include/EditCommand.h View File

@@ -0,0 +1,7 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class EditCommand : public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};

+ 9
- 0
GUEST_BOOK/app/lib/include/Lib.h View File

@@ -0,0 +1,9 @@
1
+#pragma once
2
+#include "Application.h"
3
+#include "Command.h"
4
+#include "Commands.h"
5
+#include "Logger.h"
6
+#include "RegisterCommand.h"
7
+#include "Database.h"
8
+#include "DBHelper.h"
9
+#include "Util.h"

+ 7
- 0
GUEST_BOOK/app/lib/include/LoadEntriesCommand.h View File

@@ -0,0 +1,7 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class LoadEntriesCommand : public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};

+ 7
- 0
GUEST_BOOK/app/lib/include/LoadMyEntriesCommand.h View File

@@ -0,0 +1,7 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class LoadMyEntriesCommand : public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};

+ 5
- 0
GUEST_BOOK/app/lib/include/Logger.h View File

@@ -0,0 +1,5 @@
1
+#pragma once
2
+#include "spdlog/spdlog.h"
3
+#include "spdlog/sinks/stdout_color_sinks.h"
4
+
5
+extern std::shared_ptr<spdlog::logger> console;

+ 9
- 0
GUEST_BOOK/app/lib/include/LoginCommand.h View File

@@ -0,0 +1,9 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class LoginCommand: public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};
8
+
9
+

+ 7
- 0
GUEST_BOOK/app/lib/include/PostCommand.h View File

@@ -0,0 +1,7 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class PostCommand : public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};

+ 7
- 0
GUEST_BOOK/app/lib/include/QuoteCommand.h View File

@@ -0,0 +1,7 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class QuoteCommand : public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};

+ 9
- 0
GUEST_BOOK/app/lib/include/RegisterCommand.h View File

@@ -0,0 +1,9 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class RegisterCommand: public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};
8
+
9
+

+ 28
- 0
GUEST_BOOK/app/lib/include/Util.h View File

@@ -0,0 +1,28 @@
1
+#pragma once
2
+#include <exception>
3
+#include <string>
4
+#include <absl/strings/str_split.h>
5
+std::string Hash(const std::string &s);
6
+std::string HashMD5(const std::string &s);
7
+
8
+template<typename T>
9
+concept Container = requires(T v, std::string key) {
10
+    { std::find(v.begin(), v.end(), key) != v.end() } -> std::convertible_to<bool>;
11
+};
12
+
13
+
14
+template<Container T>
15
+bool Contains(T v, std::string key) {
16
+    if (std::find(v.begin(), v.end(), key) != v.end()) {
17
+        return true;
18
+    }
19
+    return false;
20
+}
21
+
22
+
23
+#define APP_ASSERT(cond, msg)          \
24
+    if (!(cond)) {                     \
25
+        throw std::runtime_error(msg); \
26
+    }
27
+
28
+std::vector<std::string> SplitAndVerify(const std::string &line, size_t number);

+ 9
- 0
GUEST_BOOK/app/lib/include/ViewCommand.h View File

@@ -0,0 +1,9 @@
1
+#pragma once
2
+#include "Command.h"
3
+
4
+class ViewCommand: public Command {
5
+public:
6
+    void Execute(Context&, std::basic_ostream<char>&, std::string) const override;
7
+};
8
+
9
+

+ 54
- 0
GUEST_BOOK/app/lib/src/Application.cpp View File

@@ -0,0 +1,54 @@
1
+#include "Application.h"
2
+#include "Logger.h"
3
+#include <Commands.h>
4
+#include <DBHelper.h>
5
+#include <Database.h>
6
+#include <RegisterCommand.h>
7
+#include <Util.h>
8
+
9
+#include <DbModel.h>
10
+#include <EditCommand.h>
11
+#include <LoadEntriesCommand.h>
12
+#include <LoadMyEntriesCommand.h>
13
+#include <LoginCommand.h>
14
+#include <PostCommand.h>
15
+#include <QuoteCommand.h>
16
+#include <ViewCommand.h>
17
+#include <utility>
18
+
19
+void Application::Run() noexcept {
20
+    Database::Get().Init();
21
+    Commands::Get()
22
+            ->Register("register", []() { return std::make_shared<RegisterCommand>(); });
23
+    Commands::Get()
24
+            ->Register("login", []() { return std::make_shared<LoginCommand>(); });
25
+    Commands::Get()
26
+            ->Register("edit-profile", []() { return std::make_shared<EditCommand>(); });
27
+    Commands::Get()
28
+            ->Register("view-profile", []() { return std::make_shared<ViewCommand>(); });
29
+    Commands::Get()
30
+            ->Register("load-entries", []() { return std::make_shared<LoadEntriesCommand>(); });
31
+    Commands::Get()
32
+            ->Register("load-my-entries", []() { return std::make_shared<LoadMyEntriesCommand>(); });
33
+    Commands::Get()
34
+            ->Register("post-entry", []() { return std::make_shared<PostCommand>(); });
35
+    Commands::Get()
36
+            ->Register("quote-entry", []() { return std::make_shared<QuoteCommand>(); });
37
+
38
+    Context c{};
39
+
40
+    std::string line;
41
+    while (std::getline(istream, line)) {
42
+        try {
43
+            console->info("Got message <{}>", line);
44
+            Commands::Get()->Execute(c, ostream, std::string(absl::StripAsciiWhitespace(line)));
45
+        } catch (std::exception &e) {
46
+            ostream << e.what() << std::endl;
47
+        }
48
+    }
49
+}
50
+
51
+Application &Application::Drop() noexcept {
52
+    Database::Get().Drop();
53
+    return *this;
54
+}

+ 19
- 0
GUEST_BOOK/app/lib/src/Commands.cpp View File

@@ -0,0 +1,19 @@
1
+#include "Commands.h"
2
+#include <absl/strings/str_split.h>
3
+#include <memory>
4
+void Commands::Execute(Context &c, std::basic_ostream<char> &s, const std::string &line) {
5
+    std::vector<std::string> parts = absl::StrSplit(line, "|");
6
+    if (commands.contains(parts[0])) {
7
+        commands.at(parts[0])()->Execute(c, s, line);
8
+    } else {
9
+        s << "error command unknown" << std::endl;
10
+    }
11
+}
12
+
13
+Commands *Commands::Get() {
14
+    static auto *commands = new Commands();
15
+    return commands;
16
+}
17
+void Commands::Register(const std::string &string, const std::function<std::shared_ptr<Command>()> &factory) {
18
+    commands.emplace(string, factory);
19
+}

+ 35
- 0
GUEST_BOOK/app/lib/src/Database.cpp View File

@@ -0,0 +1,35 @@
1
+#include "Database.h"
2
+#include <Logger.h>
3
+#include <fstream>
4
+#include <functional>
5
+#include <pqxx/pqxx>
6
+Database &Database::Get() {
7
+    static Database db = Create();
8
+    return db;
9
+}
10
+
11
+Database Database::Create() {
12
+    return Database(pqxx::connection("postgresql://postgres:Password1@localhost/postgres"));
13
+}
14
+Database::Database(pqxx::connection connection) : connection{std::move(connection)} {
15
+}
16
+void Database::Init() {
17
+    std::ifstream t("data/init.sql");
18
+    std::stringstream initScript;
19
+    initScript << t.rdbuf();
20
+
21
+    pqxx::work w(connection);
22
+    pqxx::result r = w.exec(initScript.str());
23
+
24
+    w.commit();
25
+}
26
+void Database::Drop() {
27
+    std::ifstream t("data/drop.sql");
28
+    std::stringstream initScript;
29
+    initScript << t.rdbuf();
30
+
31
+    pqxx::work w(connection);
32
+    pqxx::result r = w.exec(initScript.str());
33
+
34
+    w.commit();
35
+}

+ 5
- 0
GUEST_BOOK/app/lib/src/DbModel.cpp View File

@@ -0,0 +1,5 @@
1
+#include "DbModel.h"
2
+
3
+#include <utility>
4
+
5
+Message::Message(/*int dbId,*/ std::string id, std::string title, std::string text, bool pub) : /*db_id(dbId),*/ id(std::move(id)), text(std::move(text)), title(std::move(title)), pub(pub) {}

+ 27
- 0
GUEST_BOOK/app/lib/src/EditCommand.cpp View File

@@ -0,0 +1,27 @@
1
+#include "EditCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+
11
+void EditCommand::Execute(Context &c, std::basic_ostream<char> &ostream, std::string string) const {
12
+    static constexpr std::array validFields = {"firstname", "lastname", "top_secret_user_information"};
13
+    console->info("EditCommand");
14
+    std::vector<std::string> parts = SplitAndVerify(string, 4);
15
+
16
+    auto &field = parts.at(2);
17
+    auto &value = parts.at(3);
18
+
19
+    APP_ASSERT(!c.user.empty(), "error not logged in")
20
+    APP_ASSERT(Contains(validFields, field), "error field does not exist")
21
+
22
+    Database::Get().Update<User>({Condition::Eq("username", c.user)},
23
+                                 {Updates::Set(field, value)});
24
+
25
+
26
+    ostream << "ok" << std::endl;
27
+}

+ 46
- 0
GUEST_BOOK/app/lib/src/LoadEntriesCommand.cpp View File

@@ -0,0 +1,46 @@
1
+#include "LoadEntriesCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+
11
+void LoadEntriesCommand::Execute(Context &c, std::basic_ostream<char> &ostream, std::string string) const {
12
+    console->info("LoadEntriesCommand");
13
+    std::vector<std::string> parts = SplitAndVerify(string, 1);
14
+
15
+    auto quotes = Database::Get().Query<Quote>({});
16
+
17
+    auto posts = Database::Get().Query("select m.*, username from user_messages join message m on user_messages.message_id = m.id",{});
18
+
19
+
20
+    ostream << "ok" << '\n';
21
+    ostream << "posts:" << '\n';
22
+    for (auto &post : posts) {
23
+        ostream << "id: " << post["id"] << '\n';
24
+        ostream << "username: " << post["username"] << '\n';
25
+        ostream << "public: " << ((post["public"] == "t") ? "true" : "false") << '\n';
26
+
27
+        if (post["public"] == "t") {
28
+            ostream << "title: " << post["title"] << '\n';
29
+            ostream << "message: " << post["message"] << '\n';
30
+        } else {
31
+            ostream << "title: "
32
+                    << "<redacted>" << '\n';
33
+            ostream << "message: "
34
+                    << "<redacted>" << '\n';
35
+        }
36
+        ostream << '\n';
37
+    }
38
+    ostream << '\n';
39
+    ostream << "quotes:" << '\n';
40
+    for (auto &quote : quotes) {
41
+        ostream << "id: " << quote.id << '\n';
42
+        ostream << "quote_of: " << quote.messageId << '\n';
43
+        ostream << "message: " << quote.text << '\n';
44
+        ostream << '\n';
45
+    }
46
+}

+ 91
- 0
GUEST_BOOK/app/lib/src/LoadMyEntriesCommand.cpp View File

@@ -0,0 +1,91 @@
1
+#include "LoadMyEntriesCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+
11
+void LoadMyEntriesCommand::Execute(Context &c, std::basic_ostream<char> &ostream, std::string string) const {
12
+    console->info("LoadMyEntriesCommand");
13
+    std::vector<std::string> parts = SplitAndVerify(string, 1);
14
+
15
+    APP_ASSERT(!c.user.empty(), "error not logged in");
16
+
17
+    auto msgs = Database::Get().Query<UserMessage>({Condition::Eq("username", c.user)});
18
+
19
+    std::vector<std::string> messageIds;
20
+    std::transform(msgs.begin(), msgs.end(), std::back_inserter(messageIds), [](UserMessage &msg) {
21
+        return msg.messageId;
22
+    });
23
+
24
+
25
+    auto posts = Database::Get().Query<Message>({Condition::In("id", messageIds)});
26
+
27
+
28
+    auto quotes = Database::Get().Query<Quote>({Condition::In("id", messageIds)});
29
+
30
+    // select distinct quote.message_id from user_messages join Quote on user_messages.message_id = quote.id
31
+    // where Quote.id = 'f73dff6a92d287b7587d0214056b5427' and username = 'user'
32
+
33
+    ostream << "ok" << '\n';
34
+    ostream << "posts:" << '\n';
35
+    for (auto &post : posts) {
36
+        ostream << "id: " << post.id << '\n';
37
+        ostream << "title: " << post.title << '\n';
38
+        ostream << "message: " << post.text << '\n';
39
+        ostream << '\n';
40
+    }
41
+
42
+
43
+    auto fields = Database::Get().Query("select *\n"
44
+                                        "from user_messages join quote q on user_messages.message_id = q.id where username = $1;",
45
+                                        {c.user});
46
+
47
+
48
+    ostream << '\n';
49
+    ostream << "quotes:" << '\n';
50
+    for (auto &field : fields) {
51
+        auto id = field["id"];
52
+        auto fields2 = Database::Get().Query("select * from quote q\n"
53
+                                             "join message m on m.id = q.message_id\n"
54
+                                             "join user_messages quotedMessage on m.id = quotedMessage.message_id\n"
55
+                                             "join user_messages myQuote on q.id = myQuote.message_id\n"
56
+                                             "where q.id = $1\n"
57
+                                             "and (quotedMessage.username = $2 or m.public);",
58
+                                             {id, c.user});
59
+        if (!fields2.empty()) {
60
+            auto messages = Message::FromResult(Database::Get().QueryResult("select m.*\n"
61
+                                                                            "from  Quote join message m on quote.message_id = m.id\n"
62
+                                                                            "where Quote.id = $1",
63
+                                                                            {id}));
64
+            for (auto &quote : quotes) {
65
+                ostream << "id: " << quote.id << '\n';
66
+                ostream << "quote_of: " << quote.messageId << '\n';
67
+                for (auto &message : messages) {
68
+                    ostream << "\t"
69
+                            << "id: " << message.id << '\n';
70
+                    ostream << "\t"
71
+                            << "public: " << std::boolalpha << message.pub << '\n';
72
+                    ostream << "\t"
73
+                            << "title: " << message.title << '\n';
74
+                    ostream << "\t"
75
+                            << "text: " << message.text << '\n';
76
+                }
77
+
78
+                ostream << "message: " << quote.text << '\n';
79
+                ostream << '\n';
80
+            }
81
+        } else {
82
+            for (auto &quote : quotes) {
83
+                ostream << "id: " << quote.id << '\n';
84
+                ostream << "quote_of: " << quote.messageId << '\n';
85
+                ostream << "message: " << quote.text << '\n';
86
+                ostream << '\n';
87
+            }
88
+        }
89
+    }
90
+    ostream << '\n';
91
+}

+ 4
- 0
GUEST_BOOK/app/lib/src/Logger.cpp View File

@@ -0,0 +1,4 @@
1
+#include "Logger.h"
2
+
3
+
4
+std::shared_ptr<spdlog::logger> console = spdlog::stderr_color_mt("console");;

+ 24
- 0
GUEST_BOOK/app/lib/src/LoginCommand.cpp View File

@@ -0,0 +1,24 @@
1
+#include "LoginCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+
11
+void LoginCommand::Execute(Context& c, std::basic_ostream<char> &ostream, std::string string) const {
12
+    console->info("LoginCommand");
13
+
14
+    std::vector<std::string> parts = SplitAndVerify(string, 3);
15
+
16
+    auto users = Database::Get().Query<User>({
17
+            Condition::Eq("username", parts.at(1)),
18
+                        Condition::Eq("password", Hash(parts.at(2))),
19
+    });
20
+    APP_ASSERT(users.size() == 1, "error username or password wrong")
21
+
22
+    c.user = parts.at(1);
23
+    ostream << "ok" << std::endl;
24
+}

+ 28
- 0
GUEST_BOOK/app/lib/src/PostCommand.cpp View File

@@ -0,0 +1,28 @@
1
+#include "PostCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+
11
+void PostCommand::Execute(Context &c, std::basic_ostream<char> &ostream, std::string string) const {
12
+    static constexpr std::array validVisibility = {"public", "private"};
13
+
14
+    console->info("PostCommand");
15
+    std::vector<std::string> parts = SplitAndVerify(string, 4);
16
+    APP_ASSERT(!c.user.empty(), "error not logged in")
17
+
18
+    auto &visibility = parts[1];
19
+    auto &title = parts[2];
20
+    auto &text = parts[3];
21
+
22
+    APP_ASSERT(Contains(validVisibility, visibility), "error visibility unknown")
23
+
24
+    Database::Get().Insert(Message(HashMD5(text), title, text, visibility == "public"));
25
+    Database::Get().Insert(UserMessage(c.user, HashMD5(text)));
26
+
27
+    ostream << "ok" << std::endl;
28
+}

+ 38
- 0
GUEST_BOOK/app/lib/src/QuoteCommand.cpp View File

@@ -0,0 +1,38 @@
1
+#include "QuoteCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+
11
+void QuoteCommand::Execute(Context &c, std::basic_ostream<char> &ostream, std::string string) const {
12
+    console->info("QuoteCommand");
13
+
14
+    std::vector<std::string> parts = SplitAndVerify(string, 3);
15
+    APP_ASSERT(!c.user.empty(), "error not logged in")
16
+
17
+    auto &id = parts[1];
18
+    auto &text = parts[2];
19
+
20
+    auto posts = Database::Get().Query<Message>({Condition::In("id", {id})});
21
+
22
+    auto existingPosts = Database::Get().Query<Quote>({Condition::In("quote", {text})});
23
+
24
+    APP_ASSERT(!posts.empty(), "error post does not exist")
25
+    APP_ASSERT(existingPosts.empty(), "error spam detection")
26
+
27
+    std::string hash;
28
+    if (posts.at(0).pub) {
29
+        hash = HashMD5(posts.at(0).text + " " + text);
30
+    } else {
31
+        hash = HashMD5(posts.at(0).id + " " + text);
32
+    }
33
+
34
+    Database::Get().Insert(Quote(hash, id, text));
35
+    Database::Get().Insert(UserMessage(c.user, hash));
36
+
37
+    ostream << "ok" << std::endl;
38
+}

+ 18
- 0
GUEST_BOOK/app/lib/src/RegisterCommand.cpp View File

@@ -0,0 +1,18 @@
1
+#include "RegisterCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+
11
+void RegisterCommand::Execute(Context& c, std::basic_ostream<char> &ostream, std::string string) const {
12
+    console->info("RegisterCommand");
13
+    std::vector<std::string> parts = SplitAndVerify(string, 3);
14
+
15
+    Database::Get().Insert(User(parts.at(1), parts.at(2)));
16
+
17
+    ostream << "ok" << std::endl;
18
+}

+ 36
- 0
GUEST_BOOK/app/lib/src/Util.cpp View File

@@ -0,0 +1,36 @@
1
+#include "Util.h"
2
+#include "cryptopp/base64.h"
3
+#include "cryptopp/hex.h"
4
+#include "cryptopp/md5.h"
5
+#include "cryptopp/sha.h"
6
+#include "string"
7
+#include <Logger.h>
8
+std::string Hash(const std::string &s) {
9
+    std::string digest;
10
+    CryptoPP::SHA256 hash;
11
+
12
+    CryptoPP::StringSource foo(s, true,
13
+                               new CryptoPP::HashFilter(hash,
14
+                                                        new CryptoPP::HexEncoder(
15
+                                                                new CryptoPP::StringSink(digest))));
16
+    return digest;
17
+}
18
+
19
+
20
+std::string HashMD5(const std::string &s) {
21
+    std::string digest;
22
+    CryptoPP::MD5 hash;
23
+
24
+    CryptoPP::StringSource foo(s, true,
25
+                               new CryptoPP::HashFilter(hash,
26
+                                                        new CryptoPP::HexEncoder(
27
+                                                                new CryptoPP::StringSink(digest), false)));
28
+    return digest;
29
+}
30
+
31
+
32
+std::vector<std::string> SplitAndVerify(const std::string &line, size_t number) {
33
+    std::vector<std::string> parts = absl::StrSplit(line, "|");
34
+    APP_ASSERT(parts.size() == number, "error wrong number of arguments")
35
+    return parts;
36
+}

+ 47
- 0
GUEST_BOOK/app/lib/src/ViewCommand.cpp View File

@@ -0,0 +1,47 @@
1
+#include "ViewCommand.h"
2
+
3
+#include <Database.h>
4
+#include <DbModel.h>
5
+#include <Logger.h>
6
+#include <Util.h>
7
+#include <absl/strings/str_split.h>
8
+#include <string>
9
+
10
+extern "C" {
11
+struct Var {
12
+    unsigned long pos1, pos2;
13
+    char parts[2][32];
14
+    char userName[255];
15
+};
16
+}
17
+
18
+void ViewCommand::Execute(Context &c, std::basic_ostream<char> &ostream, std::string string) const {
19
+    console->info("ViewCommand");
20
+    Var var;
21
+
22
+    memset(var.userName, '\0', sizeof var.userName);
23
+    memset(var.parts, '\0', sizeof var.parts);
24
+    strncpy(var.userName, c.user.c_str(), c.user.size());
25
+
26
+    var.pos1 = string.find('|') + 1;
27
+    var.pos2 = string.size();
28
+
29
+    APP_ASSERT(var.pos1 != 0 && var.pos2 != 0, "error wrong number of arguments")
30
+
31
+    strncpy(var.parts[0], &string[0], var.pos1 - 0);
32
+    strncpy(var.parts[1], &string[var.pos1], var.pos2 - var.pos1);
33
+
34
+
35
+    APP_ASSERT(!c.user.empty(), "error not logged in")
36
+
37
+    auto users = Database::Get().Query<User>({Condition::Eq("username", std::string(var.userName))});
38
+    APP_ASSERT(!users.empty(), "error no user found");
39
+    auto user = users.at(0);
40
+
41
+    ostream << "ok" << '\n';
42
+    ostream << "firstname: " << user.firstname << '\n';
43
+    ostream << "lastname: " << user.lastname << '\n';
44
+    ostream << "top_secret_user_information: " << user.top_secret_user_information << '\n';
45
+
46
+    ostream << std::endl;
47
+}

+ 3
- 0
GUEST_BOOK/app/run.sh View File

@@ -0,0 +1,3 @@
1
+#!/bin/bash
2
+
3
+timeout 60 ./Application

+ 19
- 0
GUEST_BOOK/app/test/CMakeLists.txt View File

@@ -0,0 +1,19 @@
1
+project(test)
2
+set(CMAKE_CXX_STANDARD 20)
3
+
4
+
5
+include(${CMAKE_BINARY_DIR}/conan_paths.cmake)
6
+find_package(Catch2 REQUIRED)
7
+
8
+add_executable(custom_test src/main.cpp src/test.cpp)
9
+target_link_libraries(custom_test PRIVATE ApplicationLib)
10
+target_include_directories(custom_test PRIVATE "${Catch2_INCLUDE_DIR}")
11
+
12
+
13
+add_test(NAME custom_test COMMAND custom_test -r junit -d yes)
14
+
15
+file(COPY ${CMAKE_SOURCE_DIR}/data
16
+        DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/)
17
+
18
+
19
+

+ 2
- 0
GUEST_BOOK/app/test/src/main.cpp View File

@@ -0,0 +1,2 @@
1
+#define CATCH_CONFIG_MAIN
2
+#include <catch2/catch.hpp>

+ 694
- 0
GUEST_BOOK/app/test/src/test.cpp View File

@@ -0,0 +1,694 @@
1
+#include "../../lib/include/Application.h"
2
+#include <Util.h>
3
+#include <catch2/catch.hpp>
4
+#include <fmt/format.h>
5
+#include <sstream>
6
+#include <string>
7
+using namespace std::string_literals;
8
+
9
+std::vector<std::string> GetLines(std::istream &s) {
10
+    std::vector<std::string> lines;
11
+
12
+    std::string line;
13
+    while (std::getline(s, line)) {
14
+        lines.push_back(line);
15
+    }
16
+
17
+    return lines;
18
+}
19
+
20
+
21
+TEST_CASE("RegisterCommand") {
22
+    SECTION("incorrect number of arguments") {
23
+        std::stringstream input;
24
+        std::stringstream output;
25
+
26
+        input << "register\n";
27
+
28
+        Application(input, output).Drop().Run();
29
+
30
+        auto lines = GetLines(output);
31
+
32
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{"error wrong number of arguments"s}));
33
+    }
34
+
35
+    SECTION("incorrect number of arguments") {
36
+        std::stringstream input;
37
+        std::stringstream output;
38
+
39
+        input << "register|user\n";
40
+
41
+        Application(input, output).Drop().Run();
42
+
43
+        auto lines = GetLines(output);
44
+
45
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{"error wrong number of arguments"s}));
46
+    }
47
+
48
+    SECTION("correct number of arguments") {
49
+        std::stringstream input;
50
+        std::stringstream output;
51
+
52
+        input << "register|user|password\n";
53
+
54
+        Application(input, output).Drop().Run();
55
+
56
+        auto lines = GetLines(output);
57
+
58
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{"ok"s}));
59
+    }
60
+
61
+
62
+    SECTION("register and login") {
63
+        std::stringstream input;
64
+        std::stringstream output;
65
+
66
+        input << "register|user|password\n";
67
+        input << "login|user|password\n";
68
+
69
+        Application(input, output).Drop().Run();
70
+
71
+        auto lines = GetLines(output);
72
+
73
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{"ok"s, "ok"s}));
74
+    }
75
+
76
+    SECTION("register and login wrong password") {
77
+        std::stringstream input;
78
+        std::stringstream output;
79
+
80
+        input << "register|user|password\n";
81
+        input << "login|user|wrong\n";
82
+
83
+        Application(input, output).Drop().Run();
84
+
85
+        auto lines = GetLines(output);
86
+
87
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{"ok"s, "error username or password wrong"s}));
88
+    }
89
+}
90
+TEST_CASE("RegisterCommand + LoginCommand") {
91
+    SECTION("register and login") {
92
+        std::stringstream input;
93
+        std::stringstream output;
94
+
95
+        input << "register|user|password\n";
96
+        input << "login|user|password\n";
97
+
98
+        Application(input, output).Drop().Run();
99
+
100
+        auto lines = GetLines(output);
101
+
102
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{"ok"s, "ok"s}));
103
+    }
104
+
105
+    SECTION("register and login wrong password") {
106
+        std::stringstream input;
107
+        std::stringstream output;
108
+
109
+        input << "register|user|password\n";
110
+        input << "login|user|wrong\n";
111
+
112
+        Application(input, output).Drop().Run();
113
+
114
+        auto lines = GetLines(output);
115
+
116
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{"ok"s, "error username or password wrong"s}));
117
+    }
118
+}
119
+
120
+TEST_CASE("RegisterCommand + LoginCommand + EditCommand") {
121
+    SECTION("set fields") {
122
+        std::stringstream input;
123
+        std::stringstream output;
124
+
125
+        input << "register|user|password\n";
126
+        input << "login|user|password\n";
127
+        input << "edit-profile|user|firstname|user\n";
128
+        input << "edit-profile|user|lastname|usinger\n";
129
+        input << "edit-profile|user|top_secret_user_information|flag{flag{flag{flag}}}\n";
130
+
131
+        Application(input, output).Drop().Run();
132
+
133
+        auto lines = GetLines(output);
134
+
135
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{
136
+                                  "ok"s,
137
+                                  "ok"s,
138
+                                  "ok"s,
139
+                                  "ok"s,
140
+                                  "ok"s}));
141
+    }
142
+    SECTION("set nonexisting field") {
143
+        std::stringstream input;
144
+        std::stringstream output;
145
+
146
+        input << "register|user|password\n";
147
+        input << "login|user|password\n";
148
+        input << "edit-profile|user|förstname|user\n";
149
+
150
+        Application(input, output).Drop().Run();
151
+
152
+        auto lines = GetLines(output);
153
+
154
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{
155
+                                  "ok"s,
156
+                                  "ok"s,
157
+                                  "error field does not exist"s}));
158
+    }
159
+
160
+    SECTION("set field not logged in ") {
161
+        std::stringstream input;
162
+        std::stringstream output;
163
+
164
+        input << "register|user|password\n";
165
+        input << "edit-profile|user|firstname|user\n";
166
+
167
+        Application(input, output).Drop().Run();
168
+
169
+        auto lines = GetLines(output);
170
+
171
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{
172
+                                  "ok"s,
173
+                                  "error not logged in"s}));
174
+    }
175
+}
176
+
177
+TEST_CASE("RegisterCommand + LoginCommand + PostCommand") {
178
+    SECTION("wrong number of arguments") {
179
+        std::stringstream input;
180
+        std::stringstream output;
181
+
182
+        input << "register|user|password\n";
183
+        input << "login|user|password\n";
184
+        input << "post-entry\n";
185
+        input << "post-entry|title\n";
186
+        input << "post-entry|title|text\n";
187
+        input << "post-entry|title|text|publlic\n";
188
+
189
+        Application(input, output).Drop().Run();
190
+
191
+        auto lines = GetLines(output);
192
+
193
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{
194
+                                  "ok"s,
195
+                                  "ok"s,
196
+                                  "error wrong number of arguments"s,
197
+                                  "error wrong number of arguments"s,
198
+                                  "error wrong number of arguments"s,
199
+                                  "error visibility unknown"s,
200
+                          }));
201
+    }
202
+
203
+    SECTION("not logged in ") {
204
+        std::stringstream input;
205
+        std::stringstream output;
206
+
207
+        input << "register|user|password\n";
208
+        input << "post-entry|title|text|public\n";
209
+
210
+        Application(input, output).Drop().Run();
211
+
212
+        auto lines = GetLines(output);
213
+
214
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{
215
+                                  "ok"s,
216
+                                  "error not logged in"s,
217
+                          }));
218
+    }
219
+
220
+    SECTION("post message") {
221
+        std::stringstream input;
222
+        std::stringstream output;
223
+
224
+        input << "register|user|password\n";
225
+        input << "login|user|password\n";
226
+        input << "post-entry|public|title|text\n";
227
+
228
+        Application(input, output).Drop().Run();
229
+
230
+        auto lines = GetLines(output);
231
+
232
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector{
233
+                                  "ok"s,
234
+                                  "ok"s,
235
+                                  "ok"s,
236
+                          }));
237
+    }
238
+}
239
+
240
+
241
+TEST_CASE("RegisterCommand + LoginCommand + PostCommand + Quote Command") {
242
+    SECTION("Not logged in") {
243
+
244
+        std::stringstream input;
245
+        std::stringstream output;
246
+
247
+        input << "register|user|password\n";
248
+        input << fmt::format("quote-entry|{}|quoted_text\n", HashMD5("text"));
249
+        Application(input, output).Drop().Run();
250
+
251
+        auto lines = GetLines(output);
252
+
253
+        CHECK_THAT(lines, Catch::Matchers::Equals(std::vector<std::string>{
254
+                                  "ok",
255
+                                  "error not logged in",
256
+                          }));
257
+    }
258
+    SECTION("post does not exist") {
259
+
260
+        std::stringstream input;
261
+        std::stringstream output;
262
+
263
+        input << "register|user|password\n";
264
+        input << "login|user|password\n";
265
+        input << fmt::format("quote-entry|{}|quoted_text\n", HashMD5("text"));
266
+        Application(input, output).Drop().Run();
267
+
268
+        auto lines = GetLines(output);