We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Идея: добавить лайки постов в выгружаемый архив с моими данными
Предлагаю добавить в архив файл где будут собраны посты (батлы, интро, проекты, и т.д) которые я лайкал. Формат: название поста, дата поста, ссылка на пост.

Мой конкретный кейс: в клуб заходит мой друг, хочу ему показать что-то из того, что мне понравилось из прошлого.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gdpr/serializers.py`
Content:
```
1 from typing import List
2 
3 from django.conf import settings
4 from django.urls import reverse
5 
6 from bookmarks.models import PostBookmark
7 from comments.models import Comment
8 from posts.models.post import Post
9 from users.models.expertise import UserExpertise
10 from users.models.tags import UserTag
11 from users.models.user import User
12 
13 
14 def post_to_md(post: Post) -> str:
15     return f"# {post.title}\n\n{post.text}"
16 
17 
18 def post_to_json(post: Post) -> dict:
19     return {
20         "id": str(post.id),
21         "slug": post.slug,
22         "author_id": str(post.author_id),
23         "type": post.type,
24         "topic": post.topic.name if post.topic else None,
25         "label": post.label,
26         "title": post.title,
27         "text": post.text,
28         "url": post.url,
29         "image": post.image,
30         "metadata": post.metadata,
31         "created_at": post.created_at.isoformat(),
32         "updated_at": post.updated_at.isoformat(),
33         "last_activity_at": post.last_activity_at.isoformat(),
34         "comment_count": post.comment_count,
35         "view_count": post.view_count,
36         "upvotes": post.upvotes,
37     }
38 
39 
40 def comment_to_md(comment: Comment) -> str:
41     return f"{comment.text}\n\n- {comment.author.slug}"
42 
43 
44 def comments_to_json(comments: List[Comment]) -> dict:
45     return {
46         "comments": [
47             comment_to_json(comment) for comment in comments
48         ]
49     }
50 
51 
52 def comment_to_json(comment: Comment) -> dict:
53     return {
54         "id": str(comment.id),
55         "author_id": str(comment.author_id),
56         "post_id": str(comment.post_id),
57         "post_title": comment.post.title,
58         "reply_to_id": str(comment.reply_to_id) if comment.reply_to else None,
59         "title": comment.title,
60         "text": comment.text,
61         "url": comment.url,
62         "metadata": comment.metadata,
63         "created_at": comment.created_at.isoformat(),
64         "updated_at": comment.updated_at.isoformat(),
65         "upvotes": comment.upvotes,
66         "is_visible": comment.is_visible,
67         "is_deleted": comment.is_deleted,
68         "is_pinned": comment.is_pinned,
69     }
70 
71 
72 def user_to_json(user: User) -> dict:
73     return {
74         "id": str(user.id),
75         "slug": user.slug,
76         "email": user.email,
77         "full_name": user.full_name,
78         "avatar": user.avatar,
79         "company": user.company,
80         "position": user.position,
81         "city": user.city,
82         "country": user.country,
83         "bio": user.bio,
84         "contact": user.contact,
85         "hat": user.hat,
86         "balance": user.balance,
87         "upvotes": user.upvotes,
88         "created_at": user.created_at.isoformat(),
89         "updated_at": user.updated_at.isoformat() if user.updated_at else None,
90         "last_activity_at": user.last_activity_at.isoformat() if user.last_activity_at else None,
91         "membership_started_at": user.membership_started_at.isoformat() if user.membership_started_at else None,
92         "membership_expires_at": user.membership_expires_at.isoformat() if user.membership_expires_at else None,
93         "membership_platform_type": user.membership_platform_type,
94         "patreon_id": user.patreon_id,
95         "email_digest_type": user.email_digest_type,
96         "telegram_id": user.telegram_id,
97         "telegram_data": user.telegram_data,
98         "stripe_id": user.stripe_id,
99         "is_email_verified": user.is_email_verified,
100         "is_email_unsubscribed": user.is_email_unsubscribed,
101         "moderation_status": user.moderation_status,
102         "roles": user.roles,
103     }
104 
105 
106 def user_tags_to_json(user_tags: List[UserTag]) -> dict:
107     return {
108         "user_tags": [
109             user_tag_to_json(user_tag) for user_tag in user_tags
110         ]
111     }
112 
113 
114 def user_tag_to_json(user_tag: UserTag) -> dict:
115     return {
116         "user_id": str(user_tag.user_id),
117         "name": user_tag.name,
118         "created_at": user_tag.created_at.isoformat() if user_tag.created_at else None,
119     }
120 
121 
122 def user_expertises_to_json(user_expertises: List[UserExpertise]) -> dict:
123     return {
124         "user_expertise": [
125             user_expertise_to_json(user_expertise) for user_expertise in user_expertises
126         ]
127     }
128 
129 
130 def user_expertise_to_json(user_expertise: UserExpertise) -> dict:
131     return {
132         "user_id": str(user_expertise.user_id),
133         "name": user_expertise.name,
134         "value": user_expertise.value,
135         "created_at": user_expertise.created_at.isoformat() if user_expertise.created_at else None,
136     }
137 
138 
139 def bookmarks_to_json(bookmarks: List[PostBookmark]) -> dict:
140     return {
141         "bookmarks": [
142             bookmark_to_json(bookmark) for bookmark in bookmarks
143         ]
144     }
145 
146 
147 def bookmark_to_json(bookmark: PostBookmark) -> dict:
148     return {
149         "url": settings.APP_HOST + reverse("show_post", kwargs={
150             "post_type": bookmark.post.type, "post_slug": bookmark.post.slug
151         }),
152         "created_at": bookmark.created_at.isoformat() if bookmark.created_at else None,
153     }
154
```

Path: `gdpr/archive.py`
Content:
```
1 import json
2 import os
3 import random
4 import shutil
5 import tempfile
6 from datetime import datetime
7 
8 from django.conf import settings
9 from django_q.tasks import schedule
10 
11 from bookmarks.models import PostBookmark
12 from comments.models import Comment
13 from gdpr.serializers import post_to_json, post_to_md, user_to_json, comments_to_json, user_tags_to_json, \
14     user_expertises_to_json, comment_to_md, comment_to_json, bookmarks_to_json
15 from notifications.email.users import send_data_archive_ready_email
16 from posts.models.post import Post
17 from users.models.expertise import UserExpertise
18 from users.models.tags import UserTag
19 
20 
21 def generate_data_archive(user, save_path=settings.GDPR_ARCHIVE_STORAGE_PATH):
22     with tempfile.TemporaryDirectory() as tmp_dir:
23         user_dir = os.path.join(tmp_dir, user.slug)
24         os.makedirs(user_dir)
25 
26         # dump data
27         dump_user_profile(user_dir, user)
28         dump_user_posts(user_dir, user)
29         dump_user_comments(user_dir, user)
30         dump_user_bookmarks(user_dir, user)
31 
32         # save zip archive
33         archive_name = f"{user.slug}-{datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}-{random.randint(1000000, 9999998)}"
34         archive_path = shutil.make_archive(os.path.join(save_path, archive_name), "zip", tmp_dir)
35 
36         # schedule a task to remove archive after timeout
37         schedule(
38             "gdpr.archive.delete_data_archive",
39             archive_path,
40             next_run=datetime.utcnow() + settings.GDPR_ARCHIVE_DELETE_TIMEDELTA
41         )
42 
43         # notify the user
44         send_data_archive_ready_email(
45             user=user,
46             url=settings.GDPR_ARCHIVE_URL + os.path.basename(archive_path),
47         )
48 
49 
50 def delete_data_archive(archive_path):
51     os.remove(archive_path)
52 
53 
54 def dump_user_profile(user_dir, user):
55     with open(os.path.join(user_dir, "profile.json"), "w", encoding="utf-8") as f:
56         f.write(json.dumps(user_to_json(user), ensure_ascii=False))
57 
58     user_tags = UserTag.objects.filter(user=user)
59     with open(os.path.join(user_dir, "tags.json"), "w", encoding="utf-8") as f:
60         f.write(json.dumps(user_tags_to_json(user_tags), ensure_ascii=False))
61 
62     user_expertises = UserExpertise.objects.filter(user=user)
63     with open(os.path.join(user_dir, "expertise.json"), "w", encoding="utf-8") as f:
64         f.write(json.dumps(user_expertises_to_json(user_expertises), ensure_ascii=False))
65 
66 
67 def dump_user_posts(user_dir, user):
68     posts = Post.objects.filter(author=user).select_related("author", "topic")
69 
70     for post in posts:
71         post_dir = os.path.join(user_dir, f"posts/{post.slug}")
72         os.makedirs(post_dir)
73 
74         with open(os.path.join(post_dir, f"{post.slug}.md"), "w", encoding="utf-8") as f:
75             f.write(post_to_md(post))
76 
77         with open(os.path.join(post_dir, f"{post.slug}.json"), "w", encoding="utf-8") as f:
78             f.write(json.dumps(post_to_json(post), ensure_ascii=False))
79 
80         # dump post comments
81         post_comments = Comment.objects.filter(post=post).select_related("author", "post")
82         with open(os.path.join(post_dir, f"comments.json"), "w", encoding="utf-8") as f:
83             f.write(json.dumps(comments_to_json(post_comments), ensure_ascii=False))
84 
85 
86 def dump_user_comments(user_dir, user):
87     comments = Comment.objects.filter(author=user).select_related("author", "post")
88 
89     for comment in comments:
90         comment_dir = os.path.join(user_dir, f"comments/{comment.id}")
91         os.makedirs(comment_dir)
92 
93         with open(os.path.join(comment_dir, f"{comment.id}.md"), "w", encoding="utf-8") as f:
94             f.write(comment_to_md(comment))
95 
96         with open(os.path.join(comment_dir, f"{comment.id}.json"), "w", encoding="utf-8") as f:
97             f.write(json.dumps(comment_to_json(comment), ensure_ascii=False))
98 
99         # dump replies
100         comment_replies = Comment.objects.filter(reply_to=comment).select_related("author", "post")
101         with open(os.path.join(comment_dir, f"replies.json"), "w", encoding="utf-8") as f:
102             f.write(json.dumps(comments_to_json(comment_replies), ensure_ascii=False))
103 
104 
105 def dump_user_bookmarks(user_dir, user):
106     bookmarks = PostBookmark.objects.filter(user=user).select_related("post")
107 
108     with open(os.path.join(user_dir, "bookmarks.json"), "w", encoding="utf-8") as f:
109         f.write(json.dumps(bookmarks_to_json(bookmarks), ensure_ascii=False))
110
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gdpr/archive.py b/gdpr/archive.py
--- a/gdpr/archive.py
+++ b/gdpr/archive.py
@@ -11,9 +11,10 @@
 from bookmarks.models import PostBookmark
 from comments.models import Comment
 from gdpr.serializers import post_to_json, post_to_md, user_to_json, comments_to_json, user_tags_to_json, \
-    user_expertises_to_json, comment_to_md, comment_to_json, bookmarks_to_json
+    user_expertises_to_json, comment_to_md, comment_to_json, bookmarks_to_json, upvotes_to_json
 from notifications.email.users import send_data_archive_ready_email
 from posts.models.post import Post
+from posts.models.votes import PostVote
 from users.models.expertise import UserExpertise
 from users.models.tags import UserTag
 
@@ -28,6 +29,7 @@
         dump_user_posts(user_dir, user)
         dump_user_comments(user_dir, user)
         dump_user_bookmarks(user_dir, user)
+        dump_user_upvotes(user_dir, user)
 
         # save zip archive
         archive_name = f"{user.slug}-{datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}-{random.randint(1000000, 9999998)}"
@@ -107,3 +109,10 @@
 
     with open(os.path.join(user_dir, "bookmarks.json"), "w", encoding="utf-8") as f:
         f.write(json.dumps(bookmarks_to_json(bookmarks), ensure_ascii=False))
+
+
+def dump_user_upvotes(user_dir, user):
+    upvotes = PostVote.objects.filter(user=user).select_related("post")
+
+    with open(os.path.join(user_dir, "upvotes.json"), "w", encoding="utf-8") as f:
+        f.write(json.dumps(upvotes_to_json(upvotes), ensure_ascii=False))
diff --git a/gdpr/serializers.py b/gdpr/serializers.py
--- a/gdpr/serializers.py
+++ b/gdpr/serializers.py
@@ -6,6 +6,7 @@
 from bookmarks.models import PostBookmark
 from comments.models import Comment
 from posts.models.post import Post
+from posts.models.votes import PostVote
 from users.models.expertise import UserExpertise
 from users.models.tags import UserTag
 from users.models.user import User
@@ -151,3 +152,21 @@
         }),
         "created_at": bookmark.created_at.isoformat() if bookmark.created_at else None,
     }
+
+
+def upvotes_to_json(upvotes: List[PostVote]) -> dict:
+    return {
+        "upvotes": [
+            upvote_to_json(upvote) for upvote in upvotes
+        ]
+    }
+
+
+def upvote_to_json(upvote: PostVote) -> dict:
+    return {
+        "url": settings.APP_HOST + reverse("show_post", kwargs={
+            "post_type": upvote.post.type, "post_slug": upvote.post.slug
+        }),
+        "created_at": upvote.created_at.isoformat() if upvote.created_at else None,
+        "title": upvote.post.title,
+    }

{"golden_diff": "diff --git a/gdpr/archive.py b/gdpr/archive.py\n--- a/gdpr/archive.py\n+++ b/gdpr/archive.py\n@@ -11,9 +11,10 @@\n from bookmarks.models import PostBookmark\n from comments.models import Comment\n from gdpr.serializers import post_to_json, post_to_md, user_to_json, comments_to_json, user_tags_to_json, \\\n-    user_expertises_to_json, comment_to_md, comment_to_json, bookmarks_to_json\n+    user_expertises_to_json, comment_to_md, comment_to_json, bookmarks_to_json, upvotes_to_json\n from notifications.email.users import send_data_archive_ready_email\n from posts.models.post import Post\n+from posts.models.votes import PostVote\n from users.models.expertise import UserExpertise\n from users.models.tags import UserTag\n \n@@ -28,6 +29,7 @@\n         dump_user_posts(user_dir, user)\n         dump_user_comments(user_dir, user)\n         dump_user_bookmarks(user_dir, user)\n+        dump_user_upvotes(user_dir, user)\n \n         # save zip archive\n         archive_name = f\"{user.slug}-{datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}-{random.randint(1000000, 9999998)}\"\n@@ -107,3 +109,10 @@\n \n     with open(os.path.join(user_dir, \"bookmarks.json\"), \"w\", encoding=\"utf-8\") as f:\n         f.write(json.dumps(bookmarks_to_json(bookmarks), ensure_ascii=False))\n+\n+\n+def dump_user_upvotes(user_dir, user):\n+    upvotes = PostVote.objects.filter(user=user).select_related(\"post\")\n+\n+    with open(os.path.join(user_dir, \"upvotes.json\"), \"w\", encoding=\"utf-8\") as f:\n+        f.write(json.dumps(upvotes_to_json(upvotes), ensure_ascii=False))\ndiff --git a/gdpr/serializers.py b/gdpr/serializers.py\n--- a/gdpr/serializers.py\n+++ b/gdpr/serializers.py\n@@ -6,6 +6,7 @@\n from bookmarks.models import PostBookmark\n from comments.models import Comment\n from posts.models.post import Post\n+from posts.models.votes import PostVote\n from users.models.expertise import UserExpertise\n from users.models.tags import UserTag\n from users.models.user import User\n@@ -151,3 +152,21 @@\n         }),\n         \"created_at\": bookmark.created_at.isoformat() if bookmark.created_at else None,\n     }\n+\n+\n+def upvotes_to_json(upvotes: List[PostVote]) -> dict:\n+    return {\n+        \"upvotes\": [\n+            upvote_to_json(upvote) for upvote in upvotes\n+        ]\n+    }\n+\n+\n+def upvote_to_json(upvote: PostVote) -> dict:\n+    return {\n+        \"url\": settings.APP_HOST + reverse(\"show_post\", kwargs={\n+            \"post_type\": upvote.post.type, \"post_slug\": upvote.post.slug\n+        }),\n+        \"created_at\": upvote.created_at.isoformat() if upvote.created_at else None,\n+        \"title\": upvote.post.title,\n+    }\n", "issue": "\u0418\u0434\u0435\u044f: \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043b\u0430\u0439\u043a\u0438 \u043f\u043e\u0441\u0442\u043e\u0432 \u0432 \u0432\u044b\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0439 \u0430\u0440\u0445\u0438\u0432 \u0441 \u043c\u043e\u0438\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438\n\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0430\u0440\u0445\u0438\u0432 \u0444\u0430\u0439\u043b \u0433\u0434\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0431\u0440\u0430\u043d\u044b \u043f\u043e\u0441\u0442\u044b (\u0431\u0430\u0442\u043b\u044b, \u0438\u043d\u0442\u0440\u043e, \u043f\u0440\u043e\u0435\u043a\u0442\u044b, \u0438 \u0442.\u0434) \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043b\u0430\u0439\u043a\u0430\u043b. \u0424\u043e\u0440\u043c\u0430\u0442: \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0430, \u0434\u0430\u0442\u0430 \u043f\u043e\u0441\u0442\u0430, \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043f\u043e\u0441\u0442.\r\n\r\n\u041c\u043e\u0439 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u0435\u0439\u0441: \u0432 \u043a\u043b\u0443\u0431 \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u043c\u043e\u0439 \u0434\u0440\u0443\u0433, \u0445\u043e\u0447\u0443 \u0435\u043c\u0443 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u0438\u0437 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u043c\u043d\u0435 \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u043e\u0441\u044c \u0438\u0437 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e.\n", "before_files": [{"content": "from typing import List\n\nfrom django.conf import settings\nfrom django.urls import reverse\n\nfrom bookmarks.models import PostBookmark\nfrom comments.models import Comment\nfrom posts.models.post import Post\nfrom users.models.expertise import UserExpertise\nfrom users.models.tags import UserTag\nfrom users.models.user import User\n\n\ndef post_to_md(post: Post) -> str:\n    return f\"# {post.title}\\n\\n{post.text}\"\n\n\ndef post_to_json(post: Post) -> dict:\n    return {\n        \"id\": str(post.id),\n        \"slug\": post.slug,\n        \"author_id\": str(post.author_id),\n        \"type\": post.type,\n        \"topic\": post.topic.name if post.topic else None,\n        \"label\": post.label,\n        \"title\": post.title,\n        \"text\": post.text,\n        \"url\": post.url,\n        \"image\": post.image,\n        \"metadata\": post.metadata,\n        \"created_at\": post.created_at.isoformat(),\n        \"updated_at\": post.updated_at.isoformat(),\n        \"last_activity_at\": post.last_activity_at.isoformat(),\n        \"comment_count\": post.comment_count,\n        \"view_count\": post.view_count,\n        \"upvotes\": post.upvotes,\n    }\n\n\ndef comment_to_md(comment: Comment) -> str:\n    return f\"{comment.text}\\n\\n- {comment.author.slug}\"\n\n\ndef comments_to_json(comments: List[Comment]) -> dict:\n    return {\n        \"comments\": [\n            comment_to_json(comment) for comment in comments\n        ]\n    }\n\n\ndef comment_to_json(comment: Comment) -> dict:\n    return {\n        \"id\": str(comment.id),\n        \"author_id\": str(comment.author_id),\n        \"post_id\": str(comment.post_id),\n        \"post_title\": comment.post.title,\n        \"reply_to_id\": str(comment.reply_to_id) if comment.reply_to else None,\n        \"title\": comment.title,\n        \"text\": comment.text,\n        \"url\": comment.url,\n        \"metadata\": comment.metadata,\n        \"created_at\": comment.created_at.isoformat(),\n        \"updated_at\": comment.updated_at.isoformat(),\n        \"upvotes\": comment.upvotes,\n        \"is_visible\": comment.is_visible,\n        \"is_deleted\": comment.is_deleted,\n        \"is_pinned\": comment.is_pinned,\n    }\n\n\ndef user_to_json(user: User) -> dict:\n    return {\n        \"id\": str(user.id),\n        \"slug\": user.slug,\n        \"email\": user.email,\n        \"full_name\": user.full_name,\n        \"avatar\": user.avatar,\n        \"company\": user.company,\n        \"position\": user.position,\n        \"city\": user.city,\n        \"country\": user.country,\n        \"bio\": user.bio,\n        \"contact\": user.contact,\n        \"hat\": user.hat,\n        \"balance\": user.balance,\n        \"upvotes\": user.upvotes,\n        \"created_at\": user.created_at.isoformat(),\n        \"updated_at\": user.updated_at.isoformat() if user.updated_at else None,\n        \"last_activity_at\": user.last_activity_at.isoformat() if user.last_activity_at else None,\n        \"membership_started_at\": user.membership_started_at.isoformat() if user.membership_started_at else None,\n        \"membership_expires_at\": user.membership_expires_at.isoformat() if user.membership_expires_at else None,\n        \"membership_platform_type\": user.membership_platform_type,\n        \"patreon_id\": user.patreon_id,\n        \"email_digest_type\": user.email_digest_type,\n        \"telegram_id\": user.telegram_id,\n        \"telegram_data\": user.telegram_data,\n        \"stripe_id\": user.stripe_id,\n        \"is_email_verified\": user.is_email_verified,\n        \"is_email_unsubscribed\": user.is_email_unsubscribed,\n        \"moderation_status\": user.moderation_status,\n        \"roles\": user.roles,\n    }\n\n\ndef user_tags_to_json(user_tags: List[UserTag]) -> dict:\n    return {\n        \"user_tags\": [\n            user_tag_to_json(user_tag) for user_tag in user_tags\n        ]\n    }\n\n\ndef user_tag_to_json(user_tag: UserTag) -> dict:\n    return {\n        \"user_id\": str(user_tag.user_id),\n        \"name\": user_tag.name,\n        \"created_at\": user_tag.created_at.isoformat() if user_tag.created_at else None,\n    }\n\n\ndef user_expertises_to_json(user_expertises: List[UserExpertise]) -> dict:\n    return {\n        \"user_expertise\": [\n            user_expertise_to_json(user_expertise) for user_expertise in user_expertises\n        ]\n    }\n\n\ndef user_expertise_to_json(user_expertise: UserExpertise) -> dict:\n    return {\n        \"user_id\": str(user_expertise.user_id),\n        \"name\": user_expertise.name,\n        \"value\": user_expertise.value,\n        \"created_at\": user_expertise.created_at.isoformat() if user_expertise.created_at else None,\n    }\n\n\ndef bookmarks_to_json(bookmarks: List[PostBookmark]) -> dict:\n    return {\n        \"bookmarks\": [\n            bookmark_to_json(bookmark) for bookmark in bookmarks\n        ]\n    }\n\n\ndef bookmark_to_json(bookmark: PostBookmark) -> dict:\n    return {\n        \"url\": settings.APP_HOST + reverse(\"show_post\", kwargs={\n            \"post_type\": bookmark.post.type, \"post_slug\": bookmark.post.slug\n        }),\n        \"created_at\": bookmark.created_at.isoformat() if bookmark.created_at else None,\n    }\n", "path": "gdpr/serializers.py"}, {"content": "import json\nimport os\nimport random\nimport shutil\nimport tempfile\nfrom datetime import datetime\n\nfrom django.conf import settings\nfrom django_q.tasks import schedule\n\nfrom bookmarks.models import PostBookmark\nfrom comments.models import Comment\nfrom gdpr.serializers import post_to_json, post_to_md, user_to_json, comments_to_json, user_tags_to_json, \\\n    user_expertises_to_json, comment_to_md, comment_to_json, bookmarks_to_json\nfrom notifications.email.users import send_data_archive_ready_email\nfrom posts.models.post import Post\nfrom users.models.expertise import UserExpertise\nfrom users.models.tags import UserTag\n\n\ndef generate_data_archive(user, save_path=settings.GDPR_ARCHIVE_STORAGE_PATH):\n    with tempfile.TemporaryDirectory() as tmp_dir:\n        user_dir = os.path.join(tmp_dir, user.slug)\n        os.makedirs(user_dir)\n\n        # dump data\n        dump_user_profile(user_dir, user)\n        dump_user_posts(user_dir, user)\n        dump_user_comments(user_dir, user)\n        dump_user_bookmarks(user_dir, user)\n\n        # save zip archive\n        archive_name = f\"{user.slug}-{datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}-{random.randint(1000000, 9999998)}\"\n        archive_path = shutil.make_archive(os.path.join(save_path, archive_name), \"zip\", tmp_dir)\n\n        # schedule a task to remove archive after timeout\n        schedule(\n            \"gdpr.archive.delete_data_archive\",\n            archive_path,\n            next_run=datetime.utcnow() + settings.GDPR_ARCHIVE_DELETE_TIMEDELTA\n        )\n\n        # notify the user\n        send_data_archive_ready_email(\n            user=user,\n            url=settings.GDPR_ARCHIVE_URL + os.path.basename(archive_path),\n        )\n\n\ndef delete_data_archive(archive_path):\n    os.remove(archive_path)\n\n\ndef dump_user_profile(user_dir, user):\n    with open(os.path.join(user_dir, \"profile.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(user_to_json(user), ensure_ascii=False))\n\n    user_tags = UserTag.objects.filter(user=user)\n    with open(os.path.join(user_dir, \"tags.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(user_tags_to_json(user_tags), ensure_ascii=False))\n\n    user_expertises = UserExpertise.objects.filter(user=user)\n    with open(os.path.join(user_dir, \"expertise.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(user_expertises_to_json(user_expertises), ensure_ascii=False))\n\n\ndef dump_user_posts(user_dir, user):\n    posts = Post.objects.filter(author=user).select_related(\"author\", \"topic\")\n\n    for post in posts:\n        post_dir = os.path.join(user_dir, f\"posts/{post.slug}\")\n        os.makedirs(post_dir)\n\n        with open(os.path.join(post_dir, f\"{post.slug}.md\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(post_to_md(post))\n\n        with open(os.path.join(post_dir, f\"{post.slug}.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(post_to_json(post), ensure_ascii=False))\n\n        # dump post comments\n        post_comments = Comment.objects.filter(post=post).select_related(\"author\", \"post\")\n        with open(os.path.join(post_dir, f\"comments.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(comments_to_json(post_comments), ensure_ascii=False))\n\n\ndef dump_user_comments(user_dir, user):\n    comments = Comment.objects.filter(author=user).select_related(\"author\", \"post\")\n\n    for comment in comments:\n        comment_dir = os.path.join(user_dir, f\"comments/{comment.id}\")\n        os.makedirs(comment_dir)\n\n        with open(os.path.join(comment_dir, f\"{comment.id}.md\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(comment_to_md(comment))\n\n        with open(os.path.join(comment_dir, f\"{comment.id}.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(comment_to_json(comment), ensure_ascii=False))\n\n        # dump replies\n        comment_replies = Comment.objects.filter(reply_to=comment).select_related(\"author\", \"post\")\n        with open(os.path.join(comment_dir, f\"replies.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(comments_to_json(comment_replies), ensure_ascii=False))\n\n\ndef dump_user_bookmarks(user_dir, user):\n    bookmarks = PostBookmark.objects.filter(user=user).select_related(\"post\")\n\n    with open(os.path.join(user_dir, \"bookmarks.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(bookmarks_to_json(bookmarks), ensure_ascii=False))\n", "path": "gdpr/archive.py"}], "after_files": [{"content": "from typing import List\n\nfrom django.conf import settings\nfrom django.urls import reverse\n\nfrom bookmarks.models import PostBookmark\nfrom comments.models import Comment\nfrom posts.models.post import Post\nfrom posts.models.votes import PostVote\nfrom users.models.expertise import UserExpertise\nfrom users.models.tags import UserTag\nfrom users.models.user import User\n\n\ndef post_to_md(post: Post) -> str:\n    return f\"# {post.title}\\n\\n{post.text}\"\n\n\ndef post_to_json(post: Post) -> dict:\n    return {\n        \"id\": str(post.id),\n        \"slug\": post.slug,\n        \"author_id\": str(post.author_id),\n        \"type\": post.type,\n        \"topic\": post.topic.name if post.topic else None,\n        \"label\": post.label,\n        \"title\": post.title,\n        \"text\": post.text,\n        \"url\": post.url,\n        \"image\": post.image,\n        \"metadata\": post.metadata,\n        \"created_at\": post.created_at.isoformat(),\n        \"updated_at\": post.updated_at.isoformat(),\n        \"last_activity_at\": post.last_activity_at.isoformat(),\n        \"comment_count\": post.comment_count,\n        \"view_count\": post.view_count,\n        \"upvotes\": post.upvotes,\n    }\n\n\ndef comment_to_md(comment: Comment) -> str:\n    return f\"{comment.text}\\n\\n- {comment.author.slug}\"\n\n\ndef comments_to_json(comments: List[Comment]) -> dict:\n    return {\n        \"comments\": [\n            comment_to_json(comment) for comment in comments\n        ]\n    }\n\n\ndef comment_to_json(comment: Comment) -> dict:\n    return {\n        \"id\": str(comment.id),\n        \"author_id\": str(comment.author_id),\n        \"post_id\": str(comment.post_id),\n        \"post_title\": comment.post.title,\n        \"reply_to_id\": str(comment.reply_to_id) if comment.reply_to else None,\n        \"title\": comment.title,\n        \"text\": comment.text,\n        \"url\": comment.url,\n        \"metadata\": comment.metadata,\n        \"created_at\": comment.created_at.isoformat(),\n        \"updated_at\": comment.updated_at.isoformat(),\n        \"upvotes\": comment.upvotes,\n        \"is_visible\": comment.is_visible,\n        \"is_deleted\": comment.is_deleted,\n        \"is_pinned\": comment.is_pinned,\n    }\n\n\ndef user_to_json(user: User) -> dict:\n    return {\n        \"id\": str(user.id),\n        \"slug\": user.slug,\n        \"email\": user.email,\n        \"full_name\": user.full_name,\n        \"avatar\": user.avatar,\n        \"company\": user.company,\n        \"position\": user.position,\n        \"city\": user.city,\n        \"country\": user.country,\n        \"bio\": user.bio,\n        \"contact\": user.contact,\n        \"hat\": user.hat,\n        \"balance\": user.balance,\n        \"upvotes\": user.upvotes,\n        \"created_at\": user.created_at.isoformat(),\n        \"updated_at\": user.updated_at.isoformat() if user.updated_at else None,\n        \"last_activity_at\": user.last_activity_at.isoformat() if user.last_activity_at else None,\n        \"membership_started_at\": user.membership_started_at.isoformat() if user.membership_started_at else None,\n        \"membership_expires_at\": user.membership_expires_at.isoformat() if user.membership_expires_at else None,\n        \"membership_platform_type\": user.membership_platform_type,\n        \"patreon_id\": user.patreon_id,\n        \"email_digest_type\": user.email_digest_type,\n        \"telegram_id\": user.telegram_id,\n        \"telegram_data\": user.telegram_data,\n        \"stripe_id\": user.stripe_id,\n        \"is_email_verified\": user.is_email_verified,\n        \"is_email_unsubscribed\": user.is_email_unsubscribed,\n        \"moderation_status\": user.moderation_status,\n        \"roles\": user.roles,\n    }\n\n\ndef user_tags_to_json(user_tags: List[UserTag]) -> dict:\n    return {\n        \"user_tags\": [\n            user_tag_to_json(user_tag) for user_tag in user_tags\n        ]\n    }\n\n\ndef user_tag_to_json(user_tag: UserTag) -> dict:\n    return {\n        \"user_id\": str(user_tag.user_id),\n        \"name\": user_tag.name,\n        \"created_at\": user_tag.created_at.isoformat() if user_tag.created_at else None,\n    }\n\n\ndef user_expertises_to_json(user_expertises: List[UserExpertise]) -> dict:\n    return {\n        \"user_expertise\": [\n            user_expertise_to_json(user_expertise) for user_expertise in user_expertises\n        ]\n    }\n\n\ndef user_expertise_to_json(user_expertise: UserExpertise) -> dict:\n    return {\n        \"user_id\": str(user_expertise.user_id),\n        \"name\": user_expertise.name,\n        \"value\": user_expertise.value,\n        \"created_at\": user_expertise.created_at.isoformat() if user_expertise.created_at else None,\n    }\n\n\ndef bookmarks_to_json(bookmarks: List[PostBookmark]) -> dict:\n    return {\n        \"bookmarks\": [\n            bookmark_to_json(bookmark) for bookmark in bookmarks\n        ]\n    }\n\n\ndef bookmark_to_json(bookmark: PostBookmark) -> dict:\n    return {\n        \"url\": settings.APP_HOST + reverse(\"show_post\", kwargs={\n            \"post_type\": bookmark.post.type, \"post_slug\": bookmark.post.slug\n        }),\n        \"created_at\": bookmark.created_at.isoformat() if bookmark.created_at else None,\n    }\n\n\ndef upvotes_to_json(upvotes: List[PostVote]) -> dict:\n    return {\n        \"upvotes\": [\n            upvote_to_json(upvote) for upvote in upvotes\n        ]\n    }\n\n\ndef upvote_to_json(upvote: PostVote) -> dict:\n    return {\n        \"url\": settings.APP_HOST + reverse(\"show_post\", kwargs={\n            \"post_type\": upvote.post.type, \"post_slug\": upvote.post.slug\n        }),\n        \"created_at\": upvote.created_at.isoformat() if upvote.created_at else None,\n        \"title\": upvote.post.title,\n    }\n", "path": "gdpr/serializers.py"}, {"content": "import json\nimport os\nimport random\nimport shutil\nimport tempfile\nfrom datetime import datetime\n\nfrom django.conf import settings\nfrom django_q.tasks import schedule\n\nfrom bookmarks.models import PostBookmark\nfrom comments.models import Comment\nfrom gdpr.serializers import post_to_json, post_to_md, user_to_json, comments_to_json, user_tags_to_json, \\\n    user_expertises_to_json, comment_to_md, comment_to_json, bookmarks_to_json, upvotes_to_json\nfrom notifications.email.users import send_data_archive_ready_email\nfrom posts.models.post import Post\nfrom posts.models.votes import PostVote\nfrom users.models.expertise import UserExpertise\nfrom users.models.tags import UserTag\n\n\ndef generate_data_archive(user, save_path=settings.GDPR_ARCHIVE_STORAGE_PATH):\n    with tempfile.TemporaryDirectory() as tmp_dir:\n        user_dir = os.path.join(tmp_dir, user.slug)\n        os.makedirs(user_dir)\n\n        # dump data\n        dump_user_profile(user_dir, user)\n        dump_user_posts(user_dir, user)\n        dump_user_comments(user_dir, user)\n        dump_user_bookmarks(user_dir, user)\n        dump_user_upvotes(user_dir, user)\n\n        # save zip archive\n        archive_name = f\"{user.slug}-{datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}-{random.randint(1000000, 9999998)}\"\n        archive_path = shutil.make_archive(os.path.join(save_path, archive_name), \"zip\", tmp_dir)\n\n        # schedule a task to remove archive after timeout\n        schedule(\n            \"gdpr.archive.delete_data_archive\",\n            archive_path,\n            next_run=datetime.utcnow() + settings.GDPR_ARCHIVE_DELETE_TIMEDELTA\n        )\n\n        # notify the user\n        send_data_archive_ready_email(\n            user=user,\n            url=settings.GDPR_ARCHIVE_URL + os.path.basename(archive_path),\n        )\n\n\ndef delete_data_archive(archive_path):\n    os.remove(archive_path)\n\n\ndef dump_user_profile(user_dir, user):\n    with open(os.path.join(user_dir, \"profile.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(user_to_json(user), ensure_ascii=False))\n\n    user_tags = UserTag.objects.filter(user=user)\n    with open(os.path.join(user_dir, \"tags.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(user_tags_to_json(user_tags), ensure_ascii=False))\n\n    user_expertises = UserExpertise.objects.filter(user=user)\n    with open(os.path.join(user_dir, \"expertise.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(user_expertises_to_json(user_expertises), ensure_ascii=False))\n\n\ndef dump_user_posts(user_dir, user):\n    posts = Post.objects.filter(author=user).select_related(\"author\", \"topic\")\n\n    for post in posts:\n        post_dir = os.path.join(user_dir, f\"posts/{post.slug}\")\n        os.makedirs(post_dir)\n\n        with open(os.path.join(post_dir, f\"{post.slug}.md\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(post_to_md(post))\n\n        with open(os.path.join(post_dir, f\"{post.slug}.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(post_to_json(post), ensure_ascii=False))\n\n        # dump post comments\n        post_comments = Comment.objects.filter(post=post).select_related(\"author\", \"post\")\n        with open(os.path.join(post_dir, f\"comments.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(comments_to_json(post_comments), ensure_ascii=False))\n\n\ndef dump_user_comments(user_dir, user):\n    comments = Comment.objects.filter(author=user).select_related(\"author\", \"post\")\n\n    for comment in comments:\n        comment_dir = os.path.join(user_dir, f\"comments/{comment.id}\")\n        os.makedirs(comment_dir)\n\n        with open(os.path.join(comment_dir, f\"{comment.id}.md\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(comment_to_md(comment))\n\n        with open(os.path.join(comment_dir, f\"{comment.id}.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(comment_to_json(comment), ensure_ascii=False))\n\n        # dump replies\n        comment_replies = Comment.objects.filter(reply_to=comment).select_related(\"author\", \"post\")\n        with open(os.path.join(comment_dir, f\"replies.json\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(comments_to_json(comment_replies), ensure_ascii=False))\n\n\ndef dump_user_bookmarks(user_dir, user):\n    bookmarks = PostBookmark.objects.filter(user=user).select_related(\"post\")\n\n    with open(os.path.join(user_dir, \"bookmarks.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(bookmarks_to_json(bookmarks), ensure_ascii=False))\n\n\ndef dump_user_upvotes(user_dir, user):\n    upvotes = PostVote.objects.filter(user=user).select_related(\"post\")\n\n    with open(os.path.join(user_dir, \"upvotes.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(json.dumps(upvotes_to_json(upvotes), ensure_ascii=False))\n", "path": "gdpr/archive.py"}]}