diff --git a/files/classes/__init__.py b/files/classes/__init__.py index 730f08481..02c324a66 100644 --- a/files/classes/__init__.py +++ b/files/classes/__init__.py @@ -38,3 +38,5 @@ from .currency_logs import * if FEATURES['IP_LOGGING']: from .ip_logs import * + +from .edit_logs import * diff --git a/files/classes/comment.py b/files/classes/comment.py index 801ba0c4e..2e84a0213 100644 --- a/files/classes/comment.py +++ b/files/classes/comment.py @@ -232,6 +232,7 @@ class Comment(Base): options = relationship("CommentOption", order_by="CommentOption.id") casino_game = relationship("CasinoGame") wall_user = relationship("User", primaryjoin="User.id==Comment.wall_user_id") + edits = relationship("CommentEdit", order_by="CommentEdit.id.desc()") def __init__(self, *args, **kwargs): if "created_utc" not in kwargs: diff --git a/files/classes/edit_logs.py b/files/classes/edit_logs.py new file mode 100644 index 000000000..0420b7ac3 --- /dev/null +++ b/files/classes/edit_logs.py @@ -0,0 +1,38 @@ +import time + +from sqlalchemy import Column, ForeignKey +from sqlalchemy.sql.sqltypes import * + +from files.classes import Base + +class PostEdit(Base): + __tablename__ = "post_edits" + id = Column(Integer, primary_key=True) + post_id = Column(Integer, ForeignKey("posts.id")) + old_body = Column(String) + old_body_html = Column(String) + old_title = Column(String) + old_title_html = Column(String) + created_utc = Column(Integer) + + def __init__(self, *args, **kwargs): + if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time()) + super().__init__(*args, **kwargs) + + def __repr__(self): + return f"<{self.__class__.__name__}(id={self.id})>" + +class CommentEdit(Base): + __tablename__ = "comment_edits" + id = Column(Integer, primary_key=True) + comment_id = Column(Integer, ForeignKey("comments.id")) + old_body = Column(String) + old_body_html = Column(String) + created_utc = Column(Integer) + + def __init__(self, *args, **kwargs): + if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time()) + super().__init__(*args, **kwargs) + + def __repr__(self): + return f"<{self.__class__.__name__}(id={self.id})>" diff --git a/files/classes/post.py b/files/classes/post.py index f3ca76ae8..fa70eb95d 100644 --- a/files/classes/post.py +++ b/files/classes/post.py @@ -85,6 +85,7 @@ class Post(Base): comments = relationship("Comment", primaryjoin="Comment.parent_post==Post.id", back_populates="post") hole_obj = relationship("Hole", primaryjoin="foreign(Post.hole)==remote(Hole.name)") options = relationship("PostOption", order_by="PostOption.id") + edits = relationship("PostEdit", order_by="PostEdit.id.desc()") def __init__(self, *args, **kwargs): if "created_utc" not in kwargs: diff --git a/files/helpers/config/const.py b/files/helpers/config/const.py index 2664dad93..af511aa72 100644 --- a/files/helpers/config/const.py +++ b/files/helpers/config/const.py @@ -171,6 +171,7 @@ PERMS = { # Minimum admin_level to perform action. 'VIEW_ACTIVE_USERS': 1, 'VIEW_ALT_VOTES': 1, 'VIEW_LAST_ACTIVE': 1, + 'VIEW_EDITS': 1, 'ENABLE_VOTE_BUTTONS_ON_USER_PAGE': 1, 'NOTIFICATIONS_MODERATOR_ACTIONS': 1, 'EXEMPT_FROM_IP_LOGGING': 1, diff --git a/files/routes/admin.py b/files/routes/admin.py index 0b7289549..b5ff18635 100644 --- a/files/routes/admin.py +++ b/files/routes/admin.py @@ -2231,3 +2231,16 @@ def unmark_effortpost(pid, v): send_repeatable_notification(p.author_id, f":marseyitsover: @{v.username} (a site admin) has unmarked {p.textlink} as an effortpost. {coins} coins have been deducted from you. :!marseyitsover:") return {"message": "Post has been unmarked as an effortpost!"} + +@app.get("/edits/") +@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400) +@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID) +@admin_level_required(PERMS['VIEW_EDITS']) +def view_edits(v, link): + try: + if "p_" in link: obj = get_post(int(link.split("p_")[1]), v=v) + elif "c_" in link: obj = get_comment(int(link.split("c_")[1]), v=v) + else: abort(400) + except: abort(400) + + return render_template("edits.html", v=v, obj=obj) diff --git a/files/routes/comments.py b/files/routes/comments.py index ef72d9527..a5d10d296 100644 --- a/files/routes/comments.py +++ b/files/routes/comments.py @@ -637,6 +637,13 @@ def edit_comment(cid, v): if c.author.hieroglyphs and marseyaward_body_regex.search(body_html): abort(403, "You can only type marseys!") + edit_log = CommentEdit( + comment_id=c.id, + old_body=c.body, + old_body_html=c.body_html, + ) + g.db.add(edit_log) + oldtext = c.body c.body = body diff --git a/files/routes/posts.py b/files/routes/posts.py index c83ae8aa2..6bfadf6b0 100644 --- a/files/routes/posts.py +++ b/files/routes/posts.py @@ -1057,6 +1057,14 @@ def edit_post(pid, v): changed = False + edit_log = PostEdit( + post_id=p.id, + old_title=p.title, + old_title_html=p.title_html, + old_body=p.body, + old_body_html=p.body_html, + ) + g.db.add(edit_log) if title != p.title: title_html = filter_emojis_only(title, golden=False, obj=p, author=p.author) diff --git a/files/templates/comments.html b/files/templates/comments.html index da6b50202..8d192d1ca 100644 --- a/files/templates/comments.html +++ b/files/templates/comments.html @@ -233,9 +233,15 @@ #{{c.id}} - -   Edited {{c.edited_string}} - + {% if c.edited_utc and v and v.admin_level >= PERMS['VIEW_EDITS'] %} + +   Edited {{c.edited_string}} + + {% else %} + +   Edited {{c.edited_string}} + + {% endif %} {% if c.treasure_amount and c.treasure_amount != '0' %} {% if c.treasure_amount.startswith('l') %} diff --git a/files/templates/edits.html b/files/templates/edits.html new file mode 100644 index 000000000..25f9e07ed --- /dev/null +++ b/files/templates/edits.html @@ -0,0 +1,26 @@ +{% extends "default.html" %} +{% block pagetitle %}Edits on {{obj.shortlink}}{% endblock %} +{% block content %} +

Edits on {{obj.textlink | safe}}

+
+ + + + {% if obj.fullname.startswith('p_') %} + + {% endif %} + + + + + {% for edit in obj.edits %} + + {% if obj.fullname.startswith('p_') %} + + {% endif %} + + + {% endfor %} +
TitleBody
{{edit.old_title_html | safe}}{{edit.old_body_html | safe}}
+
+{% endblock %} diff --git a/files/templates/util/macros.html b/files/templates/util/macros.html index f476557a1..c03028dc9 100644 --- a/files/templates/util/macros.html +++ b/files/templates/util/macros.html @@ -125,7 +125,11 @@ {% endif %} {% if p.edited_utc %} - Edited {{p.edited_string}} + {% if v and v.admin_level >= PERMS['VIEW_EDITS'] %} + Edited {{p.edited_string}} + {% else %} + Edited {{p.edited_string}} + {% endif %} {% endif %} {{p.views}} thread views diff --git a/migrations/20240307-add-edit-logs.sql b/migrations/20240307-add-edit-logs.sql new file mode 100644 index 000000000..37c48a123 --- /dev/null +++ b/migrations/20240307-add-edit-logs.sql @@ -0,0 +1,48 @@ +create table post_edits ( + id integer primary key, + post_id integer not null, + old_title character varying(500), + old_title_html character varying(1500), + old_body character varying(100000), + old_body_html character varying(200000), + created_utc integer NOT NULL +); + +CREATE SEQUENCE public.post_edits_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE public.post_edits_id_seq OWNED BY public.post_edits.id; + +ALTER TABLE ONLY public.post_edits ALTER COLUMN id SET DEFAULT nextval('public.post_edits_id_seq'::regclass); + +alter table only post_edits + add constraint post_edits_post_fkey foreign key (post_id) references public.posts(id); + + +create table comment_edits ( + id integer primary key, + comment_id integer not null, + old_body character varying(100000), + old_body_html character varying(200000), + created_utc integer NOT NULL +); + +CREATE SEQUENCE public.comment_edits_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE public.comment_edits_id_seq OWNED BY public.comment_edits.id; + +ALTER TABLE ONLY public.comment_edits ALTER COLUMN id SET DEFAULT nextval('public.comment_edits_id_seq'::regclass); + +alter table only comment_edits + add constraint comment_edits_comment_fkey foreign key (comment_id) references public.comments(id);